mirror of
https://github.com/esphome/esphome.git
synced 2024-11-21 06:28:10 +01:00
🏗 Merge C++ into python codebase (#504)
## Description: Move esphome-core codebase into esphome (and a bunch of other refactors). See https://github.com/esphome/feature-requests/issues/97 Yes this is a shit ton of work and no there's no way to automate it :( But it will be worth it 👍 Progress: - Core support (file copy etc): 80% - Base Abstractions (light, switch): ~50% - Integrations: ~10% - Working? Yes, (but only with ported components). Other refactors: - Moves all codegen related stuff into a single class: `esphome.codegen` (imported as `cg`) - Rework coroutine syntax - Move from `component/platform.py` to `domain/component.py` structure as with HA - Move all defaults out of C++ and into config validation. - Remove `make_...` helpers from Application class. Reason: Merge conflicts with every single new integration. - Pointer Variables are stored globally instead of locally in setup(). Reason: stack size limit. Future work: - Rework const.py - Move all `CONF_...` into a conf class (usage `conf.UPDATE_INTERVAL` vs `CONF_UPDATE_INTERVAL`). Reason: Less convoluted import block - Enable loading from `custom_components` folder. **Related issue (if applicable):** https://github.com/esphome/feature-requests/issues/97 **Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs#<esphome-docs 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).
This commit is contained in:
parent
049807e3ab
commit
6682c43dfa
817 changed files with 54156 additions and 10830 deletions
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: ''
|
80
.gitignore
vendored
80
.gitignore
vendored
|
@ -25,12 +25,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 +45,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 +57,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
|
||||
|
|
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.py --all-headers -j 2 --fix
|
||||
- script/clang-format.py -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,28 +1,22 @@
|
|||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
from esphome import const, core_config, writer, yaml_util
|
||||
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 import const, writer, yaml_util
|
||||
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
|
||||
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
|
||||
|
@ -124,34 +118,17 @@ def run_miniterm(config, port):
|
|||
|
||||
|
||||
def write_cpp(config):
|
||||
from esphome.cpp_generator import Expression, RawStatement, add, statement
|
||||
|
||||
_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 _, component, conf in iter_components(CORE.config):
|
||||
if component.to_code is not None:
|
||||
CORE.add_job(component.to_code, 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
|
||||
|
||||
|
@ -160,17 +137,7 @@ 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):
|
||||
|
@ -195,31 +162,13 @@ def upload_program(config, args, host):
|
|||
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):
|
||||
|
@ -237,7 +186,7 @@ def show_logs(config, args, port):
|
|||
|
||||
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):
|
||||
|
@ -409,7 +358,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.')
|
||||
|
@ -424,7 +372,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.')
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import copy
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
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, coroutine
|
||||
from esphome.core import coroutine
|
||||
from esphome.cpp_generator import Pvariable, TemplateArguments, add, get_variable, \
|
||||
process_lambda, templatable
|
||||
from esphome.cpp_types import Action, App, Component, PollingComponent, Trigger, bool_, \
|
||||
|
@ -15,7 +13,7 @@ from esphome.util import ServiceRegistry
|
|||
|
||||
|
||||
def maybe_simple_id(*validators):
|
||||
validator = vol.All(*validators)
|
||||
validator = cv.All(*validators)
|
||||
|
||||
def validate(value):
|
||||
if isinstance(value, dict):
|
||||
|
@ -32,24 +30,24 @@ def validate_recursive_condition(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)
|
||||
raise cv.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)
|
||||
raise cv.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])
|
||||
raise cv.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)
|
||||
raise cv.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:
|
||||
except cv.Invalid as err:
|
||||
err.prepend(path)
|
||||
raise err
|
||||
value[i] = {
|
||||
|
@ -67,24 +65,24 @@ def validate_recursive_action(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)
|
||||
raise cv.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)
|
||||
raise cv.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])
|
||||
raise cv.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)
|
||||
raise cv.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:
|
||||
except cv.Invalid as err:
|
||||
err.prepend(path)
|
||||
raise err
|
||||
value[i] = {
|
||||
|
@ -116,7 +114,7 @@ LambdaCondition = esphome_ns.class_('LambdaCondition', 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)
|
||||
|
||||
|
@ -125,17 +123,17 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
|||
try:
|
||||
# First try as a sequence of actions
|
||||
return [schema({CONF_THEN: value})]
|
||||
except vol.Invalid as err:
|
||||
except cv.Invalid as err:
|
||||
if err.path and err.path[0] == CONF_THEN:
|
||||
err.path.pop(0)
|
||||
|
||||
# Next try as a sequence of automations
|
||||
try:
|
||||
return cv.Schema([schema])(value)
|
||||
except vol.Invalid as err2:
|
||||
except cv.Invalid as err2:
|
||||
if '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)]
|
||||
|
@ -149,7 +147,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
|
||||
|
||||
|
@ -159,7 +157,7 @@ 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.Required(CONF_THEN): validate_recursive_action,
|
||||
})
|
||||
|
||||
AND_CONDITION_SCHEMA = validate_recursive_condition
|
||||
|
@ -184,9 +182,9 @@ def or_condition_to_code(config, condition_id, template_arg, args):
|
|||
yield Pvariable(condition_id, rhs, type=type)
|
||||
|
||||
|
||||
RANGE_CONDITION_SCHEMA = vol.All(cv.Schema({
|
||||
vol.Optional(CONF_ABOVE): cv.templatable(cv.float_),
|
||||
vol.Optional(CONF_BELOW): cv.templatable(cv.float_),
|
||||
RANGE_CONDITION_SCHEMA = cv.All(cv.Schema({
|
||||
cv.Optional(CONF_ABOVE): cv.templatable(cv.float_),
|
||||
cv.Optional(CONF_BELOW): cv.templatable(cv.float_),
|
||||
}), cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW))
|
||||
|
||||
|
||||
|
@ -218,10 +216,10 @@ def delay_action_to_code(config, action_id, template_arg, args):
|
|||
yield action
|
||||
|
||||
|
||||
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,
|
||||
IF_ACTION_SCHEMA = cv.All({
|
||||
cv.Required(CONF_CONDITION): validate_recursive_condition,
|
||||
cv.Optional(CONF_THEN): validate_recursive_action,
|
||||
cv.Optional(CONF_ELSE): validate_recursive_action,
|
||||
}, cv.has_at_least_one_key(CONF_THEN, CONF_ELSE))
|
||||
|
||||
|
||||
|
@ -241,8 +239,8 @@ def if_action_to_code(config, action_id, template_arg, args):
|
|||
|
||||
|
||||
WHILE_ACTION_SCHEMA = cv.Schema({
|
||||
vol.Required(CONF_CONDITION): validate_recursive_condition,
|
||||
vol.Required(CONF_THEN): validate_recursive_action,
|
||||
cv.Required(CONF_CONDITION): validate_recursive_condition,
|
||||
cv.Required(CONF_THEN): validate_recursive_action,
|
||||
})
|
||||
|
||||
|
||||
|
@ -259,7 +257,7 @@ def while_action_to_code(config, action_id, template_arg, args):
|
|||
|
||||
def validate_wait_until(value):
|
||||
schema = cv.Schema({
|
||||
vol.Required(CONF_CONDITION): validate_recursive_condition
|
||||
cv.Required(CONF_CONDITION): validate_recursive_condition
|
||||
})
|
||||
if isinstance(value, dict) and CONF_CONDITION in value:
|
||||
return schema(value)
|
||||
|
@ -303,7 +301,7 @@ def lambda_condition_to_code(config, condition_id, template_arg, args):
|
|||
|
||||
CONF_COMPONENT_UPDATE = 'component.update'
|
||||
COMPONENT_UPDATE_ACTION_SCHEMA = maybe_simple_id({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(PollingComponent),
|
||||
cv.Required(CONF_ID): cv.use_variable_id(PollingComponent),
|
||||
})
|
||||
|
||||
|
||||
|
@ -352,16 +350,12 @@ def build_conditions(config, templ, args):
|
|||
|
||||
|
||||
@coroutine
|
||||
def build_automation_(trigger, args, config):
|
||||
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)
|
||||
rhs = type.new(trigger)
|
||||
obj = Pvariable(config[CONF_AUTOMATION_ID], rhs, type=type)
|
||||
actions = yield build_actions(config[CONF_THEN], templ, args)
|
||||
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, TemplateArguments,
|
||||
StructInitializer, ArrayInitializer, safe_exp, Statement,
|
||||
progmem_array, statement, variable, Pvariable, new_Pvariable,
|
||||
add, add_global, add_library, add_build_flag, add_define,
|
||||
get_variable, process_lambda, is_template, templatable, MockObj,
|
||||
MockObjClass)
|
||||
from esphome.cpp_helpers import ( # noqa
|
||||
gpio_pin_expression, register_component, build_registry_entry,
|
||||
build_registry_list)
|
||||
from esphome.cpp_types import ( # noqa
|
||||
global_ns, void, nullptr, float_, bool_, std_ns, std_string,
|
||||
std_vector, uint8, uint16, uint32, int32, const_char_ptr, NAN,
|
||||
esphome_ns, App, Nameable, Trigger, Action, 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
|
27
esphome/components/a4988/a4988.h
Normal file
27
esphome/components/a4988/a4988.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
#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:
|
||||
A4988(GPIOPin *step_pin, GPIOPin *dir_pin) : step_pin_(step_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
|
28
esphome/components/a4988/stepper.py
Normal file
28
esphome/components/a4988/stepper.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
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_variable_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):
|
||||
step_pin = yield cg.gpio_pin_expression(config[CONF_STEP_PIN])
|
||||
dir_pin = yield cg.gpio_pin_expression(config[CONF_DIR_PIN])
|
||||
var = cg.new_Pvariable(config[CONF_ID], step_pin, dir_pin)
|
||||
yield cg.register_component(var, config)
|
||||
yield stepper.register_stepper(var, config)
|
||||
|
||||
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
89
esphome/components/adc/adc_sensor.cpp
Normal file
89
esphome/components/adc/adc_sensor.cpp
Normal file
|
@ -0,0 +1,89 @@
|
|||
#include "esphome/components/adc/adc_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace adc {
|
||||
|
||||
static const char *TAG = "adc";
|
||||
|
||||
ADCSensor::ADCSensor(const std::string &name, uint8_t pin, uint32_t update_interval)
|
||||
: PollingSensorComponent(name, update_interval), pin_(pin) {}
|
||||
|
||||
#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() {
|
||||
#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;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#ifdef USE_ADC_SENSOR_VCC
|
||||
float value_v = ESP.getVcc() / 1024.0f;
|
||||
#else
|
||||
float value_v = analogRead(this->pin_) / 1024.0f;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
ESP_LOGD(TAG, "'%s': Got voltage=%.2fV", this->get_name().c_str(), value_v);
|
||||
|
||||
this->publish_state(value_v);
|
||||
}
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
|
||||
#endif
|
||||
|
||||
} // namespace adc
|
||||
} // namespace esphome
|
43
esphome/components/adc/adc_sensor.h
Normal file
43
esphome/components/adc/adc_sensor.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/esphal.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace adc {
|
||||
|
||||
class ADCSensor : public sensor::PollingSensorComponent {
|
||||
public:
|
||||
/// Construct the ADCSensor with the provided pin and update interval in ms.
|
||||
explicit ADCSensor(const std::string &name, uint8_t pin, uint32_t update_interval);
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
/// Set the attenuation for this pin. Only available on the ESP32.
|
||||
void set_attenuation(adc_attenuation_t attenuation);
|
||||
#endif
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
/// 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;
|
||||
|
||||
#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
|
52
esphome/components/adc/sensor.py
Normal file
52
esphome/components/adc/sensor.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import sensor
|
||||
from esphome.const import CONF_ATTENUATION, CONF_ID, CONF_NAME, CONF_PIN, CONF_UPDATE_INTERVAL, \
|
||||
CONF_ICON, ICON_FLASH, CONF_UNIT_OF_MEASUREMENT, UNIT_VOLT, CONF_ACCURACY_DECIMALS
|
||||
|
||||
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.PollingSensorComponent)
|
||||
|
||||
CONFIG_SCHEMA = cv.nameable(sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(ADCSensor),
|
||||
cv.Required(CONF_PIN): validate_adc_pin,
|
||||
cv.Optional(CONF_ATTENUATION): cv.All(cv.only_on_esp32, cv.one_of(*ATTENUATION_MODES,
|
||||
lower=True)),
|
||||
|
||||
cv.Optional(CONF_UPDATE_INTERVAL, default='60s'): cv.update_interval,
|
||||
cv.Optional(CONF_ICON, default=ICON_FLASH): sensor.icon,
|
||||
cv.Optional(CONF_UNIT_OF_MEASUREMENT, default=UNIT_VOLT): sensor.unit_of_measurement,
|
||||
cv.Optional(CONF_ACCURACY_DECIMALS, default=2): sensor.accuracy_decimals,
|
||||
}).extend(cv.COMPONENT_SCHEMA))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
pin = config[CONF_PIN]
|
||||
if pin == 'VCC':
|
||||
pin = 0
|
||||
|
||||
cg.add_define('USE_ADC_SENSOR_VCC')
|
||||
cg.add_global(cg.global_ns.ADC_MODE(cg.global_ns.ADC_VCC))
|
||||
rhs = ADCSensor.new(config[CONF_NAME], pin, config[CONF_UPDATE_INTERVAL])
|
||||
adc = cg.Pvariable(config[CONF_ID], rhs)
|
||||
yield cg.register_component(adc, config)
|
||||
yield sensor.register_sensor(adc, config)
|
||||
|
||||
if CONF_ATTENUATION in config:
|
||||
cg.add(adc.set_attenuation(ATTENUATION_MODES[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']
|
||||
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_variable_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)
|
158
esphome/components/ads1115/ads1115.cpp
Normal file
158
esphome/components/ads1115/ads1115.cpp
Normal file
|
@ -0,0 +1,158 @@
|
|||
#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; }
|
||||
void ADS1115Component::request_measurement_(ADS1115Sensor *sensor) {
|
||||
uint16_t config;
|
||||
if (!this->read_byte_16(ADS1115_REGISTER_CONFIG, &config)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
yield();
|
||||
}
|
||||
|
||||
uint16_t raw_conversion;
|
||||
if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &raw_conversion)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
float v = millivolts / 1000.0f;
|
||||
ESP_LOGD(TAG, "'%s': Got Voltage=%fV", sensor->get_name().c_str(), v);
|
||||
sensor->publish_state(v);
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
} // namespace ads1115
|
||||
} // namespace esphome
|
69
esphome/components/ads1115/ads1115.h
Normal file
69
esphome/components/ads1115/ads1115.h
Normal file
|
@ -0,0 +1,69 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.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;
|
||||
|
||||
protected:
|
||||
/// Helper method to request a measurement from a sensor.
|
||||
void request_measurement_(ADS1115Sensor *sensor);
|
||||
|
||||
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:
|
||||
ADS1115Sensor(const std::string &name, uint32_t update_interval)
|
||||
: sensor::Sensor(name), update_interval_(update_interval) {}
|
||||
|
||||
void set_multiplexer(ADS1115Multiplexer multiplexer);
|
||||
void set_gain(ADS1115Gain gain);
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
uint8_t get_multiplexer() const;
|
||||
uint8_t get_gain() const;
|
||||
|
||||
protected:
|
||||
ADS1115Multiplexer multiplexer_;
|
||||
ADS1115Gain gain_;
|
||||
uint32_t update_interval_;
|
||||
};
|
||||
|
||||
} // namespace ads1115
|
||||
} // namespace esphome
|
|
@ -1,16 +1,15 @@
|
|||
import voluptuous as vol
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.components.ads1115 import ADS1115Component
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADS1115_ID, CONF_GAIN, CONF_MULTIPLEXER, CONF_NAME, \
|
||||
CONF_UPDATE_INTERVAL
|
||||
from esphome.cpp_generator import get_variable
|
||||
from esphome.const import CONF_GAIN, CONF_MULTIPLEXER, CONF_UPDATE_INTERVAL, \
|
||||
ICON_FLASH, UNIT_VOLT, CONF_ID, CONF_NAME
|
||||
from esphome.py_compat import string_types
|
||||
from . import ads1115_ns
|
||||
|
||||
DEPENDENCIES = ['ads1115']
|
||||
|
||||
ADS1115Multiplexer = sensor.sensor_ns.enum('ADS1115Multiplexer')
|
||||
ADS1115Multiplexer = ads1115_ns.enum('ADS1115Multiplexer')
|
||||
MUX = {
|
||||
'A0_A1': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1,
|
||||
'A0_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3,
|
||||
|
@ -22,7 +21,7 @@ MUX = {
|
|||
'A3_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG,
|
||||
}
|
||||
|
||||
ADS1115Gain = sensor.sensor_ns.enum('ADS1115Gain')
|
||||
ADS1115Gain = ads1115_ns.enum('ADS1115Gain')
|
||||
GAIN = {
|
||||
'6.144': ADS1115Gain.ADS1115_GAIN_6P144,
|
||||
'4.096': ADS1115Gain.ADS1115_GAIN_4P096,
|
||||
|
@ -37,35 +36,29 @@ def validate_gain(value):
|
|||
if isinstance(value, float):
|
||||
value = u'{:0.03f}'.format(value)
|
||||
elif not isinstance(value, string_types):
|
||||
raise vol.Invalid('invalid gain "{}"'.format(value))
|
||||
raise cv.Invalid('invalid gain "{}"'.format(value))
|
||||
|
||||
return cv.one_of(*GAIN)(value)
|
||||
|
||||
|
||||
def validate_mux(value):
|
||||
value = cv.string(value).upper()
|
||||
value = value.replace(' ', '_')
|
||||
return cv.one_of(*MUX)(value)
|
||||
ADS1115Sensor = ads1115_ns.class_('ADS1115Sensor', sensor.Sensor)
|
||||
|
||||
|
||||
ADS1115Sensor = sensor.sensor_ns.class_('ADS1115Sensor', sensor.EmptySensor)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(sensor.SENSOR_PLATFORM_SCHEMA.extend({
|
||||
CONF_ADS1115_ID = 'ads1115_id'
|
||||
CONFIG_SCHEMA = cv.nameable(sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 3).extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(ADS1115Sensor),
|
||||
vol.Required(CONF_MULTIPLEXER): validate_mux,
|
||||
vol.Required(CONF_GAIN): validate_gain,
|
||||
cv.GenerateID(CONF_ADS1115_ID): cv.use_variable_id(ADS1115Component),
|
||||
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
|
||||
cv.Required(CONF_MULTIPLEXER): cv.one_of(*MUX, upper=True, space='_'),
|
||||
cv.Required(CONF_GAIN): validate_gain,
|
||||
cv.Optional(CONF_UPDATE_INTERVAL, default='60s'): cv.update_interval,
|
||||
}))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
hub = yield get_variable(config[CONF_ADS1115_ID])
|
||||
hub = yield cg.get_variable(config[CONF_ADS1115_ID])
|
||||
var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME], config[CONF_UPDATE_INTERVAL])
|
||||
cg.add(var.set_multiplexer(MUX[config[CONF_MULTIPLEXER]]))
|
||||
cg.add(var.set_gain(GAIN[config[CONF_GAIN]]))
|
||||
yield sensor.register_sensor(var, config)
|
||||
|
||||
mux = MUX[config[CONF_MULTIPLEXER]]
|
||||
gain = GAIN[config[CONF_GAIN]]
|
||||
rhs = hub.get_sensor(config[CONF_NAME], mux, gain, config.get(CONF_UPDATE_INTERVAL))
|
||||
sensor.register_sensor(rhs, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_ADS1115_SENSOR'
|
||||
cg.add(hub.register_sensor(var))
|
|
@ -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'
|
24
esphome/components/apds9960/__init__.py
Normal file
24
esphome/components/apds9960/__init__.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c
|
||||
from esphome.const import CONF_ID, CONF_UPDATE_INTERVAL
|
||||
|
||||
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_variable_id(APDS9960),
|
||||
cv.Optional(CONF_UPDATE_INTERVAL, default='60s'): cv.update_interval,
|
||||
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x39))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID], config[CONF_UPDATE_INTERVAL])
|
||||
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
|
62
esphome/components/apds9960/apds9960.h
Normal file
62
esphome/components/apds9960/apds9960.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
#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:
|
||||
APDS9960(uint32_t update_interval) : PollingComponent(update_interval) {}
|
||||
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 = cv.nameable(binary_sensor.BINARY_SENSOR_SCHEMA.extend({
|
||||
cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True),
|
||||
cv.GenerateID(CONF_APDS9960_ID): cv.use_variable_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))
|
32
esphome/components/apds9960/sensor.py
Normal file
32
esphome/components/apds9960/sensor.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import CONF_TYPE, CONF_UNIT_OF_MEASUREMENT, CONF_ACCURACY_DECIMALS, CONF_ICON, \
|
||||
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 = cv.nameable(sensor.SENSOR_SCHEMA.extend({
|
||||
cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
|
||||
cv.GenerateID(CONF_APDS9960_ID): cv.use_variable_id(APDS9960),
|
||||
|
||||
cv.Optional(CONF_UNIT_OF_MEASUREMENT, default=UNIT_PERCENT): sensor.unit_of_measurement,
|
||||
cv.Optional(CONF_ACCURACY_DECIMALS, default=1): sensor.accuracy_decimals,
|
||||
cv.Optional(CONF_ICON, default=ICON_LIGHTBULB): sensor.icon,
|
||||
}))
|
||||
|
||||
|
||||
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))
|
|
@ -1,24 +1,20 @@
|
|||
import voluptuous as vol
|
||||
|
||||
from esphome import automation
|
||||
from esphome.automation import ACTION_REGISTRY, CONDITION_REGISTRY, Condition
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
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
|
||||
from esphome.cpp_generator import Pvariable, add, get_variable, process_lambda
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import Action, App, Component, StoringController, esphome_ns, Trigger, \
|
||||
bool_, int32, float_, std_string
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
|
||||
api_ns = esphome_ns.namespace('api')
|
||||
APIServer = api_ns.class_('APIServer', Component, StoringController)
|
||||
HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', Action)
|
||||
api_ns = cg.esphome_ns.namespace('api')
|
||||
APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller)
|
||||
HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', cg.Action)
|
||||
KeyValuePair = api_ns.class_('KeyValuePair')
|
||||
TemplatableKeyValuePair = api_ns.class_('TemplatableKeyValuePair')
|
||||
APIConnectedCondition = api_ns.class_('APIConnectedCondition', Condition)
|
||||
|
||||
UserService = api_ns.class_('UserService', Trigger)
|
||||
UserService = api_ns.class_('UserService', cg.Trigger)
|
||||
ServiceTypeArgument = api_ns.class_('ServiceTypeArgument')
|
||||
ServiceArgType = api_ns.enum('ServiceArgType')
|
||||
SERVICE_ARG_TYPES = {
|
||||
|
@ -28,38 +24,37 @@ SERVICE_ARG_TYPES = {
|
|||
'string': ServiceArgType.SERVICE_ARG_TYPE_STRING,
|
||||
}
|
||||
SERVICE_ARG_NATIVE_TYPES = {
|
||||
'bool': bool_,
|
||||
'int': int32,
|
||||
'float': float_,
|
||||
'string': std_string,
|
||||
'bool': bool,
|
||||
'int': cg.int32,
|
||||
'float': float,
|
||||
'string': cg.std_string,
|
||||
}
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(APIServer),
|
||||
vol.Optional(CONF_PORT, default=6053): cv.port,
|
||||
vol.Optional(CONF_PASSWORD, default=''): cv.string_strict,
|
||||
vol.Optional(CONF_REBOOT_TIMEOUT): cv.positive_time_period_milliseconds,
|
||||
vol.Optional(CONF_SERVICES): automation.validate_automation({
|
||||
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_variable_id(UserService),
|
||||
vol.Required(CONF_SERVICE): cv.valid_name,
|
||||
vol.Optional(CONF_VARIABLES, default={}): cv.Schema({
|
||||
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.schema)
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
@coroutine_with_priority(40.0)
|
||||
def to_code(config):
|
||||
rhs = App.init_api_server()
|
||||
api = Pvariable(config[CONF_ID], rhs)
|
||||
rhs = APIServer.new()
|
||||
api = cg.Pvariable(config[CONF_ID], rhs)
|
||||
yield cg.register_component(api, config)
|
||||
|
||||
if config[CONF_PORT] != 6053:
|
||||
add(api.set_port(config[CONF_PORT]))
|
||||
if config.get(CONF_PASSWORD):
|
||||
add(api.set_password(config[CONF_PASSWORD]))
|
||||
if CONF_REBOOT_TIMEOUT in config:
|
||||
add(api.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
||||
cg.add(api.set_port(config[CONF_PORT]))
|
||||
cg.add(api.set_password(config[CONF_PASSWORD]))
|
||||
cg.add(api.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
||||
|
||||
for conf in config.get(CONF_SERVICES, []):
|
||||
template_args = []
|
||||
|
@ -73,58 +68,51 @@ def to_code(config):
|
|||
func = api.make_user_service_trigger.template(*template_args)
|
||||
rhs = func(conf[CONF_SERVICE], service_type_args)
|
||||
type_ = UserService.template(*template_args)
|
||||
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs, type=type_)
|
||||
automation.build_automations(trigger, func_args, conf)
|
||||
trigger = cg.Pvariable(conf[CONF_TRIGGER_ID], rhs, type=type_)
|
||||
yield automation.build_automation(trigger, func_args, conf)
|
||||
|
||||
setup_component(api, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_API'
|
||||
|
||||
|
||||
def lib_deps(config):
|
||||
cg.add_define('USE_API')
|
||||
if CORE.is_esp32:
|
||||
return 'AsyncTCP@1.0.3'
|
||||
if CORE.is_esp8266:
|
||||
return 'ESPAsyncTCP@1.2.0'
|
||||
raise NotImplementedError
|
||||
cg.add_library('AsyncTCP', '1.0.3')
|
||||
elif CORE.is_esp8266:
|
||||
cg.add_library('ESPAsyncTCP', '1.2.0')
|
||||
|
||||
|
||||
CONF_HOMEASSISTANT_SERVICE = 'homeassistant.service'
|
||||
HOMEASSISTANT_SERVIC_ACTION_SCHEMA = cv.Schema({
|
||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.use_variable_id(APIServer),
|
||||
vol.Required(CONF_SERVICE): cv.string,
|
||||
vol.Optional(CONF_DATA): cv.Schema({
|
||||
cv.Required(CONF_SERVICE): cv.string,
|
||||
cv.Optional(CONF_DATA): cv.Schema({
|
||||
cv.string: cv.string,
|
||||
}),
|
||||
vol.Optional(CONF_DATA_TEMPLATE): cv.Schema({
|
||||
cv.Optional(CONF_DATA_TEMPLATE): cv.Schema({
|
||||
cv.string: cv.string,
|
||||
}),
|
||||
vol.Optional(CONF_VARIABLES): cv.Schema({
|
||||
cv.Optional(CONF_VARIABLES): cv.Schema({
|
||||
cv.string: cv.lambda_,
|
||||
}),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_HOMEASSISTANT_SERVICE, HOMEASSISTANT_SERVIC_ACTION_SCHEMA)
|
||||
@ACTION_REGISTRY.register(CONF_HOMEASSISTANT_SERVICE, HOMEASSISTANT_SERVICE_ACTION_SCHEMA)
|
||||
def homeassistant_service_to_code(config, action_id, template_arg, args):
|
||||
var = yield get_variable(config[CONF_ID])
|
||||
rhs = var.make_home_assistant_service_call_action(template_arg)
|
||||
var = yield cg.get_variable(config[CONF_ID])
|
||||
type = HomeAssistantServiceCallAction.template(template_arg)
|
||||
act = Pvariable(action_id, rhs, type=type)
|
||||
add(act.set_service(config[CONF_SERVICE]))
|
||||
rhs = type.new(var)
|
||||
act = cg.Pvariable(action_id, rhs, type=type)
|
||||
cg.add(act.set_service(config[CONF_SERVICE]))
|
||||
if CONF_DATA in config:
|
||||
datas = [KeyValuePair(k, v) for k, v in config[CONF_DATA].items()]
|
||||
add(act.set_data(datas))
|
||||
cg.add(act.set_data(datas))
|
||||
if CONF_DATA_TEMPLATE in config:
|
||||
datas = [KeyValuePair(k, v) for k, v in config[CONF_DATA_TEMPLATE].items()]
|
||||
add(act.set_data_template(datas))
|
||||
cg.add(act.set_data_template(datas))
|
||||
if CONF_VARIABLES in config:
|
||||
datas = []
|
||||
for key, value in config[CONF_VARIABLES].items():
|
||||
value_ = yield process_lambda(value, [])
|
||||
value_ = yield cg.process_lambda(value, [])
|
||||
datas.append(TemplatableKeyValuePair(key, value_))
|
||||
add(act.set_variables(datas))
|
||||
cg.add(act.set_variables(datas))
|
||||
yield act
|
||||
|
||||
|
||||
|
@ -136,4 +124,4 @@ API_CONNECTED_CONDITION_SCHEMA = cv.Schema({})
|
|||
def api_connected_to_code(config, condition_id, template_arg, args):
|
||||
rhs = APIConnectedCondition.new(template_arg)
|
||||
type = APIConnectedCondition.template(template_arg)
|
||||
yield Pvariable(condition_id, rhs, type=type)
|
||||
yield cg.Pvariable(condition_id, rhs, type=type)
|
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
|
1202
esphome/components/api/api_server.cpp
Normal file
1202
esphome/components/api/api_server.cpp
Normal file
File diff suppressed because it is too large
Load diff
245
esphome/components/api/api_server.h
Normal file
245
esphome/components/api/api_server.h
Normal file
|
@ -0,0 +1,245 @@
|
|||
#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_);
|
||||
this->play_next(x...);
|
||||
}
|
||||
|
||||
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
159
esphome/components/bang_bang/bang_bang_climate.cpp
Normal file
159
esphome/components/bang_bang/bang_bang_climate.cpp
Normal file
|
@ -0,0 +1,159 @@
|
|||
#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(const std::string &name)
|
||||
: climate::Climate(name),
|
||||
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(const std::string &name);
|
||||
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_NAME, CONF_SENSOR
|
||||
|
||||
bang_bang_ns = cg.esphome_ns.namespace('bang_bang')
|
||||
BangBangClimate = bang_bang_ns.class_('BangBangClimate', climate.ClimateDevice)
|
||||
BangBangClimateTargetTempConfig = bang_bang_ns.struct('BangBangClimateTargetTempConfig')
|
||||
|
||||
CONFIG_SCHEMA = cv.nameable(climate.CLIMATE_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(BangBangClimate),
|
||||
cv.Required(CONF_SENSOR): cv.use_variable_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], config[CONF_NAME])
|
||||
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
81
esphome/components/bh1750/bh1750.cpp
Normal file
81
esphome/components/bh1750/bh1750.cpp
Normal file
|
@ -0,0 +1,81 @@
|
|||
#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;
|
||||
|
||||
BH1750Sensor::BH1750Sensor(const std::string &name, uint32_t update_interval)
|
||||
: PollingSensorComponent(name, update_interval) {}
|
||||
|
||||
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
|
48
esphome/components/bh1750/bh1750.h
Normal file
48
esphome/components/bh1750/bh1750.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
#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::PollingSensorComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
BH1750Sensor(const std::string &name, uint32_t update_interval);
|
||||
|
||||
/** 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
|
32
esphome/components/bh1750/sensor.py
Normal file
32
esphome/components/bh1750/sensor.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import CONF_ID, CONF_NAME, CONF_RESOLUTION, CONF_UPDATE_INTERVAL, 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.PollingSensorComponent, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.nameable(sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 1).extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(BH1750Sensor),
|
||||
cv.Optional(CONF_RESOLUTION, default=0.0): cv.one_of(*BH1750_RESOLUTIONS, float=True),
|
||||
cv.Optional(CONF_UPDATE_INTERVAL, default='60s'): cv.update_interval,
|
||||
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x23)))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME], config[CONF_UPDATE_INTERVAL])
|
||||
yield cg.register_component(var, config)
|
||||
yield sensor.register_sensor(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
|
||||
cg.add(var.set_resolution(BH1750_RESOLUTIONS[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')
|
25
esphome/components/binary/fan/__init__.py
Normal file
25
esphome/components/binary/fan/__init__.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
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 = cv.nameable(fan.FAN_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_variable_id(BinaryFan),
|
||||
cv.Required(CONF_OUTPUT): cv.use_variable_id(output.BinaryOutput),
|
||||
cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_variable_id(output.BinaryOutput),
|
||||
}).extend(cv.COMPONENT_SCHEMA))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
output_ = yield cg.get_variable(config[CONF_OUTPUT])
|
||||
state = yield fan.create_fan_state(config)
|
||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID], state, output_)
|
||||
yield cg.register_component(var, config)
|
||||
|
||||
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
|
27
esphome/components/binary/fan/binary_fan.h
Normal file
27
esphome/components/binary/fan/binary_fan.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
#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:
|
||||
BinaryFan(fan::FanState *fan, output::BinaryOutput *output) : fan_(fan), 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
|
18
esphome/components/binary/light/__init__.py
Normal file
18
esphome/components/binary/light/__init__.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
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 = cv.nameable(light.BINARY_LIGHT_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_variable_id(BinaryLightOutput),
|
||||
cv.Required(CONF_OUTPUT): cv.use_variable_id(output.BinaryOutput),
|
||||
}))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
out = yield cg.get_variable(config[CONF_OUTPUT])
|
||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID], out)
|
||||
yield light.register_light(var, config)
|
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:
|
||||
BinaryLightOutput(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
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
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.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.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
|
||||
CONF_STATE, CONF_TIMING, CONF_TRIGGER_ID, CONF_FOR, CONF_VALUE, CONF_NAME, CONF_MQTT_ID
|
||||
from esphome.core import CORE, coroutine
|
||||
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
|
||||
from esphome.py_compat import string_types
|
||||
from esphome.util import ServiceRegistry
|
||||
|
||||
DEVICE_CLASSES = [
|
||||
'', 'battery', 'cold', 'connectivity', 'door', 'garage_door', 'gas',
|
||||
|
@ -22,50 +19,88 @@ 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', cg.Trigger.template())
|
||||
ReleaseTrigger = binary_sensor_ns.class_('ReleaseTrigger', cg.Trigger.template())
|
||||
ClickTrigger = binary_sensor_ns.class_('ClickTrigger', cg.Trigger.template())
|
||||
DoubleClickTrigger = binary_sensor_ns.class_('DoubleClickTrigger', cg.Trigger.template())
|
||||
MultiClickTrigger = binary_sensor_ns.class_('MultiClickTrigger', cg.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', cg.Trigger.template(bool))
|
||||
BinarySensorPublishAction = binary_sensor_ns.class_('BinarySensorPublishAction', cg.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 = ServiceRegistry()
|
||||
validate_filters = cv.validate_registry('filter', FILTER_REGISTRY, [CONF_ID])
|
||||
|
||||
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',
|
||||
cv.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(InvertFilter)
|
||||
}))
|
||||
def invert_filter_to_code(config):
|
||||
rhs = InvertFilter.new()
|
||||
var = cg.Pvariable(config[CONF_ID], rhs)
|
||||
yield var
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register('delayed_on',
|
||||
cv.maybe_simple_value(cv.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(DelayedOnFilter),
|
||||
cv.Required(CONF_VALUE): cv.positive_time_period_milliseconds,
|
||||
}).extend(cv.COMPONENT_SCHEMA)))
|
||||
def delayed_on_filter_to_code(config):
|
||||
rhs = DelayedOnFilter.new(config[CONF_VALUE])
|
||||
var = cg.Pvariable(config[CONF_ID], rhs)
|
||||
yield cg.register_component(var, config)
|
||||
yield var
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register('delayed_off',
|
||||
cv.maybe_simple_value(cv.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(DelayedOffFilter),
|
||||
cv.Required(CONF_VALUE): cv.positive_time_period_milliseconds,
|
||||
}).extend(cv.COMPONENT_SCHEMA)))
|
||||
def delayed_off_filter_to_code(config):
|
||||
rhs = DelayedOffFilter.new(config[CONF_VALUE])
|
||||
var = cg.Pvariable(config[CONF_ID], rhs)
|
||||
yield cg.register_component(var, config)
|
||||
yield var
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register('lambda',
|
||||
cv.maybe_simple_value(cv.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(LambdaFilter),
|
||||
cv.Required(CONF_VALUE): cv.lambda_,
|
||||
})))
|
||||
def lambda_filter_to_code(config):
|
||||
lambda_ = yield cg.process_lambda(config[CONF_VALUE], [(bool, 'x')],
|
||||
return_type=cg.optional.template(bool))
|
||||
rhs = LambdaFilter.new(lambda_)
|
||||
var = cg.Pvariable(config[CONF_ID], rhs)
|
||||
yield var
|
||||
|
||||
|
||||
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 +110,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 +126,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 +159,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 +167,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,164 +190,138 @@ def validate_multi_click_timing(value):
|
|||
return timings
|
||||
|
||||
|
||||
BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_MQTT_ID): cv.declare_variable_id(MQTTBinarySensorComponent),
|
||||
device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space='_')
|
||||
|
||||
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({
|
||||
BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(BinarySensor),
|
||||
cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_variable_id(mqtt.MQTTBinarySensorComponent),
|
||||
|
||||
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_variable_id(PressTrigger),
|
||||
}),
|
||||
vol.Optional(CONF_ON_RELEASE): automation.validate_automation({
|
||||
cv.Optional(CONF_ON_RELEASE): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(ReleaseTrigger),
|
||||
}),
|
||||
vol.Optional(CONF_ON_CLICK): automation.validate_automation({
|
||||
cv.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_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.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_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.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.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.Optional(CONF_ON_STATE): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_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)
|
||||
|
||||
|
||||
@coroutine
|
||||
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:
|
||||
lambda_ = yield process_lambda(config[CONF_LAMBDA], [(bool_, 'x')],
|
||||
return_type=optional.template(bool_))
|
||||
yield LambdaFilter.new(lambda_)
|
||||
|
||||
|
||||
@coroutine
|
||||
def setup_filters(config):
|
||||
filters = []
|
||||
for conf in config:
|
||||
filters.append((yield setup_filter(conf)))
|
||||
yield filters
|
||||
|
||||
|
||||
@coroutine
|
||||
def setup_binary_sensor_core_(binary_sensor_var, config):
|
||||
def setup_binary_sensor_core_(var, config):
|
||||
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:
|
||||
filters = yield setup_filters(config[CONF_FILTERS])
|
||||
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 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_IS_ON_OFF_CONDITION_SCHEMA = maybe_simple_id({
|
||||
cv.Required(CONF_ID): cv.use_variable_id(BinarySensor),
|
||||
cv.Optional(CONF_FOR): cv.positive_time_period_milliseconds,
|
||||
})
|
||||
|
||||
|
||||
@CONDITION_REGISTRY.register(CONF_BINARY_SENSOR_IS_ON, BINARY_SENSOR_IS_ON_CONDITION_SCHEMA)
|
||||
@CONDITION_REGISTRY.register('binary_sensor.is_on', BINARY_SENSOR_IS_ON_OFF_CONDITION_SCHEMA)
|
||||
def binary_sensor_is_on_to_code(config, condition_id, template_arg, args):
|
||||
var = yield get_variable(config[CONF_ID])
|
||||
rhs = var.make_binary_sensor_is_on_condition(template_arg, config.get(CONF_FOR))
|
||||
var = yield cg.get_variable(config[CONF_ID])
|
||||
type = BinarySensorCondition.template(template_arg)
|
||||
yield Pvariable(condition_id, rhs, type=type)
|
||||
rhs = type.new(var, True, config.get(CONF_FOR))
|
||||
yield cg.Pvariable(condition_id, rhs, type=type)
|
||||
|
||||
|
||||
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)
|
||||
@CONDITION_REGISTRY.register('binary_sensor.is_off', BINARY_SENSOR_IS_ON_OFF_CONDITION_SCHEMA)
|
||||
def binary_sensor_is_off_to_code(config, condition_id, template_arg, args):
|
||||
var = yield get_variable(config[CONF_ID])
|
||||
rhs = var.make_binary_sensor_is_off_condition(template_arg, config.get(CONF_FOR))
|
||||
var = yield cg.get_variable(config[CONF_ID])
|
||||
type = BinarySensorCondition.template(template_arg)
|
||||
yield Pvariable(condition_id, rhs, type=type)
|
||||
rhs = type.new(var, False, config.get(CONF_FOR))
|
||||
yield cg.Pvariable(condition_id, rhs, type=type)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
cg.add_define('USE_BINARY_SENSOR')
|
||||
cg.add_global(binary_sensor_ns.using)
|
||||
|
|
|
@ -1,31 +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):
|
||||
hub = yield get_variable(config[CONF_APDS9960_ID])
|
||||
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
|
161
esphome/components/binary_sensor/automation.h
Normal file
161
esphome/components/binary_sensor/automation.h
Normal file
|
@ -0,0 +1,161 @@
|
|||
#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, uint32_t for_time = 0)
|
||||
: parent_(parent), state_(state), for_time_(for_time) {
|
||||
parent->add_on_state_callback([this](bool state) { this->last_state_time_ = millis(); });
|
||||
}
|
||||
bool check(Ts... x) override {
|
||||
if (this->parent_->state != this->state_)
|
||||
return false;
|
||||
|
||||
return millis() - this->last_state_time_ >= this->for_time_;
|
||||
}
|
||||
|
||||
protected:
|
||||
BinarySensor *parent_;
|
||||
bool state_;
|
||||
uint32_t last_state_time_{0};
|
||||
uint32_t for_time_{0};
|
||||
};
|
||||
|
||||
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);
|
||||
this->play_next(x...);
|
||||
}
|
||||
|
||||
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 "esphome/components/binary_sensor/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:
|
||||
/** Construct a binary sensor with the specified name
|
||||
*
|
||||
* @param name Name of this binary sensor.
|
||||
*/
|
||||
explicit BinarySensor(const std::string &name);
|
||||
explicit BinarySensor();
|
||||
|
||||
/** 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,34 +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):
|
||||
template_ = yield process_lambda(config[CONF_LAMBDA], [],
|
||||
return_type=std_vector.template(binary_sensor.BinarySensorPtr))
|
||||
|
||||
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,24 +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):
|
||||
hub = yield get_variable(config[CONF_ESP32_BLE_ID])
|
||||
rhs = hub.make_presence_sensor(config[CONF_NAME], make_address_array(config[CONF_MAC_ADDRESS]))
|
||||
binary_sensor.register_binary_sensor(rhs, config)
|
|
@ -1,56 +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 = yield get_variable(config[CONF_ESP32_TOUCH_ID])
|
||||
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 "esphome/components/binary_sensor/filter.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
namespace binary_sensor {
|
||||
|
||||
static const char *TAG = "something.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
|
|
@ -1,29 +0,0 @@
|
|||
import voluptuous as vol
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_NAME, CONF_PIN
|
||||
from esphome.cpp_generator import Pvariable
|
||||
from esphome.cpp_helpers import gpio_input_pin_expression, setup_component
|
||||
from esphome.cpp_types import App, Component
|
||||
|
||||
GPIOBinarySensorComponent = binary_sensor.binary_sensor_ns.class_('GPIOBinarySensorComponent',
|
||||
binary_sensor.BinarySensor,
|
||||
Component)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(GPIOBinarySensorComponent),
|
||||
vol.Required(CONF_PIN): pins.gpio_input_pin_schema
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
pin = yield gpio_input_pin_expression(config[CONF_PIN])
|
||||
rhs = App.make_gpio_binary_sensor(config[CONF_NAME], pin)
|
||||
gpio = Pvariable(config[CONF_ID], rhs)
|
||||
binary_sensor.setup_binary_sensor(gpio, config)
|
||||
setup_component(gpio, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_GPIO_BINARY_SENSOR'
|
|
@ -1,26 +0,0 @@
|
|||
import voluptuous as vol
|
||||
|
||||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ENTITY_ID, CONF_ID, CONF_NAME
|
||||
from esphome.cpp_generator import Pvariable
|
||||
from esphome.cpp_types import App
|
||||
|
||||
DEPENDENCIES = ['api']
|
||||
|
||||
HomeassistantBinarySensor = binary_sensor.binary_sensor_ns.class_('HomeassistantBinarySensor',
|
||||
binary_sensor.BinarySensor)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(HomeassistantBinarySensor),
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
}))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_homeassistant_binary_sensor(config[CONF_NAME], config[CONF_ENTITY_ID])
|
||||
subs = Pvariable(config[CONF_ID], rhs)
|
||||
binary_sensor.setup_binary_sensor(subs, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_HOMEASSISTANT_BINARY_SENSOR'
|
|
@ -1,23 +0,0 @@
|
|||
import voluptuous as vol
|
||||
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.components.mpr121 import MPR121Component, CONF_MPR121_ID
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHANNEL, CONF_NAME
|
||||
from esphome.cpp_generator import get_variable
|
||||
|
||||
DEPENDENCIES = ['mpr121']
|
||||
MPR121Channel = binary_sensor.binary_sensor_ns.class_(
|
||||
'MPR121Channel', binary_sensor.BinarySensor)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(MPR121Channel),
|
||||
cv.GenerateID(CONF_MPR121_ID): cv.use_variable_id(MPR121Component),
|
||||
vol.Required(CONF_CHANNEL): vol.All(vol.Coerce(int), vol.Range(min=0, max=11))
|
||||
}))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
hub = yield get_variable(config[CONF_MPR121_ID])
|
||||
rhs = MPR121Channel.new(config[CONF_NAME], config[CONF_CHANNEL])
|
||||
binary_sensor.register_binary_sensor(hub.add_channel(rhs), config)
|
|
@ -1,28 +0,0 @@
|
|||
import voluptuous as vol
|
||||
|
||||
from esphome.components import binary_sensor, display
|
||||
from esphome.components.display.nextion import Nextion
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_COMPONENT_ID, CONF_NAME, CONF_PAGE_ID
|
||||
from esphome.cpp_generator import get_variable
|
||||
|
||||
DEPENDENCIES = ['display']
|
||||
|
||||
CONF_NEXTION_ID = 'nextion_id'
|
||||
|
||||
NextionTouchComponent = display.display_ns.class_('NextionTouchComponent',
|
||||
binary_sensor.BinarySensor)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(NextionTouchComponent),
|
||||
vol.Required(CONF_PAGE_ID): cv.uint8_t,
|
||||
vol.Required(CONF_COMPONENT_ID): cv.uint8_t,
|
||||
cv.GenerateID(CONF_NEXTION_ID): cv.use_variable_id(Nextion)
|
||||
}))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
hub = yield get_variable(config[CONF_NEXTION_ID])
|
||||
rhs = hub.make_touch_component(config[CONF_NAME], config[CONF_PAGE_ID],
|
||||
config[CONF_COMPONENT_ID])
|
||||
binary_sensor.register_binary_sensor(rhs, config)
|
|
@ -1,44 +0,0 @@
|
|||
import voluptuous as vol
|
||||
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.components.pn532 import PN532Component
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_NAME, CONF_UID
|
||||
from esphome.core import HexInt
|
||||
from esphome.cpp_generator import get_variable
|
||||
|
||||
DEPENDENCIES = ['pn532']
|
||||
|
||||
CONF_PN532_ID = 'pn532_id'
|
||||
|
||||
|
||||
def validate_uid(value):
|
||||
value = cv.string_strict(value)
|
||||
for x in value.split('-'):
|
||||
if len(x) != 2:
|
||||
raise vol.Invalid("Each part (separated by '-') of the UID must be two characters "
|
||||
"long.")
|
||||
try:
|
||||
x = int(x, 16)
|
||||
except ValueError:
|
||||
raise vol.Invalid("Valid characters for parts of a UID are 0123456789ABCDEF.")
|
||||
if x < 0 or x > 255:
|
||||
raise vol.Invalid("Valid values for UID parts (separated by '-') are 00 to FF")
|
||||
return value
|
||||
|
||||
|
||||
PN532BinarySensor = binary_sensor.binary_sensor_ns.class_('PN532BinarySensor',
|
||||
binary_sensor.BinarySensor)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(PN532BinarySensor),
|
||||
vol.Required(CONF_UID): validate_uid,
|
||||
cv.GenerateID(CONF_PN532_ID): cv.use_variable_id(PN532Component)
|
||||
}))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
hub = yield get_variable(config[CONF_PN532_ID])
|
||||
addr = [HexInt(int(x, 16)) for x in config[CONF_UID].split('-')]
|
||||
rhs = hub.make_tag(config[CONF_NAME], addr)
|
||||
binary_sensor.register_binary_sensor(rhs, config)
|
|
@ -1,25 +0,0 @@
|
|||
import voluptuous as vol
|
||||
|
||||
from esphome.components import binary_sensor, rdm6300
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_NAME, CONF_UID
|
||||
from esphome.cpp_generator import get_variable
|
||||
|
||||
DEPENDENCIES = ['rdm6300']
|
||||
|
||||
CONF_RDM6300_ID = 'rdm6300_id'
|
||||
|
||||
RDM6300BinarySensor = binary_sensor.binary_sensor_ns.class_('RDM6300BinarySensor',
|
||||
binary_sensor.BinarySensor)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(RDM6300BinarySensor),
|
||||
vol.Required(CONF_UID): cv.uint32_t,
|
||||
cv.GenerateID(CONF_RDM6300_ID): cv.use_variable_id(rdm6300.RDM6300Component)
|
||||
}))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
hub = yield get_variable(config[CONF_RDM6300_ID])
|
||||
rhs = hub.make_card(config[CONF_NAME], config[CONF_UID])
|
||||
binary_sensor.register_binary_sensor(rhs, config)
|
|
@ -1,146 +0,0 @@
|
|||
import voluptuous as vol
|
||||
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.components.remote_receiver import RemoteReceiverComponent, remote_ns
|
||||
from esphome.components.remote_transmitter import RC_SWITCH_RAW_SCHEMA, \
|
||||
RC_SWITCH_TYPE_A_SCHEMA, RC_SWITCH_TYPE_B_SCHEMA, RC_SWITCH_TYPE_C_SCHEMA, \
|
||||
RC_SWITCH_TYPE_D_SCHEMA, binary_code, build_rc_switch_protocol
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_CHANNEL, CONF_CODE, CONF_COMMAND, CONF_DATA, \
|
||||
CONF_DEVICE, CONF_FAMILY, CONF_GROUP, CONF_ID, CONF_JVC, CONF_LG, CONF_NAME, CONF_NBITS, \
|
||||
CONF_NEC, CONF_PANASONIC, CONF_PROTOCOL, CONF_RAW, CONF_RC_SWITCH_RAW, CONF_RC_SWITCH_TYPE_A, \
|
||||
CONF_RC_SWITCH_TYPE_B, CONF_RC_SWITCH_TYPE_C, CONF_RC_SWITCH_TYPE_D, CONF_SAMSUNG, CONF_SONY, \
|
||||
CONF_STATE, CONF_RC5
|
||||
from esphome.cpp_generator import Pvariable, get_variable, progmem_array
|
||||
from esphome.cpp_types import int32
|
||||
|
||||
DEPENDENCIES = ['remote_receiver']
|
||||
|
||||
REMOTE_KEYS = [CONF_JVC, CONF_NEC, CONF_LG, CONF_SONY, CONF_PANASONIC, CONF_SAMSUNG, CONF_RAW,
|
||||
CONF_RC_SWITCH_RAW, CONF_RC_SWITCH_TYPE_A, CONF_RC_SWITCH_TYPE_B,
|
||||
CONF_RC_SWITCH_TYPE_C, CONF_RC_SWITCH_TYPE_D, CONF_RC5]
|
||||
|
||||
CONF_REMOTE_RECEIVER_ID = 'remote_receiver_id'
|
||||
CONF_RECEIVER_ID = 'receiver_id'
|
||||
|
||||
RemoteReceiver = remote_ns.class_('RemoteReceiver', binary_sensor.BinarySensor)
|
||||
JVCReceiver = remote_ns.class_('JVCReceiver', RemoteReceiver)
|
||||
LGReceiver = remote_ns.class_('LGReceiver', RemoteReceiver)
|
||||
NECReceiver = remote_ns.class_('NECReceiver', RemoteReceiver)
|
||||
PanasonicReceiver = remote_ns.class_('PanasonicReceiver', RemoteReceiver)
|
||||
RawReceiver = remote_ns.class_('RawReceiver', RemoteReceiver)
|
||||
SamsungReceiver = remote_ns.class_('SamsungReceiver', RemoteReceiver)
|
||||
SonyReceiver = remote_ns.class_('SonyReceiver', RemoteReceiver)
|
||||
RC5Receiver = remote_ns.class_('RC5Receiver', RemoteReceiver)
|
||||
RCSwitchRawReceiver = remote_ns.class_('RCSwitchRawReceiver', RemoteReceiver)
|
||||
RCSwitchTypeAReceiver = remote_ns.class_('RCSwitchTypeAReceiver', RCSwitchRawReceiver)
|
||||
RCSwitchTypeBReceiver = remote_ns.class_('RCSwitchTypeBReceiver', RCSwitchRawReceiver)
|
||||
RCSwitchTypeCReceiver = remote_ns.class_('RCSwitchTypeCReceiver', RCSwitchRawReceiver)
|
||||
RCSwitchTypeDReceiver = remote_ns.class_('RCSwitchTypeDReceiver', RCSwitchRawReceiver)
|
||||
|
||||
|
||||
def validate_raw(value):
|
||||
if isinstance(value, dict):
|
||||
return cv.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(int32),
|
||||
vol.Required(CONF_DATA): [vol.Any(vol.Coerce(int), cv.time_period_microseconds)],
|
||||
})(value)
|
||||
return validate_raw({
|
||||
CONF_DATA: value
|
||||
})
|
||||
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(RemoteReceiver),
|
||||
vol.Optional(CONF_JVC): cv.Schema({
|
||||
vol.Required(CONF_DATA): cv.hex_uint32_t,
|
||||
}),
|
||||
vol.Optional(CONF_LG): cv.Schema({
|
||||
vol.Required(CONF_DATA): cv.hex_uint32_t,
|
||||
vol.Optional(CONF_NBITS, default=28): cv.one_of(28, 32, int=True),
|
||||
}),
|
||||
vol.Optional(CONF_NEC): cv.Schema({
|
||||
vol.Required(CONF_ADDRESS): cv.hex_uint16_t,
|
||||
vol.Required(CONF_COMMAND): cv.hex_uint16_t,
|
||||
}),
|
||||
vol.Optional(CONF_SAMSUNG): cv.Schema({
|
||||
vol.Required(CONF_DATA): cv.hex_uint32_t,
|
||||
}),
|
||||
vol.Optional(CONF_SONY): cv.Schema({
|
||||
vol.Required(CONF_DATA): cv.hex_uint32_t,
|
||||
vol.Optional(CONF_NBITS, default=12): cv.one_of(12, 15, 20, int=True),
|
||||
}),
|
||||
vol.Optional(CONF_PANASONIC): cv.Schema({
|
||||
vol.Required(CONF_ADDRESS): cv.hex_uint16_t,
|
||||
vol.Required(CONF_COMMAND): cv.hex_uint32_t,
|
||||
}),
|
||||
vol.Optional(CONF_RC5): cv.Schema({
|
||||
vol.Required(CONF_ADDRESS): vol.All(cv.hex_int, vol.Range(min=0, max=0x1F)),
|
||||
vol.Required(CONF_COMMAND): vol.All(cv.hex_int, vol.Range(min=0, max=0x3F)),
|
||||
}),
|
||||
vol.Optional(CONF_RAW): validate_raw,
|
||||
vol.Optional(CONF_RC_SWITCH_RAW): RC_SWITCH_RAW_SCHEMA,
|
||||
vol.Optional(CONF_RC_SWITCH_TYPE_A): RC_SWITCH_TYPE_A_SCHEMA,
|
||||
vol.Optional(CONF_RC_SWITCH_TYPE_B): RC_SWITCH_TYPE_B_SCHEMA,
|
||||
vol.Optional(CONF_RC_SWITCH_TYPE_C): RC_SWITCH_TYPE_C_SCHEMA,
|
||||
vol.Optional(CONF_RC_SWITCH_TYPE_D): RC_SWITCH_TYPE_D_SCHEMA,
|
||||
|
||||
cv.GenerateID(CONF_REMOTE_RECEIVER_ID): cv.use_variable_id(RemoteReceiverComponent),
|
||||
cv.GenerateID(CONF_RECEIVER_ID): cv.declare_variable_id(RemoteReceiver),
|
||||
}), cv.has_exactly_one_key(*REMOTE_KEYS))
|
||||
|
||||
|
||||
def receiver_base(full_config):
|
||||
name = full_config[CONF_NAME]
|
||||
key, config = next((k, v) for k, v in full_config.items() if k in REMOTE_KEYS)
|
||||
if key == CONF_JVC:
|
||||
return JVCReceiver.new(name, config[CONF_DATA])
|
||||
if key == CONF_LG:
|
||||
return LGReceiver.new(name, config[CONF_DATA], config[CONF_NBITS])
|
||||
if key == CONF_NEC:
|
||||
return NECReceiver.new(name, config[CONF_ADDRESS], config[CONF_COMMAND])
|
||||
if key == CONF_PANASONIC:
|
||||
return PanasonicReceiver.new(name, config[CONF_ADDRESS], config[CONF_COMMAND])
|
||||
if key == CONF_SAMSUNG:
|
||||
return SamsungReceiver.new(name, config[CONF_DATA])
|
||||
if key == CONF_SONY:
|
||||
return SonyReceiver.new(name, config[CONF_DATA], config[CONF_NBITS])
|
||||
if key == CONF_RC5:
|
||||
return RC5Receiver.new(name, config[CONF_ADDRESS], config[CONF_COMMAND])
|
||||
if key == CONF_RAW:
|
||||
arr = progmem_array(config[CONF_ID], config[CONF_DATA])
|
||||
return RawReceiver.new(name, arr, len(config[CONF_DATA]))
|
||||
if key == CONF_RC_SWITCH_RAW:
|
||||
return RCSwitchRawReceiver.new(name, build_rc_switch_protocol(config[CONF_PROTOCOL]),
|
||||
binary_code(config[CONF_CODE]), len(config[CONF_CODE]))
|
||||
if key == CONF_RC_SWITCH_TYPE_A:
|
||||
return RCSwitchTypeAReceiver.new(name, build_rc_switch_protocol(config[CONF_PROTOCOL]),
|
||||
binary_code(config[CONF_GROUP]),
|
||||
binary_code(config[CONF_DEVICE]),
|
||||
config[CONF_STATE])
|
||||
if key == CONF_RC_SWITCH_TYPE_B:
|
||||
return RCSwitchTypeBReceiver.new(name, build_rc_switch_protocol(config[CONF_PROTOCOL]),
|
||||
config[CONF_ADDRESS], config[CONF_CHANNEL],
|
||||
config[CONF_STATE])
|
||||
if key == CONF_RC_SWITCH_TYPE_C:
|
||||
return RCSwitchTypeCReceiver.new(name, build_rc_switch_protocol(config[CONF_PROTOCOL]),
|
||||
ord(config[CONF_FAMILY][0]) - ord('a'),
|
||||
config[CONF_GROUP], config[CONF_DEVICE],
|
||||
config[CONF_STATE])
|
||||
if key == CONF_RC_SWITCH_TYPE_D:
|
||||
return RCSwitchTypeDReceiver.new(name, build_rc_switch_protocol(config[CONF_PROTOCOL]),
|
||||
ord(config[CONF_GROUP][0]) - ord('a'),
|
||||
config[CONF_DEVICE], config[CONF_STATE])
|
||||
|
||||
raise NotImplementedError("Unknown receiver type {}".format(config))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
remote = yield get_variable(config[CONF_REMOTE_RECEIVER_ID])
|
||||
rhs = receiver_base(config)
|
||||
receiver = Pvariable(config[CONF_RECEIVER_ID], rhs)
|
||||
|
||||
binary_sensor.register_binary_sensor(remote.add_decoder(receiver), config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_REMOTE_RECEIVER'
|
|
@ -1,24 +0,0 @@
|
|||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_NAME
|
||||
from esphome.cpp_generator import Pvariable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import App, Component
|
||||
|
||||
StatusBinarySensor = binary_sensor.binary_sensor_ns.class_('StatusBinarySensor',
|
||||
binary_sensor.BinarySensor,
|
||||
Component)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(StatusBinarySensor),
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_status_binary_sensor(config[CONF_NAME])
|
||||
status = Pvariable(config[CONF_ID], rhs)
|
||||
binary_sensor.setup_binary_sensor(status, config)
|
||||
setup_component(status, config)
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_STATUS_BINARY_SENSOR'
|
|
@ -1,53 +0,0 @@
|
|||
import voluptuous as vol
|
||||
|
||||
from esphome.automation import ACTION_REGISTRY
|
||||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_NAME, CONF_STATE
|
||||
from esphome.cpp_generator import Pvariable, add, get_variable, process_lambda, templatable
|
||||
from esphome.cpp_helpers import setup_component
|
||||
from esphome.cpp_types import Action, App, Component, bool_, optional
|
||||
|
||||
TemplateBinarySensor = binary_sensor.binary_sensor_ns.class_('TemplateBinarySensor',
|
||||
binary_sensor.BinarySensor,
|
||||
Component)
|
||||
BinarySensorPublishAction = binary_sensor.binary_sensor_ns.class_('BinarySensorPublishAction',
|
||||
Action)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(TemplateBinarySensor),
|
||||
vol.Optional(CONF_LAMBDA): cv.lambda_,
|
||||
}).extend(cv.COMPONENT_SCHEMA.schema))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
rhs = App.make_template_binary_sensor(config[CONF_NAME])
|
||||
var = Pvariable(config[CONF_ID], rhs)
|
||||
binary_sensor.setup_binary_sensor(var, config)
|
||||
setup_component(var, config)
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
template_ = yield process_lambda(config[CONF_LAMBDA], [],
|
||||
return_type=optional.template(bool_))
|
||||
add(var.set_template(template_))
|
||||
|
||||
|
||||
BUILD_FLAGS = '-DUSE_TEMPLATE_BINARY_SENSOR'
|
||||
|
||||
CONF_BINARY_SENSOR_TEMPLATE_PUBLISH = 'binary_sensor.template.publish'
|
||||
BINARY_SENSOR_TEMPLATE_PUBLISH_ACTION_SCHEMA = cv.Schema({
|
||||
vol.Required(CONF_ID): cv.use_variable_id(binary_sensor.BinarySensor),
|
||||
vol.Required(CONF_STATE): cv.templatable(cv.boolean),
|
||||
})
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_BINARY_SENSOR_TEMPLATE_PUBLISH,
|
||||
BINARY_SENSOR_TEMPLATE_PUBLISH_ACTION_SCHEMA)
|
||||
def binary_sensor_template_publish_to_code(config, action_id, template_arg, args):
|
||||
var = yield get_variable(config[CONF_ID])
|
||||
rhs = var.make_binary_sensor_publish_action(template_arg)
|
||||
type = BinarySensorPublishAction.template(template_arg)
|
||||
action = Pvariable(action_id, rhs, type=type)
|
||||
template_ = yield templatable(config[CONF_STATE], args, bool_)
|
||||
add(action.set_state(template_))
|
||||
yield action
|
|
@ -1,23 +0,0 @@
|
|||
import voluptuous as vol
|
||||
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.components.ttp229_lsf import TTP229LSFComponent, CONF_TTP229_ID
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHANNEL, CONF_NAME
|
||||
from esphome.cpp_generator import get_variable
|
||||
|
||||
DEPENDENCIES = ['ttp229_lsf']
|
||||
TTP229Channel = binary_sensor.binary_sensor_ns.class_(
|
||||
'TTP229Channel', binary_sensor.BinarySensor)
|
||||
|
||||
PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(TTP229Channel),
|
||||
cv.GenerateID(CONF_TTP229_ID): cv.use_variable_id(TTP229LSFComponent),
|
||||
vol.Required(CONF_CHANNEL): vol.All(vol.Coerce(int), vol.Range(min=0, max=15))
|
||||
}))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
hub = yield get_variable(config[CONF_TTP229_ID])
|
||||
rhs = TTP229Channel.new(config[CONF_NAME], config[CONF_CHANNEL])
|
||||
binary_sensor.register_binary_sensor(hub.add_channel(rhs), config)
|
0
esphome/components/ble_presence/__init__.py
Normal file
0
esphome/components/ble_presence/__init__.py
Normal file
24
esphome/components/ble_presence/binary_sensor.py
Normal file
24
esphome/components/ble_presence/binary_sensor.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.components.esp32_ble_tracker import CONF_ESP32_BLE_ID, ESP_BLE_DEVICE_SCHEMA, \
|
||||
ESPBTDeviceListener
|
||||
from esphome.const import CONF_MAC_ADDRESS, CONF_NAME, CONF_ID
|
||||
|
||||
DEPENDENCIES = ['esp32_ble_tracker']
|
||||
|
||||
ble_presence_ns = cg.esphome_ns.namespace('ble_presence')
|
||||
BLEPresenceDevice = ble_presence_ns.class_('BLEPresenceDevice', binary_sensor.BinarySensor,
|
||||
cg.Component, ESPBTDeviceListener)
|
||||
|
||||
CONFIG_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(BLEPresenceDevice),
|
||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
}).extend(ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
hub = yield cg.get_variable(config[CONF_ESP32_BLE_ID])
|
||||
var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME], hub, config[CONF_MAC_ADDRESS].as_hex)
|
||||
yield cg.register_component(var, config)
|
||||
yield binary_sensor.register_binary_sensor(var, config)
|
16
esphome/components/ble_presence/ble_presence_device.cpp
Normal file
16
esphome/components/ble_presence/ble_presence_device.cpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#include "ble_presence_device.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_presence {
|
||||
|
||||
static const char *TAG = "something.something";
|
||||
|
||||
void BLEPresenceDevice::dump_config() { LOG_BINARY_SENSOR("", "BLE Presence", this); }
|
||||
|
||||
} // namespace ble_presence
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
44
esphome/components/ble_presence/ble_presence_device.h
Normal file
44
esphome/components/ble_presence/ble_presence_device.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_presence {
|
||||
|
||||
class BLEPresenceDevice : public binary_sensor::BinarySensor,
|
||||
public esp32_ble_tracker::ESPBTDeviceListener,
|
||||
public Component {
|
||||
public:
|
||||
BLEPresenceDevice(const std::string &name, esp32_ble_tracker::ESP32BLETracker *parent, uint64_t address)
|
||||
: binary_sensor::BinarySensor(name), esp32_ble_tracker::ESPBTDeviceListener(parent), address_(address) {}
|
||||
|
||||
void on_scan_end() override {
|
||||
if (!this->found_)
|
||||
this->publish_state(false);
|
||||
this->found_ = false;
|
||||
}
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
|
||||
if (device.address_uint64() == this->address_) {
|
||||
this->publish_state(true);
|
||||
this->found_ = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void setup() override { this->setup_ble(); }
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
protected:
|
||||
bool found_{false};
|
||||
uint64_t address_;
|
||||
};
|
||||
|
||||
} // namespace ble_presence
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
0
esphome/components/ble_rssi/__init__.py
Normal file
0
esphome/components/ble_rssi/__init__.py
Normal file
16
esphome/components/ble_rssi/ble_rssi_sensor.cpp
Normal file
16
esphome/components/ble_rssi/ble_rssi_sensor.cpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#include "ble_rssi_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_rssi {
|
||||
|
||||
static const char *TAG = "ble_rssi";
|
||||
|
||||
void BLERSSISensor::dump_config() { LOG_SENSOR("", "BLE RSSI Sensor", this); }
|
||||
|
||||
} // namespace ble_rssi
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
42
esphome/components/ble_rssi/ble_rssi_sensor.h
Normal file
42
esphome/components/ble_rssi/ble_rssi_sensor.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_rssi {
|
||||
|
||||
class BLERSSISensor : public sensor::Sensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||
public:
|
||||
BLERSSISensor(const std::string &name, esp32_ble_tracker::ESP32BLETracker *parent, uint64_t address)
|
||||
: sensor::Sensor(name), esp32_ble_tracker::ESPBTDeviceListener(parent), address_(address) {}
|
||||
|
||||
void on_scan_end() override {
|
||||
if (!this->found_)
|
||||
this->publish_state(NAN);
|
||||
this->found_ = false;
|
||||
}
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
|
||||
if (device.address_uint64() == this->address_) {
|
||||
this->publish_state(device.get_rssi());
|
||||
this->found_ = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void setup() override { this->setup_ble(); }
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
protected:
|
||||
bool found_{false};
|
||||
uint64_t address_;
|
||||
};
|
||||
|
||||
} // namespace ble_rssi
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
29
esphome/components/ble_rssi/sensor.py
Normal file
29
esphome/components/ble_rssi/sensor.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.components.esp32_ble_tracker import CONF_ESP32_BLE_ID, ESPBTDeviceListener, \
|
||||
ESP_BLE_DEVICE_SCHEMA
|
||||
from esphome.const import CONF_MAC_ADDRESS, CONF_NAME, CONF_ID, CONF_UNIT_OF_MEASUREMENT, \
|
||||
CONF_ICON, CONF_ACCURACY_DECIMALS, UNIT_DECIBEL, ICON_SIGNAL
|
||||
|
||||
DEPENDENCIES = ['esp32_ble_tracker']
|
||||
|
||||
ble_rssi_ns = cg.esphome_ns.namespace('ble_rssi')
|
||||
BLERSSISensor = ble_rssi_ns.class_('BLERSSISensor', sensor.Sensor, cg.Component,
|
||||
ESPBTDeviceListener)
|
||||
|
||||
CONFIG_SCHEMA = cv.nameable(sensor.SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_variable_id(BLERSSISensor),
|
||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
|
||||
cv.Optional(CONF_UNIT_OF_MEASUREMENT, default=UNIT_DECIBEL): sensor.unit_of_measurement,
|
||||
cv.Optional(CONF_ICON, default=ICON_SIGNAL): sensor.icon,
|
||||
cv.Optional(CONF_ACCURACY_DECIMALS, default=0): sensor.accuracy_decimals
|
||||
}).extend(ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
hub = yield cg.get_variable(config[CONF_ESP32_BLE_ID])
|
||||
var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME], hub, config[CONF_MAC_ADDRESS].as_hex)
|
||||
yield cg.register_component(var, config)
|
||||
yield sensor.register_sensor(var, config)
|
0
esphome/components/bme280/__init__.py
Normal file
0
esphome/components/bme280/__init__.py
Normal file
318
esphome/components/bme280/bme280.cpp
Normal file
318
esphome/components/bme280/bme280.cpp
Normal file
|
@ -0,0 +1,318 @@
|
|||
#include "bme280.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bme280 {
|
||||
|
||||
static const char *TAG = "bme280.sensor";
|
||||
|
||||
static const uint8_t BME280_REGISTER_DIG_T1 = 0x88;
|
||||
static const uint8_t BME280_REGISTER_DIG_T2 = 0x8A;
|
||||
static const uint8_t BME280_REGISTER_DIG_T3 = 0x8C;
|
||||
|
||||
static const uint8_t BME280_REGISTER_DIG_P1 = 0x8E;
|
||||
static const uint8_t BME280_REGISTER_DIG_P2 = 0x90;
|
||||
static const uint8_t BME280_REGISTER_DIG_P3 = 0x92;
|
||||
static const uint8_t BME280_REGISTER_DIG_P4 = 0x94;
|
||||
static const uint8_t BME280_REGISTER_DIG_P5 = 0x96;
|
||||
static const uint8_t BME280_REGISTER_DIG_P6 = 0x98;
|
||||
static const uint8_t BME280_REGISTER_DIG_P7 = 0x9A;
|
||||
static const uint8_t BME280_REGISTER_DIG_P8 = 0x9C;
|
||||
static const uint8_t BME280_REGISTER_DIG_P9 = 0x9E;
|
||||
|
||||
static const uint8_t BME280_REGISTER_DIG_H1 = 0xA1;
|
||||
static const uint8_t BME280_REGISTER_DIG_H2 = 0xE1;
|
||||
static const uint8_t BME280_REGISTER_DIG_H3 = 0xE3;
|
||||
static const uint8_t BME280_REGISTER_DIG_H4 = 0xE4;
|
||||
static const uint8_t BME280_REGISTER_DIG_H5 = 0xE5;
|
||||
static const uint8_t BME280_REGISTER_DIG_H6 = 0xE7;
|
||||
|
||||
static const uint8_t BME280_REGISTER_CHIPID = 0xD0;
|
||||
|
||||
static const uint8_t BME280_REGISTER_CONTROLHUMID = 0xF2;
|
||||
static const uint8_t BME280_REGISTER_STATUS = 0xF3;
|
||||
static const uint8_t BME280_REGISTER_CONTROL = 0xF4;
|
||||
static const uint8_t BME280_REGISTER_CONFIG = 0xF5;
|
||||
static const uint8_t BME280_REGISTER_PRESSUREDATA = 0xF7;
|
||||
static const uint8_t BME280_REGISTER_TEMPDATA = 0xFA;
|
||||
static const uint8_t BME280_REGISTER_HUMIDDATA = 0xFD;
|
||||
|
||||
static const uint8_t BME280_MODE_FORCED = 0b01;
|
||||
|
||||
inline uint16_t combine_bytes(uint8_t msb, uint8_t lsb) { return ((msb & 0xFF) << 8) | (lsb & 0xFF); }
|
||||
|
||||
static const char *oversampling_to_str(BME280Oversampling oversampling) {
|
||||
switch (oversampling) {
|
||||
case BME280_OVERSAMPLING_NONE:
|
||||
return "None";
|
||||
case BME280_OVERSAMPLING_1X:
|
||||
return "1x";
|
||||
case BME280_OVERSAMPLING_2X:
|
||||
return "2x";
|
||||
case BME280_OVERSAMPLING_4X:
|
||||
return "4x";
|
||||
case BME280_OVERSAMPLING_8X:
|
||||
return "8x";
|
||||
case BME280_OVERSAMPLING_16X:
|
||||
return "16x";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *iir_filter_to_str(BME280IIRFilter filter) {
|
||||
switch (filter) {
|
||||
case BME280_IIR_FILTER_OFF:
|
||||
return "OFF";
|
||||
case BME280_IIR_FILTER_2X:
|
||||
return "2x";
|
||||
case BME280_IIR_FILTER_4X:
|
||||
return "4x";
|
||||
case BME280_IIR_FILTER_8X:
|
||||
return "8x";
|
||||
case BME280_IIR_FILTER_16X:
|
||||
return "16x";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
void BME280Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up BME280...");
|
||||
uint8_t chip_id = 0;
|
||||
if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) {
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (chip_id != 0x60) {
|
||||
this->error_code_ = WRONG_CHIP_ID;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Read calibration
|
||||
this->calibration_.t1 = read_u16_le_(BME280_REGISTER_DIG_T1);
|
||||
this->calibration_.t2 = read_s16_le_(BME280_REGISTER_DIG_T2);
|
||||
this->calibration_.t3 = read_s16_le_(BME280_REGISTER_DIG_T3);
|
||||
|
||||
this->calibration_.p1 = read_u16_le_(BME280_REGISTER_DIG_P1);
|
||||
this->calibration_.p2 = read_s16_le_(BME280_REGISTER_DIG_P2);
|
||||
this->calibration_.p3 = read_s16_le_(BME280_REGISTER_DIG_P3);
|
||||
this->calibration_.p4 = read_s16_le_(BME280_REGISTER_DIG_P4);
|
||||
this->calibration_.p5 = read_s16_le_(BME280_REGISTER_DIG_P5);
|
||||
this->calibration_.p6 = read_s16_le_(BME280_REGISTER_DIG_P6);
|
||||
this->calibration_.p7 = read_s16_le_(BME280_REGISTER_DIG_P7);
|
||||
this->calibration_.p8 = read_s16_le_(BME280_REGISTER_DIG_P8);
|
||||
this->calibration_.p9 = read_s16_le_(BME280_REGISTER_DIG_P9);
|
||||
|
||||
this->calibration_.h1 = read_u8_(BME280_REGISTER_DIG_H1);
|
||||
this->calibration_.h2 = read_s16_le_(BME280_REGISTER_DIG_H2);
|
||||
this->calibration_.h3 = read_u8_(BME280_REGISTER_DIG_H3);
|
||||
this->calibration_.h4 = read_u8_(BME280_REGISTER_DIG_H4) << 4 | (read_u8_(BME280_REGISTER_DIG_H4 + 1) & 0x0F);
|
||||
this->calibration_.h5 = read_u8_(BME280_REGISTER_DIG_H5 + 1) << 4 | (read_u8_(BME280_REGISTER_DIG_H5) >> 4);
|
||||
this->calibration_.h6 = read_u8_(BME280_REGISTER_DIG_H6);
|
||||
|
||||
uint8_t humid_register = 0;
|
||||
if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_register)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
humid_register &= ~0b00000111;
|
||||
humid_register |= this->humidity_oversampling_ & 0b111;
|
||||
if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_register)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t config_register = 0;
|
||||
if (!this->read_byte(BME280_REGISTER_CONFIG, &config_register)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
config_register &= ~0b11111100;
|
||||
config_register |= 0b000 << 5; // 0.5 ms standby time
|
||||
config_register |= (this->iir_filter_ & 0b111) << 2;
|
||||
if (!this->write_byte(BME280_REGISTER_CONFIG, config_register)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
void BME280Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BME280:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
switch (this->error_code_) {
|
||||
case COMMUNICATION_FAILED:
|
||||
ESP_LOGE(TAG, "Communication with BME280 failed!");
|
||||
break;
|
||||
case WRONG_CHIP_ID:
|
||||
ESP_LOGE(TAG, "BMP280 has wrong chip ID! Is it a BMP280?");
|
||||
break;
|
||||
case NONE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " IIR Filter: %s", iir_filter_to_str(this->iir_filter_));
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Oversampling: %s", oversampling_to_str(this->temperature_oversampling_));
|
||||
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Oversampling: %s", oversampling_to_str(this->pressure_oversampling_));
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Oversampling: %s", oversampling_to_str(this->humidity_oversampling_));
|
||||
}
|
||||
float BME280Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
inline uint8_t oversampling_to_time(BME280Oversampling over_sampling) { return (1 << uint8_t(over_sampling)) >> 1; }
|
||||
|
||||
void BME280Component::update() {
|
||||
// Enable sensor
|
||||
ESP_LOGV(TAG, "Sending conversion request...");
|
||||
uint8_t meas_register = 0;
|
||||
meas_register |= (this->temperature_oversampling_ & 0b111) << 5;
|
||||
meas_register |= (this->pressure_oversampling_ & 0b111) << 2;
|
||||
meas_register |= 0b01; // Forced mode
|
||||
if (!this->write_byte(BME280_REGISTER_CONTROL, meas_register)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
float meas_time = 1;
|
||||
meas_time += 2.3f * oversampling_to_time(this->temperature_oversampling_);
|
||||
meas_time += 2.3f * oversampling_to_time(this->pressure_oversampling_) + 0.575f;
|
||||
meas_time += 2.3f * oversampling_to_time(this->humidity_oversampling_) + 0.575f;
|
||||
|
||||
this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
|
||||
int32_t t_fine = 0;
|
||||
float temperature = this->read_temperature_(&t_fine);
|
||||
if (isnan(temperature)) {
|
||||
ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values.");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
float pressure = this->read_pressure_(t_fine);
|
||||
float humidity = this->read_humidity_(t_fine);
|
||||
|
||||
ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity);
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
if (this->pressure_sensor_ != nullptr)
|
||||
this->pressure_sensor_->publish_state(pressure);
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
this->humidity_sensor_->publish_state(humidity);
|
||||
this->status_clear_warning();
|
||||
});
|
||||
}
|
||||
float BME280Component::read_temperature_(int32_t *t_fine) {
|
||||
uint8_t data[3];
|
||||
if (!this->read_bytes(BME280_REGISTER_TEMPDATA, data, 3))
|
||||
return NAN;
|
||||
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
|
||||
adc >>= 4;
|
||||
if (adc == 0x80000)
|
||||
// temperature was disabled
|
||||
return NAN;
|
||||
|
||||
const int32_t t1 = this->calibration_.t1;
|
||||
const int32_t t2 = this->calibration_.t2;
|
||||
const int32_t t3 = this->calibration_.t3;
|
||||
|
||||
int32_t var1 = (((adc >> 3) - (t1 << 1)) * t2) >> 11;
|
||||
int32_t var2 = (((((adc >> 4) - t1) * ((adc >> 4) - t1)) >> 12) * t3) >> 14;
|
||||
*t_fine = var1 + var2;
|
||||
|
||||
float temperature = (*t_fine * 5 + 128) >> 8;
|
||||
return temperature / 100.0f;
|
||||
}
|
||||
|
||||
float BME280Component::read_pressure_(int32_t t_fine) {
|
||||
uint8_t data[3];
|
||||
if (!this->read_bytes(BME280_REGISTER_PRESSUREDATA, data, 3))
|
||||
return NAN;
|
||||
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
|
||||
adc >>= 4;
|
||||
if (adc == 0x80000)
|
||||
// pressure was disabled
|
||||
return NAN;
|
||||
const int64_t p1 = this->calibration_.p1;
|
||||
const int64_t p2 = this->calibration_.p2;
|
||||
const int64_t p3 = this->calibration_.p3;
|
||||
const int64_t p4 = this->calibration_.p4;
|
||||
const int64_t p5 = this->calibration_.p5;
|
||||
const int64_t p6 = this->calibration_.p6;
|
||||
const int64_t p7 = this->calibration_.p7;
|
||||
const int64_t p8 = this->calibration_.p8;
|
||||
const int64_t p9 = this->calibration_.p9;
|
||||
|
||||
int64_t var1, var2, p;
|
||||
var1 = int64_t(t_fine) - 128000;
|
||||
var2 = var1 * var1 * p6;
|
||||
var2 = var2 + ((var1 * p5) << 17);
|
||||
var2 = var2 + (p4 << 35);
|
||||
var1 = ((var1 * var1 * p3) >> 8) + ((var1 * p2) << 12);
|
||||
var1 = ((int64_t(1) << 47) + var1) * p1 >> 33;
|
||||
|
||||
if (var1 == 0)
|
||||
return NAN;
|
||||
|
||||
p = 1048576 - adc;
|
||||
p = (((p << 31) - var2) * 3125) / var1;
|
||||
var1 = (p9 * (p >> 13) * (p >> 13)) >> 25;
|
||||
var2 = (p8 * p) >> 19;
|
||||
|
||||
p = ((p + var1 + var2) >> 8) + (p7 << 4);
|
||||
return (p / 256.0f) / 100.0f;
|
||||
}
|
||||
|
||||
float BME280Component::read_humidity_(int32_t t_fine) {
|
||||
uint16_t raw_adc;
|
||||
if (!this->read_byte_16(BME280_REGISTER_HUMIDDATA, &raw_adc) || raw_adc == 0x8000)
|
||||
return NAN;
|
||||
|
||||
int32_t adc = raw_adc;
|
||||
|
||||
const int32_t h1 = this->calibration_.h1;
|
||||
const int32_t h2 = this->calibration_.h2;
|
||||
const int32_t h3 = this->calibration_.h3;
|
||||
const int32_t h4 = this->calibration_.h4;
|
||||
const int32_t h5 = this->calibration_.h5;
|
||||
const int32_t h6 = this->calibration_.h6;
|
||||
|
||||
int32_t v_x1_u32r = t_fine - 76800;
|
||||
|
||||
v_x1_u32r = ((((adc << 14) - (h4 << 20) - (h5 * v_x1_u32r)) + 16384) >> 15) *
|
||||
(((((((v_x1_u32r * h6) >> 10) * (((v_x1_u32r * h3) >> 11) + 32768)) >> 10) + 2097152) * h2 + 8192) >> 14);
|
||||
|
||||
v_x1_u32r = v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * h1) >> 4);
|
||||
|
||||
v_x1_u32r = v_x1_u32r < 0 ? 0 : v_x1_u32r;
|
||||
v_x1_u32r = v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r;
|
||||
float h = v_x1_u32r >> 12;
|
||||
|
||||
return h / 1024.0f;
|
||||
}
|
||||
void BME280Component::set_temperature_oversampling(BME280Oversampling temperature_over_sampling) {
|
||||
this->temperature_oversampling_ = temperature_over_sampling;
|
||||
}
|
||||
void BME280Component::set_pressure_oversampling(BME280Oversampling pressure_over_sampling) {
|
||||
this->pressure_oversampling_ = pressure_over_sampling;
|
||||
}
|
||||
void BME280Component::set_humidity_oversampling(BME280Oversampling humidity_over_sampling) {
|
||||
this->humidity_oversampling_ = humidity_over_sampling;
|
||||
}
|
||||
void BME280Component::set_iir_filter(BME280IIRFilter iir_filter) { this->iir_filter_ = iir_filter; }
|
||||
uint8_t BME280Component::read_u8_(uint8_t a_register) {
|
||||
uint8_t data = 0;
|
||||
this->read_byte(a_register, &data);
|
||||
return data;
|
||||
}
|
||||
uint16_t BME280Component::read_u16_le_(uint8_t a_register) {
|
||||
uint16_t data = 0;
|
||||
this->read_byte_16(a_register, &data);
|
||||
return (data >> 8) | (data << 8);
|
||||
}
|
||||
int16_t BME280Component::read_s16_le_(uint8_t a_register) { return this->read_u16_le_(a_register); }
|
||||
|
||||
} // namespace bme280
|
||||
} // namespace esphome
|
112
esphome/components/bme280/bme280.h
Normal file
112
esphome/components/bme280/bme280.h
Normal file
|
@ -0,0 +1,112 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bme280 {
|
||||
|
||||
/// Internal struct storing the calibration values of an BME280.
|
||||
struct BME280CalibrationData {
|
||||
uint16_t t1; // 0x88 - 0x89
|
||||
int16_t t2; // 0x8A - 0x8B
|
||||
int16_t t3; // 0x8C - 0x8D
|
||||
|
||||
uint16_t p1; // 0x8E - 0x8F
|
||||
int16_t p2; // 0x90 - 0x91
|
||||
int16_t p3; // 0x92 - 0x93
|
||||
int16_t p4; // 0x94 - 0x95
|
||||
int16_t p5; // 0x96 - 0x97
|
||||
int16_t p6; // 0x98 - 0x99
|
||||
int16_t p7; // 0x9A - 0x9B
|
||||
int16_t p8; // 0x9C - 0x9D
|
||||
int16_t p9; // 0x9E - 0x9F
|
||||
|
||||
uint8_t h1; // 0xA1
|
||||
int16_t h2; // 0xE1 - 0xE2
|
||||
uint8_t h3; // 0xE3
|
||||
int16_t h4; // 0xE4 - 0xE5[3:0]
|
||||
int16_t h5; // 0xE5[7:4] - 0xE6
|
||||
int8_t h6; // 0xE7
|
||||
};
|
||||
|
||||
/** Enum listing all Oversampling values for the BME280.
|
||||
*
|
||||
* Oversampling basically means measuring a condition multiple times. Higher oversampling
|
||||
* values therefore increase the time required to read sensor values but increase accuracy.
|
||||
*/
|
||||
enum BME280Oversampling {
|
||||
BME280_OVERSAMPLING_NONE = 0b000,
|
||||
BME280_OVERSAMPLING_1X = 0b001,
|
||||
BME280_OVERSAMPLING_2X = 0b010,
|
||||
BME280_OVERSAMPLING_4X = 0b011,
|
||||
BME280_OVERSAMPLING_8X = 0b100,
|
||||
BME280_OVERSAMPLING_16X = 0b101,
|
||||
};
|
||||
|
||||
/** Enum listing all Infinite Impulse Filter values for the BME280.
|
||||
*
|
||||
* Higher values increase accuracy, but decrease response time.
|
||||
*/
|
||||
enum BME280IIRFilter {
|
||||
BME280_IIR_FILTER_OFF = 0b000,
|
||||
BME280_IIR_FILTER_2X = 0b001,
|
||||
BME280_IIR_FILTER_4X = 0b010,
|
||||
BME280_IIR_FILTER_8X = 0b011,
|
||||
BME280_IIR_FILTER_16X = 0b100,
|
||||
};
|
||||
|
||||
/// This class implements support for the BME280 Temperature+Pressure+Humidity i2c sensor.
|
||||
class BME280Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
BME280Component(uint32_t update_interval) : PollingComponent(update_interval) {}
|
||||
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
||||
|
||||
/// Set the oversampling value for the temperature sensor. Default is 16x.
|
||||
void set_temperature_oversampling(BME280Oversampling temperature_over_sampling);
|
||||
/// Set the oversampling value for the pressure sensor. Default is 16x.
|
||||
void set_pressure_oversampling(BME280Oversampling pressure_over_sampling);
|
||||
/// Set the oversampling value for the humidity sensor. Default is 16x.
|
||||
void set_humidity_oversampling(BME280Oversampling humidity_over_sampling);
|
||||
/// Set the IIR Filter used to increase accuracy, defaults to no IIR Filter.
|
||||
void set_iir_filter(BME280IIRFilter iir_filter);
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
/// Read the temperature value and store the calculated ambient temperature in t_fine.
|
||||
float read_temperature_(int32_t *t_fine);
|
||||
/// Read the pressure value in hPa using the provided t_fine value.
|
||||
float read_pressure_(int32_t t_fine);
|
||||
/// Read the humidity value in % using the provided t_fine value.
|
||||
float read_humidity_(int32_t t_fine);
|
||||
uint8_t read_u8_(uint8_t a_register);
|
||||
uint16_t read_u16_le_(uint8_t a_register);
|
||||
int16_t read_s16_le_(uint8_t a_register);
|
||||
|
||||
BME280CalibrationData calibration_;
|
||||
BME280Oversampling temperature_oversampling_{BME280_OVERSAMPLING_16X};
|
||||
BME280Oversampling pressure_oversampling_{BME280_OVERSAMPLING_16X};
|
||||
BME280Oversampling humidity_oversampling_{BME280_OVERSAMPLING_16X};
|
||||
BME280IIRFilter iir_filter_{BME280_IIR_FILTER_OFF};
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *pressure_sensor_;
|
||||
sensor::Sensor *humidity_sensor_;
|
||||
enum ErrorCode {
|
||||
NONE = 0,
|
||||
COMMUNICATION_FAILED,
|
||||
WRONG_CHIP_ID,
|
||||
} error_code_{NONE};
|
||||
};
|
||||
|
||||
} // namespace bme280
|
||||
} // namespace esphome
|
77
esphome/components/bme280/sensor.py
Normal file
77
esphome/components/bme280/sensor.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
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_IIR_FILTER, CONF_OVERSAMPLING, \
|
||||
CONF_PRESSURE, CONF_TEMPERATURE, CONF_UPDATE_INTERVAL, ICON_THERMOMETER, \
|
||||
UNIT_CELSIUS, UNIT_HECTOPASCAL, ICON_GAUGE, ICON_WATER_PERCENT, UNIT_PERCENT
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
bme280_ns = cg.esphome_ns.namespace('bme280')
|
||||
BME280Oversampling = bme280_ns.enum('BME280Oversampling')
|
||||
OVERSAMPLING_OPTIONS = {
|
||||
'NONE': BME280Oversampling.BME280_OVERSAMPLING_NONE,
|
||||
'1X': BME280Oversampling.BME280_OVERSAMPLING_1X,
|
||||
'2X': BME280Oversampling.BME280_OVERSAMPLING_2X,
|
||||
'4X': BME280Oversampling.BME280_OVERSAMPLING_4X,
|
||||
'8X': BME280Oversampling.BME280_OVERSAMPLING_8X,
|
||||
'16X': BME280Oversampling.BME280_OVERSAMPLING_16X,
|
||||
}
|
||||
|
||||
BME280IIRFilter = bme280_ns.enum('BME280IIRFilter')
|
||||
IIR_FILTER_OPTIONS = {
|
||||
'OFF': BME280IIRFilter.BME280_IIR_FILTER_OFF,
|
||||
'2X': BME280IIRFilter.BME280_IIR_FILTER_2X,
|
||||
'4X': BME280IIRFilter.BME280_IIR_FILTER_4X,
|
||||
'8X': BME280IIRFilter.BME280_IIR_FILTER_8X,
|
||||
'16X': BME280IIRFilter.BME280_IIR_FILTER_16X,
|
||||
}
|
||||
|
||||
BME280Component = bme280_ns.class_('BME280Component', cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(BME280Component),
|
||||
cv.Optional(CONF_TEMPERATURE): cv.nameable(
|
||||
sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({
|
||||
cv.Optional(CONF_OVERSAMPLING, default='16X'):
|
||||
cv.one_of(*OVERSAMPLING_OPTIONS, upper=True),
|
||||
})),
|
||||
cv.Optional(CONF_PRESSURE): cv.nameable(
|
||||
sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1).extend({
|
||||
cv.Optional(CONF_OVERSAMPLING, default='16X'):
|
||||
cv.one_of(*OVERSAMPLING_OPTIONS, upper=True),
|
||||
})),
|
||||
cv.Optional(CONF_HUMIDITY): cv.nameable(
|
||||
sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1).extend({
|
||||
cv.Optional(CONF_OVERSAMPLING, default='16X'):
|
||||
cv.one_of(*OVERSAMPLING_OPTIONS, upper=True),
|
||||
})),
|
||||
cv.Optional(CONF_IIR_FILTER, default='OFF'): cv.one_of(*IIR_FILTER_OPTIONS, upper=True),
|
||||
cv.Optional(CONF_UPDATE_INTERVAL, default='60s'): cv.update_interval,
|
||||
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x77))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID], config[CONF_UPDATE_INTERVAL])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
conf = config[CONF_TEMPERATURE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
cg.add(var.set_temperature_oversampling(OVERSAMPLING_OPTIONS[conf[CONF_OVERSAMPLING]]))
|
||||
|
||||
if CONF_PRESSURE in config:
|
||||
conf = config[CONF_PRESSURE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
cg.add(var.set_pressure_oversampling(OVERSAMPLING_OPTIONS[conf[CONF_OVERSAMPLING]]))
|
||||
|
||||
if CONF_HUMIDITY in config:
|
||||
conf = config[CONF_HUMIDITY]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
cg.add(var.set_humidity_oversampling(OVERSAMPLING_OPTIONS[conf[CONF_OVERSAMPLING]]))
|
||||
|
||||
cg.add(var.set_iir_filter(IIR_FILTER_OPTIONS[config[CONF_IIR_FILTER]]))
|
0
esphome/components/bme680/__init__.py
Normal file
0
esphome/components/bme680/__init__.py
Normal file
483
esphome/components/bme680/bme680.cpp
Normal file
483
esphome/components/bme680/bme680.cpp
Normal file
|
@ -0,0 +1,483 @@
|
|||
#include "bme680.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bme680 {
|
||||
|
||||
static const char *TAG = "bme680.sensor";
|
||||
|
||||
static const uint8_t BME680_REGISTER_COEFF1 = 0x89;
|
||||
static const uint8_t BME680_REGISTER_COEFF2 = 0xE1;
|
||||
|
||||
static const uint8_t BME680_REGISTER_CONFIG = 0x75;
|
||||
static const uint8_t BME680_REGISTER_CONTROL_MEAS = 0x74;
|
||||
static const uint8_t BME680_REGISTER_CONTROL_HUMIDITY = 0x72;
|
||||
static const uint8_t BME680_REGISTER_CONTROL_GAS1 = 0x71;
|
||||
static const uint8_t BME680_REGISTER_CONTROL_GAS0 = 0x70;
|
||||
static const uint8_t BME680_REGISTER_HEATER_HEAT0 = 0x5A;
|
||||
static const uint8_t BME680_REGISTER_HEATER_WAIT0 = 0x64;
|
||||
|
||||
static const uint8_t BME680_REGISTER_CHIPID = 0xD0;
|
||||
|
||||
static const uint8_t BME680_REGISTER_FIELD0 = 0x1D;
|
||||
|
||||
const float BME680_GAS_LOOKUP_TABLE_1[16] PROGMEM = {0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, -0.8,
|
||||
0.0, 0.0, -0.2, -0.5, 0.0, -1.0, 0.0, 0.0};
|
||||
|
||||
const float BME680_GAS_LOOKUP_TABLE_2[16] PROGMEM = {0.0, 0.0, 0.0, 0.0, 0.1, 0.7, 0.0, -0.8,
|
||||
-0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
|
||||
|
||||
static const char *oversampling_to_str(BME680Oversampling oversampling) {
|
||||
switch (oversampling) {
|
||||
case BME680_OVERSAMPLING_NONE:
|
||||
return "None";
|
||||
case BME680_OVERSAMPLING_1X:
|
||||
return "1x";
|
||||
case BME680_OVERSAMPLING_2X:
|
||||
return "2x";
|
||||
case BME680_OVERSAMPLING_4X:
|
||||
return "4x";
|
||||
case BME680_OVERSAMPLING_8X:
|
||||
return "8x";
|
||||
case BME680_OVERSAMPLING_16X:
|
||||
return "16x";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *iir_filter_to_str(BME680IIRFilter filter) {
|
||||
switch (filter) {
|
||||
case BME680_IIR_FILTER_OFF:
|
||||
return "OFF";
|
||||
case BME680_IIR_FILTER_1X:
|
||||
return "1x";
|
||||
case BME680_IIR_FILTER_3X:
|
||||
return "3x";
|
||||
case BME680_IIR_FILTER_7X:
|
||||
return "7x";
|
||||
case BME680_IIR_FILTER_15X:
|
||||
return "15x";
|
||||
case BME680_IIR_FILTER_31X:
|
||||
return "31x";
|
||||
case BME680_IIR_FILTER_63X:
|
||||
return "63x";
|
||||
case BME680_IIR_FILTER_127X:
|
||||
return "127x";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
void BME680Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up BME680...");
|
||||
uint8_t chip_id;
|
||||
if (!this->read_byte(BME680_REGISTER_CHIPID, &chip_id) || chip_id != 0x61) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Read calibration
|
||||
uint8_t cal1[25];
|
||||
if (!this->read_bytes(BME680_REGISTER_COEFF1, cal1, 25)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
uint8_t cal2[16];
|
||||
if (!this->read_bytes(BME680_REGISTER_COEFF2, cal2, 16)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->calibration_.t1 = cal2[9] << 8 | cal2[8];
|
||||
this->calibration_.t2 = cal1[2] << 8 | cal1[1];
|
||||
this->calibration_.t3 = cal1[3];
|
||||
|
||||
this->calibration_.h1 = cal2[2] << 4 | (cal2[1] & 0x0F);
|
||||
this->calibration_.h2 = cal2[0] << 4 | cal2[1];
|
||||
this->calibration_.h3 = cal2[3];
|
||||
this->calibration_.h4 = cal2[4];
|
||||
this->calibration_.h5 = cal2[5];
|
||||
this->calibration_.h6 = cal2[6];
|
||||
this->calibration_.h7 = cal2[7];
|
||||
|
||||
this->calibration_.p1 = cal1[6] << 8 | cal1[5];
|
||||
this->calibration_.p2 = cal1[8] << 8 | cal1[7];
|
||||
this->calibration_.p3 = cal1[9];
|
||||
this->calibration_.p4 = cal1[12] << 8 | cal1[11];
|
||||
this->calibration_.p5 = cal1[14] << 8 | cal1[13];
|
||||
this->calibration_.p6 = cal1[16];
|
||||
this->calibration_.p7 = cal1[15];
|
||||
this->calibration_.p8 = cal1[20] << 8 | cal1[19];
|
||||
this->calibration_.p9 = cal1[22] << 8 | cal1[21];
|
||||
this->calibration_.p10 = cal1[23];
|
||||
|
||||
this->calibration_.gh1 = cal2[14];
|
||||
this->calibration_.gh2 = cal2[12] << 8 | cal2[13];
|
||||
this->calibration_.gh3 = cal2[15];
|
||||
|
||||
if (!this->read_byte(0x02, &this->calibration_.res_heat_range)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (!this->read_byte(0x00, &this->calibration_.res_heat_val)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (!this->read_byte(0x04, &this->calibration_.range_sw_err)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->calibration_.ambient_temperature = 25; // prime ambient temperature
|
||||
|
||||
// Config register
|
||||
uint8_t config_register;
|
||||
if (!this->read_byte(BME680_REGISTER_CONFIG, &config_register)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
config_register &= ~0b00011100;
|
||||
config_register |= (this->iir_filter_ & 0b111) << 2;
|
||||
if (!this->write_byte(BME680_REGISTER_CONFIG, config_register)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Humidity control register
|
||||
uint8_t hum_control;
|
||||
if (!this->read_byte(BME680_REGISTER_CONTROL_HUMIDITY, &hum_control)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
hum_control &= ~0b00000111;
|
||||
hum_control |= this->humidity_oversampling_ & 0b111;
|
||||
if (!this->write_byte(BME680_REGISTER_CONTROL_HUMIDITY, hum_control)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Gas 1 control register
|
||||
uint8_t gas1_control;
|
||||
if (!this->read_byte(BME680_REGISTER_CONTROL_GAS1, &gas1_control)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
gas1_control &= ~0b00011111;
|
||||
gas1_control |= 1 << 4;
|
||||
gas1_control |= 0; // profile 0
|
||||
if (!this->write_byte(BME680_REGISTER_CONTROL_GAS1, gas1_control)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
const bool heat_off = this->heater_temperature_ == 0 || this->heater_duration_ == 0;
|
||||
|
||||
// Gas 0 control register
|
||||
uint8_t gas0_control;
|
||||
if (!this->read_byte(BME680_REGISTER_CONTROL_GAS0, &gas0_control)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
gas0_control &= ~0b00001000;
|
||||
gas0_control |= heat_off ? 0b100 : 0b000;
|
||||
if (!this->write_byte(BME680_REGISTER_CONTROL_GAS0, gas0_control)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!heat_off) {
|
||||
// Gas Heater Temperature
|
||||
uint8_t temperature = this->calc_heater_resistance_(this->heater_temperature_);
|
||||
if (!this->write_byte(BME680_REGISTER_HEATER_HEAT0, temperature)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Gas Heater Duration
|
||||
uint8_t duration = this->calc_heater_duration_(this->heater_duration_);
|
||||
|
||||
if (!this->write_byte(BME680_REGISTER_HEATER_WAIT0, duration)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BME680Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BME680:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with BME680 failed!");
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " IIR Filter: %s", iir_filter_to_str(this->iir_filter_));
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Oversampling: %s", oversampling_to_str(this->temperature_oversampling_));
|
||||
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Oversampling: %s", oversampling_to_str(this->pressure_oversampling_));
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Oversampling: %s", oversampling_to_str(this->humidity_oversampling_));
|
||||
LOG_SENSOR(" ", "Gas Resistance", this->gas_resistance_sensor_);
|
||||
if (this->heater_duration_ == 0 || this->heater_temperature_ == 0) {
|
||||
ESP_LOGCONFIG(TAG, " Heater OFF");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Heater temperature=%u°C duration=%ums", this->heater_temperature_, this->heater_duration_);
|
||||
}
|
||||
}
|
||||
|
||||
float BME680Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void BME680Component::update() {
|
||||
uint8_t meas_control = 0; // No need to fetch, we're setting all fields
|
||||
meas_control |= (this->temperature_oversampling_ & 0b111) << 5;
|
||||
meas_control |= (this->pressure_oversampling_ & 0b111) << 5;
|
||||
meas_control |= 0b01; // forced mode
|
||||
if (!this->write_byte(BME680_REGISTER_CONTROL_MEAS, meas_control)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
this->set_timeout("data", this->calc_meas_duration_(), [this]() { this->read_data_(); });
|
||||
}
|
||||
|
||||
uint8_t BME680Component::calc_heater_resistance_(uint16_t temperature) {
|
||||
if (temperature < 200)
|
||||
temperature = 200;
|
||||
if (temperature > 400)
|
||||
temperature = 400;
|
||||
|
||||
const uint8_t ambient_temperature = this->calibration_.ambient_temperature;
|
||||
const int8_t gh1 = this->calibration_.gh1;
|
||||
const int16_t gh2 = this->calibration_.gh2;
|
||||
const int8_t gh3 = this->calibration_.gh3;
|
||||
const uint8_t res_heat_range = this->calibration_.res_heat_range;
|
||||
const uint8_t res_heat_val = this->calibration_.res_heat_val;
|
||||
|
||||
uint8_t heatr_res;
|
||||
int32_t var1;
|
||||
int32_t var2;
|
||||
int32_t var3;
|
||||
int32_t var4;
|
||||
int32_t var5;
|
||||
int32_t heatr_res_x100;
|
||||
|
||||
var1 = (((int32_t) ambient_temperature * gh3) / 1000) * 256;
|
||||
var2 = (gh1 + 784) * (((((gh2 + 154009) * temperature * 5) / 100) + 3276800) / 10);
|
||||
var3 = var1 + (var2 / 2);
|
||||
var4 = (var3 / (res_heat_range + 4));
|
||||
var5 = (131 * res_heat_val) + 65536;
|
||||
heatr_res_x100 = (int32_t)(((var4 / var5) - 250) * 34);
|
||||
heatr_res = (uint8_t)((heatr_res_x100 + 50) / 100);
|
||||
|
||||
return heatr_res;
|
||||
}
|
||||
uint8_t BME680Component::calc_heater_duration_(uint16_t duration) {
|
||||
uint8_t factor = 0;
|
||||
uint8_t duration_value;
|
||||
|
||||
if (duration >= 0xfc0) {
|
||||
duration_value = 0xff;
|
||||
} else {
|
||||
while (duration > 0x3F) {
|
||||
duration /= 4;
|
||||
factor += 1;
|
||||
}
|
||||
duration_value = duration + (factor * 64);
|
||||
}
|
||||
|
||||
return duration_value;
|
||||
}
|
||||
void BME680Component::read_data_() {
|
||||
uint8_t data[15];
|
||||
if (!this->read_bytes(BME680_REGISTER_FIELD0, data, 15)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t raw_temperature = (uint32_t(data[5]) << 12) | (uint32_t(data[6]) << 4) | (uint32_t(data[7]) >> 4);
|
||||
uint32_t raw_pressure = (uint32_t(data[2]) << 12) | (uint32_t(data[3]) << 4) | (uint32_t(data[4]) >> 4);
|
||||
uint32_t raw_humidity = (uint32_t(data[8]) << 8) | uint32_t(data[9]);
|
||||
uint16_t raw_gas = (uint16_t(data[13]) << 2) | (uint16_t(14) >> 6);
|
||||
uint8_t gas_range = data[14] & 0x0F;
|
||||
|
||||
float temperature = this->calc_temperature_(raw_temperature);
|
||||
float pressure = this->calc_pressure_(raw_pressure);
|
||||
float humidity = this->calc_humidity_(raw_humidity);
|
||||
float gas_resistance = NAN;
|
||||
if (data[14] & 0x20) {
|
||||
gas_resistance = this->calc_gas_resistance_(raw_gas, gas_range);
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%% gas_resistance=%.1fΩ", temperature, pressure,
|
||||
humidity, gas_resistance);
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
if (this->pressure_sensor_ != nullptr)
|
||||
this->pressure_sensor_->publish_state(pressure);
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
this->humidity_sensor_->publish_state(humidity);
|
||||
if (this->gas_resistance_sensor_ != nullptr)
|
||||
this->gas_resistance_sensor_->publish_state(gas_resistance);
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
float BME680Component::calc_temperature_(uint32_t raw_temperature) {
|
||||
float var1 = 0;
|
||||
float var2 = 0;
|
||||
float var3 = 0;
|
||||
float calc_temp = 0;
|
||||
float temp_adc = raw_temperature;
|
||||
|
||||
const float t1 = this->calibration_.t1;
|
||||
const float t2 = this->calibration_.t2;
|
||||
const float t3 = this->calibration_.t3;
|
||||
|
||||
/* calculate var1 data */
|
||||
var1 = ((temp_adc / 16384.0f) - (t1 / 1024.0f)) * t2;
|
||||
|
||||
/* calculate var2 data */
|
||||
var3 = (temp_adc / 131072.0f) - (t1 / 8192.0f);
|
||||
var2 = var3 * var3 * t3 * 16.0f;
|
||||
|
||||
/* t_fine value*/
|
||||
this->calibration_.tfine = (var1 + var2);
|
||||
|
||||
/* compensated temperature data*/
|
||||
calc_temp = ((this->calibration_.tfine) / 5120.0f);
|
||||
|
||||
return calc_temp;
|
||||
}
|
||||
float BME680Component::calc_pressure_(uint32_t raw_pressure) {
|
||||
const float tfine = this->calibration_.tfine;
|
||||
const float p1 = this->calibration_.p1;
|
||||
const float p2 = this->calibration_.p2;
|
||||
const float p3 = this->calibration_.p3;
|
||||
const float p4 = this->calibration_.p4;
|
||||
const float p5 = this->calibration_.p5;
|
||||
const float p6 = this->calibration_.p6;
|
||||
const float p7 = this->calibration_.p7;
|
||||
const float p8 = this->calibration_.p8;
|
||||
const float p9 = this->calibration_.p9;
|
||||
const float p10 = this->calibration_.p10;
|
||||
|
||||
float var1 = 0;
|
||||
float var2 = 0;
|
||||
float var3 = 0;
|
||||
float var4 = 0;
|
||||
float calc_pres = 0;
|
||||
|
||||
var1 = (tfine / 2.0f) - 64000.0f;
|
||||
var2 = var1 * var1 * (p6 / 131072.0f);
|
||||
var2 = var2 + var1 * p5 * 2.0f;
|
||||
var2 = (var2 / 4.0f) + (p4 * 65536.0f);
|
||||
var1 = (((p3 * var1 * var1) / 16384.0f) + (p2 * var1)) / 524288.0f;
|
||||
var1 = (1.0f + (var1 / 32768.0f)) * p1;
|
||||
calc_pres = 1048576.0f - float(raw_pressure);
|
||||
|
||||
/* Avoid exception caused by division by zero */
|
||||
if (int(var1) != 0) {
|
||||
calc_pres = ((calc_pres - (var2 / 4096.0f)) * 6250.0f) / var1;
|
||||
var1 = (p9 * calc_pres * calc_pres) / 2147483648.0f;
|
||||
var2 = calc_pres * (p8 / 32768.0f);
|
||||
var4 = calc_pres / 256.0f;
|
||||
var3 = var4 * var4 * var4 * (p10 / 131072.0f);
|
||||
calc_pres = calc_pres + (var1 + var2 + var3 + (p7 * 128.0f)) / 16.0f;
|
||||
} else {
|
||||
calc_pres = 0;
|
||||
}
|
||||
|
||||
return calc_pres / 100.0f;
|
||||
}
|
||||
|
||||
float BME680Component::calc_humidity_(uint16_t raw_humidity) {
|
||||
const float tfine = this->calibration_.tfine;
|
||||
const float h1 = this->calibration_.h1;
|
||||
const float h2 = this->calibration_.h2;
|
||||
const float h3 = this->calibration_.h3;
|
||||
const float h4 = this->calibration_.h4;
|
||||
const float h5 = this->calibration_.h5;
|
||||
const float h6 = this->calibration_.h6;
|
||||
const float h7 = this->calibration_.h7;
|
||||
|
||||
float calc_hum = 0;
|
||||
float var1 = 0;
|
||||
float var2 = 0;
|
||||
float var3 = 0;
|
||||
float var4 = 0;
|
||||
float temp_comp;
|
||||
|
||||
/* compensated temperature data*/
|
||||
temp_comp = tfine / 5120.0f;
|
||||
|
||||
var1 = float(raw_humidity) - (h1 * 16.0f + ((h3 / 2.0f) * temp_comp));
|
||||
var2 = var1 *
|
||||
(((h2 / 262144.0f) * (1.0f + ((h4 / 16384.0f) * temp_comp) + ((h5 / 1048576.0f) * temp_comp * temp_comp))));
|
||||
var3 = h6 / 16384.0f;
|
||||
var4 = h7 / 2097152.0f;
|
||||
|
||||
calc_hum = var2 + (var3 + var4 * temp_comp) * var2 * var2;
|
||||
|
||||
if (calc_hum > 100.0f)
|
||||
calc_hum = 100.0f;
|
||||
else if (calc_hum < 0.0f)
|
||||
calc_hum = 0.0f;
|
||||
|
||||
return calc_hum;
|
||||
}
|
||||
uint32_t BME680Component::calc_gas_resistance_(uint16_t raw_gas, uint8_t range) {
|
||||
float calc_gas_res;
|
||||
float var1 = 0;
|
||||
float var2 = 0;
|
||||
float var3 = 0;
|
||||
const float range_sw_err = this->calibration_.range_sw_err;
|
||||
|
||||
var1 = 1340.0f + (5.0f * range_sw_err);
|
||||
var2 = var1 * (1.0f + BME680_GAS_LOOKUP_TABLE_1[range] / 100.0f);
|
||||
var3 = 1.0f + (BME680_GAS_LOOKUP_TABLE_2[range] / 100.0f);
|
||||
|
||||
calc_gas_res = 1.0f / (var3 * 0.000000125f * float(1 << range) * (((float(raw_gas) - 512.0f) / var2) + 1.0f));
|
||||
|
||||
return static_cast<uint32_t>(calc_gas_res);
|
||||
}
|
||||
uint32_t BME680Component::calc_meas_duration_() {
|
||||
uint32_t tph_dur; // Calculate in us
|
||||
uint32_t meas_cycles;
|
||||
const uint8_t os_to_meas_cycles[6] = {0, 1, 2, 4, 8, 16};
|
||||
|
||||
meas_cycles = os_to_meas_cycles[this->temperature_oversampling_];
|
||||
meas_cycles += os_to_meas_cycles[this->pressure_oversampling_];
|
||||
meas_cycles += os_to_meas_cycles[this->humidity_oversampling_];
|
||||
|
||||
/* TPH measurement duration */
|
||||
tph_dur = meas_cycles * 1963u;
|
||||
tph_dur += 477 * 4; // TPH switching duration
|
||||
tph_dur += 477 * 5; // Gas measurement duration
|
||||
tph_dur += 500; // Get it to the closest whole number.
|
||||
tph_dur /= 1000; // Convert to ms
|
||||
|
||||
tph_dur += 1; // Wake up duration of 1ms
|
||||
|
||||
/* The remaining time should be used for heating */
|
||||
tph_dur += this->heater_duration_;
|
||||
|
||||
return tph_dur;
|
||||
}
|
||||
void BME680Component::set_temperature_oversampling(BME680Oversampling temperature_oversampling) {
|
||||
this->temperature_oversampling_ = temperature_oversampling;
|
||||
}
|
||||
void BME680Component::set_pressure_oversampling(BME680Oversampling pressure_oversampling) {
|
||||
this->pressure_oversampling_ = pressure_oversampling;
|
||||
}
|
||||
void BME680Component::set_humidity_oversampling(BME680Oversampling humidity_oversampling) {
|
||||
this->humidity_oversampling_ = humidity_oversampling;
|
||||
}
|
||||
void BME680Component::set_iir_filter(BME680IIRFilter iir_filter) { this->iir_filter_ = iir_filter; }
|
||||
void BME680Component::set_heater(uint16_t heater_temperature, uint16_t heater_duration) {
|
||||
this->heater_temperature_ = heater_temperature;
|
||||
this->heater_duration_ = heater_duration;
|
||||
}
|
||||
|
||||
} // namespace bme680
|
||||
} // namespace esphome
|
141
esphome/components/bme680/bme680.h
Normal file
141
esphome/components/bme680/bme680.h
Normal file
|
@ -0,0 +1,141 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bme680 {
|
||||
|
||||
/// Enum listing all IIR Filter options for the BME680.
|
||||
enum BME680IIRFilter {
|
||||
BME680_IIR_FILTER_OFF = 0b000,
|
||||
BME680_IIR_FILTER_1X = 0b001,
|
||||
BME680_IIR_FILTER_3X = 0b010,
|
||||
BME680_IIR_FILTER_7X = 0b011,
|
||||
BME680_IIR_FILTER_15X = 0b100,
|
||||
BME680_IIR_FILTER_31X = 0b101,
|
||||
BME680_IIR_FILTER_63X = 0b110,
|
||||
BME680_IIR_FILTER_127X = 0b111,
|
||||
};
|
||||
|
||||
/// Enum listing all oversampling options for the BME680.
|
||||
enum BME680Oversampling {
|
||||
BME680_OVERSAMPLING_NONE = 0b000,
|
||||
BME680_OVERSAMPLING_1X = 0b001,
|
||||
BME680_OVERSAMPLING_2X = 0b010,
|
||||
BME680_OVERSAMPLING_4X = 0b011,
|
||||
BME680_OVERSAMPLING_8X = 0b100,
|
||||
BME680_OVERSAMPLING_16X = 0b101,
|
||||
};
|
||||
|
||||
/// Struct for storing calibration data for the BME680.
|
||||
struct BME680CalibrationData {
|
||||
uint16_t t1;
|
||||
uint16_t t2;
|
||||
uint8_t t3;
|
||||
|
||||
uint16_t p1;
|
||||
int16_t p2;
|
||||
int8_t p3;
|
||||
int16_t p4;
|
||||
int16_t p5;
|
||||
int8_t p6;
|
||||
int8_t p7;
|
||||
int16_t p8;
|
||||
int16_t p9;
|
||||
int8_t p10;
|
||||
|
||||
uint16_t h1;
|
||||
uint16_t h2;
|
||||
int8_t h3;
|
||||
int8_t h4;
|
||||
int8_t h5;
|
||||
uint8_t h6;
|
||||
int8_t h7;
|
||||
|
||||
int8_t gh1;
|
||||
int16_t gh2;
|
||||
int8_t gh3;
|
||||
|
||||
uint8_t res_heat_range;
|
||||
uint8_t res_heat_val;
|
||||
uint8_t range_sw_err;
|
||||
|
||||
float tfine;
|
||||
uint8_t ambient_temperature;
|
||||
};
|
||||
|
||||
class BME680Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
BME680Component(uint32_t update_interval) : PollingComponent(update_interval) {}
|
||||
|
||||
/// Set the temperature oversampling value. Defaults to 16X.
|
||||
void set_temperature_oversampling(BME680Oversampling temperature_oversampling);
|
||||
/// Set the pressure oversampling value. Defaults to 16X.
|
||||
void set_pressure_oversampling(BME680Oversampling pressure_oversampling);
|
||||
/// Set the humidity oversampling value. Defaults to 16X.
|
||||
void set_humidity_oversampling(BME680Oversampling humidity_oversampling);
|
||||
/// Set the IIR Filter value. Defaults to no IIR Filter.
|
||||
void set_iir_filter(BME680IIRFilter iir_filter);
|
||||
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
||||
void set_gas_resistance_sensor(sensor::Sensor *gas_resistance_sensor) {
|
||||
gas_resistance_sensor_ = gas_resistance_sensor;
|
||||
}
|
||||
|
||||
/** Set how the internal heater should operate.
|
||||
*
|
||||
* By default, the heater is off. If you want to to have more reliable
|
||||
* humidity and Gas Resistance values, you can however setup the heater
|
||||
* with this method.
|
||||
*
|
||||
* @param heater_temperature The temperature of the heater in °C.
|
||||
* @param heater_duration The duration in ms that the heater should turn on for when measuring.
|
||||
*/
|
||||
void set_heater(uint16_t heater_temperature, uint16_t heater_duration);
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
/// Calculate the heater resistance value to send to the BME680 register.
|
||||
uint8_t calc_heater_resistance_(uint16_t temperature);
|
||||
/// Calculate the heater duration value to send to the BME680 register.
|
||||
uint8_t calc_heater_duration_(uint16_t duration);
|
||||
/// Read data from the BME680 and publish results.
|
||||
void read_data_();
|
||||
|
||||
/// Calculate the temperature in °C using the provided raw ADC value.
|
||||
float calc_temperature_(uint32_t raw_temperature);
|
||||
/// Calculate the pressure in hPa using the provided raw ADC value.
|
||||
float calc_pressure_(uint32_t raw_pressure);
|
||||
/// Calculate the relative humidity in % using the provided raw ADC value.
|
||||
float calc_humidity_(uint16_t raw_humidity);
|
||||
/// Calculate the gas resistance in Ω using the provided raw ADC value.
|
||||
uint32_t calc_gas_resistance_(uint16_t raw_gas, uint8_t range);
|
||||
/// Calculate how long the sensor will take until we can retrieve data.
|
||||
uint32_t calc_meas_duration_();
|
||||
|
||||
BME680CalibrationData calibration_;
|
||||
BME680Oversampling temperature_oversampling_{BME680_OVERSAMPLING_16X};
|
||||
BME680Oversampling pressure_oversampling_{BME680_OVERSAMPLING_16X};
|
||||
BME680Oversampling humidity_oversampling_{BME680_OVERSAMPLING_16X};
|
||||
BME680IIRFilter iir_filter_{BME680_IIR_FILTER_OFF};
|
||||
uint16_t heater_temperature_{320};
|
||||
uint16_t heater_duration_{150};
|
||||
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *pressure_sensor_;
|
||||
sensor::Sensor *humidity_sensor_;
|
||||
sensor::Sensor *gas_resistance_sensor_;
|
||||
};
|
||||
|
||||
} // namespace bme680
|
||||
} // namespace esphome
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue