mirror of
https://github.com/esphome/esphome.git
synced 2026-05-09 21:28:40 +08: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:
+137
@@ -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
@@ -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: ''
|
||||||
+37
-43
@@ -25,12 +25,6 @@ wheels/
|
|||||||
*.egg
|
*.egg
|
||||||
MANIFEST
|
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
|
# Installer logs
|
||||||
pip-log.txt
|
pip-log.txt
|
||||||
pip-delete-this-directory.txt
|
pip-delete-this-directory.txt
|
||||||
@@ -51,36 +45,9 @@ coverage.xml
|
|||||||
*.mo
|
*.mo
|
||||||
*.pot
|
*.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
|
# pyenv
|
||||||
.python-version
|
.python-version
|
||||||
|
|
||||||
# celery beat schedule file
|
|
||||||
celerybeat-schedule
|
|
||||||
|
|
||||||
# SageMath parsed files
|
|
||||||
*.sage.py
|
|
||||||
|
|
||||||
# Environments
|
# Environments
|
||||||
.env
|
.env
|
||||||
.venv
|
.venv
|
||||||
@@ -90,19 +57,46 @@ ENV/
|
|||||||
env.bak/
|
env.bak/
|
||||||
venv.bak/
|
venv.bak/
|
||||||
|
|
||||||
# Spyder project settings
|
|
||||||
.spyderproject
|
|
||||||
.spyproject
|
|
||||||
|
|
||||||
# Rope project settings
|
|
||||||
.ropeproject
|
|
||||||
|
|
||||||
# mkdocs documentation
|
|
||||||
/site
|
|
||||||
|
|
||||||
# mypy
|
# mypy
|
||||||
.mypy_cache/
|
.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/
|
config/
|
||||||
tests/build/
|
tests/build/
|
||||||
tests/.esphome/
|
tests/.esphome/
|
||||||
|
/.temp-clang-tidy.cpp
|
||||||
|
|||||||
+28
-10
@@ -1,9 +1,10 @@
|
|||||||
sudo: false
|
sudo: false
|
||||||
language: python
|
language: python
|
||||||
|
install: script/setup
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
- "~/.platformio"
|
- "~/.platformio"
|
||||||
|
- "$TRAVIS_BUILD_DIR/.piolibdeps"
|
||||||
- "$TRAVIS_BUILD_DIR/tests/build/test1/.piolibdeps"
|
- "$TRAVIS_BUILD_DIR/tests/build/test1/.piolibdeps"
|
||||||
- "$TRAVIS_BUILD_DIR/tests/build/test2/.piolibdeps"
|
- "$TRAVIS_BUILD_DIR/tests/build/test2/.piolibdeps"
|
||||||
- "$TRAVIS_BUILD_DIR/tests/build/test3/.piolibdeps"
|
- "$TRAVIS_BUILD_DIR/tests/build/test3/.piolibdeps"
|
||||||
@@ -13,26 +14,43 @@ matrix:
|
|||||||
include:
|
include:
|
||||||
- python: "2.7"
|
- python: "2.7"
|
||||||
env: TARGET=Lint2.7
|
env: TARGET=Lint2.7
|
||||||
install: pip install -e . && pip install flake8==3.6.0 pylint==1.9.4 pillow
|
|
||||||
script:
|
script:
|
||||||
|
- script/ci-custom.py
|
||||||
- flake8 esphome
|
- flake8 esphome
|
||||||
- pylint esphome
|
- pylint esphome
|
||||||
- python: "3.5.3"
|
- python: "3.5.3"
|
||||||
env: TARGET=Lint3.5
|
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:
|
||||||
|
- script/ci-custom.py
|
||||||
- flake8 esphome
|
- flake8 esphome
|
||||||
- pylint esphome
|
- pylint esphome
|
||||||
- python: "2.7"
|
- python: "2.7"
|
||||||
env: TARGET=Test2.7
|
env: TARGET=Test2.7
|
||||||
install: pip install -e . && pip install flake8==3.6.0 pylint==1.9.4 pillow
|
|
||||||
script:
|
script:
|
||||||
- esphome tests/test1.yaml compile
|
- esphome tests/test1.yaml compile
|
||||||
- esphome tests/test2.yaml compile
|
- esphome tests/test2.yaml compile
|
||||||
- esphome tests/test3.yaml compile
|
- esphome tests/test3.yaml compile
|
||||||
#- python: "3.5.3"
|
- env: TARGET=Cpp-Lint
|
||||||
# env: TARGET=Test3.5
|
dist: trusty
|
||||||
# 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
|
sudo: required
|
||||||
# script:
|
addons:
|
||||||
# - esphome tests/test1.yaml compile
|
apt:
|
||||||
# - esphome tests/test2.yaml compile
|
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
|
||||||
|
|||||||
+2
-3
@@ -1,6 +1,5 @@
|
|||||||
include LICENSE
|
include LICENSE
|
||||||
include README.md
|
include README.md
|
||||||
include esphome/dashboard/templates/*.html
|
include esphome/dashboard/templates/*.html
|
||||||
include esphome/dashboard/static/*.js
|
recursive-include esphome/dashboard/static *.ico *.js *.css
|
||||||
include esphome/dashboard/static/*.css
|
recursive-include esphome *.cpp *.h *.tcc
|
||||||
include esphome/dashboard/static/*.ico
|
|
||||||
|
|||||||
+17
-70
@@ -1,28 +1,22 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
from datetime import datetime
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import random
|
|
||||||
import sys
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from esphome import const, core_config, writer, yaml_util
|
from esphome import const, writer, yaml_util
|
||||||
from esphome.config import get_component, iter_components, read_config, strip_default_ids
|
from esphome.config import iter_components, read_config, strip_default_ids
|
||||||
from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_ESPHOME, CONF_LOGGER, \
|
from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \
|
||||||
CONF_USE_CUSTOM_CODE
|
CONF_PASSWORD, CONF_PORT
|
||||||
from esphome.core import CORE, EsphomeError
|
from esphome.core import CORE, EsphomeError
|
||||||
from esphome.helpers import color, indent
|
from esphome.helpers import color, indent
|
||||||
from esphome.py_compat import IS_PY2, safe_input, text_type
|
from esphome.py_compat import IS_PY2, safe_input
|
||||||
from esphome.storage_json import StorageJSON, storage_path
|
from esphome.util import run_external_command, run_external_process, safe_print
|
||||||
from esphome.util import run_external_command, run_external_process, safe_print, \
|
|
||||||
is_dev_esphome_version
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
PRE_INITIALIZE = ['esphome', 'logger', 'wifi', 'ethernet', 'ota', 'mqtt', 'web_server', 'api',
|
|
||||||
'i2c']
|
|
||||||
|
|
||||||
|
|
||||||
def get_serial_ports():
|
def get_serial_ports():
|
||||||
# from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py
|
# 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):
|
def write_cpp(config):
|
||||||
from esphome.cpp_generator import Expression, RawStatement, add, statement
|
|
||||||
|
|
||||||
_LOGGER.info("Generating C++ source...")
|
_LOGGER.info("Generating C++ source...")
|
||||||
|
|
||||||
CORE.add_job(core_config.to_code, config[CONF_ESPHOME], domain='esphome')
|
for _, component, conf in iter_components(CORE.config):
|
||||||
for domain in PRE_INITIALIZE:
|
if component.to_code is not None:
|
||||||
if domain == CONF_ESPHOME or domain not in config:
|
CORE.add_job(component.to_code, conf)
|
||||||
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)
|
|
||||||
|
|
||||||
CORE.flush_tasks()
|
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()
|
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)
|
writer.write_cpp(code_s)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@@ -160,17 +137,7 @@ def compile_program(args, config):
|
|||||||
from esphome import platformio_api
|
from esphome import platformio_api
|
||||||
|
|
||||||
_LOGGER.info("Compiling app...")
|
_LOGGER.info("Compiling app...")
|
||||||
rc = platformio_api.run_compile(config, args.verbose)
|
return 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
|
|
||||||
|
|
||||||
|
|
||||||
def upload_using_esptool(config, port):
|
def upload_using_esptool(config, port):
|
||||||
@@ -195,31 +162,13 @@ def upload_program(config, args, host):
|
|||||||
return upload_using_esptool(config, host)
|
return upload_using_esptool(config, host)
|
||||||
return platformio_api.run_upload(config, args.verbose, host)
|
return platformio_api.run_upload(config, args.verbose, host)
|
||||||
|
|
||||||
from esphome.components import ota
|
|
||||||
from esphome import espota2
|
from esphome import espota2
|
||||||
|
|
||||||
if args.host_port is not None:
|
ota_conf = config[CONF_OTA]
|
||||||
host_port = args.host_port
|
remote_port = ota_conf[CONF_PORT]
|
||||||
else:
|
password = ota_conf[CONF_PASSWORD]
|
||||||
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())
|
|
||||||
res = espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
res = espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
||||||
if res == 0:
|
return res
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
def show_logs(config, args, port):
|
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)
|
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):
|
def clean_mqtt(config, args):
|
||||||
@@ -409,7 +358,6 @@ def parse_args(argv):
|
|||||||
'and upload the latest binary.')
|
'and upload the latest binary.')
|
||||||
parser_upload.add_argument('--upload-port', help="Manually specify the upload port to use. "
|
parser_upload.add_argument('--upload-port', help="Manually specify the upload port to use. "
|
||||||
"For example /dev/cu.SLAB_USBtoUART.")
|
"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 '
|
parser_logs = subparsers.add_parser('logs', help='Validate the configuration '
|
||||||
'and show all MQTT logs.')
|
'and show all MQTT logs.')
|
||||||
@@ -424,7 +372,6 @@ def parse_args(argv):
|
|||||||
'upload it, and start MQTT logs.')
|
'upload it, and start MQTT logs.')
|
||||||
parser_run.add_argument('--upload-port', help="Manually specify the upload port/ip to use. "
|
parser_run.add_argument('--upload-port', help="Manually specify the upload port/ip to use. "
|
||||||
"For example /dev/cu.SLAB_USBtoUART.")
|
"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.',
|
parser_run.add_argument('--no-logs', help='Disable starting MQTT logs.',
|
||||||
action='store_true')
|
action='store_true')
|
||||||
parser_run.add_argument('--topic', help='Manually set the topic to subscribe to for logs.')
|
parser_run.add_argument('--topic', help='Manually set the topic to subscribe to for logs.')
|
||||||
|
|||||||
+39
-45
@@ -1,12 +1,10 @@
|
|||||||
import copy
|
import copy
|
||||||
|
|
||||||
import voluptuous as vol
|
|
||||||
|
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ABOVE, CONF_ACTION_ID, CONF_AND, CONF_AUTOMATION_ID, CONF_BELOW, \
|
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_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
|
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, \
|
from esphome.cpp_generator import Pvariable, TemplateArguments, add, get_variable, \
|
||||||
process_lambda, templatable
|
process_lambda, templatable
|
||||||
from esphome.cpp_types import Action, App, Component, PollingComponent, Trigger, bool_, \
|
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):
|
def maybe_simple_id(*validators):
|
||||||
validator = vol.All(*validators)
|
validator = cv.All(*validators)
|
||||||
|
|
||||||
def validate(value):
|
def validate(value):
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
@@ -32,24 +30,24 @@ def validate_recursive_condition(value):
|
|||||||
path = [i] if is_list else []
|
path = [i] if is_list else []
|
||||||
item = copy.deepcopy(item)
|
item = copy.deepcopy(item)
|
||||||
if not isinstance(item, dict):
|
if not isinstance(item, dict):
|
||||||
raise vol.Invalid(u"Condition must consist of key-value mapping! Got {}".format(item),
|
raise cv.Invalid(u"Condition must consist of key-value mapping! Got {}".format(item),
|
||||||
path)
|
path)
|
||||||
key = next((x for x in item if x != CONF_CONDITION_ID), None)
|
key = next((x for x in item if x != CONF_CONDITION_ID), None)
|
||||||
if key is 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:
|
if key not in CONDITION_REGISTRY:
|
||||||
raise vol.Invalid(u"Unable to find condition with the name '{}', is the "
|
raise cv.Invalid(u"Unable to find condition with the name '{}', is the "
|
||||||
u"component loaded?".format(key), path + [key])
|
u"component loaded?".format(key), path + [key])
|
||||||
item.setdefault(CONF_CONDITION_ID, None)
|
item.setdefault(CONF_CONDITION_ID, None)
|
||||||
key2 = next((x for x in item if x not in (CONF_CONDITION_ID, key)), None)
|
key2 = next((x for x in item if x not in (CONF_CONDITION_ID, key)), None)
|
||||||
if key2 is not None:
|
if key2 is not None:
|
||||||
raise vol.Invalid(u"Cannot have two conditions in one item. Key '{}' overrides '{}'! "
|
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"Did you forget to indent the block inside the condition?"
|
||||||
u"".format(key, key2), path)
|
u"".format(key, key2), path)
|
||||||
validator = CONDITION_REGISTRY[key][0]
|
validator = CONDITION_REGISTRY[key][0]
|
||||||
try:
|
try:
|
||||||
condition = validator(item[key] or {})
|
condition = validator(item[key] or {})
|
||||||
except vol.Invalid as err:
|
except cv.Invalid as err:
|
||||||
err.prepend(path)
|
err.prepend(path)
|
||||||
raise err
|
raise err
|
||||||
value[i] = {
|
value[i] = {
|
||||||
@@ -67,24 +65,24 @@ def validate_recursive_action(value):
|
|||||||
path = [i] if is_list else []
|
path = [i] if is_list else []
|
||||||
item = copy.deepcopy(item)
|
item = copy.deepcopy(item)
|
||||||
if not isinstance(item, dict):
|
if not isinstance(item, dict):
|
||||||
raise vol.Invalid(u"Action must consist of key-value mapping! Got {}".format(item),
|
raise cv.Invalid(u"Action must consist of key-value mapping! Got {}".format(item),
|
||||||
path)
|
path)
|
||||||
key = next((x for x in item if x != CONF_ACTION_ID), None)
|
key = next((x for x in item if x != CONF_ACTION_ID), None)
|
||||||
if key is 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:
|
if key not in ACTION_REGISTRY:
|
||||||
raise vol.Invalid(u"Unable to find action with the name '{}', is the component loaded?"
|
raise cv.Invalid(u"Unable to find action with the name '{}', is the component loaded?"
|
||||||
u"".format(key), path + [key])
|
u"".format(key), path + [key])
|
||||||
item.setdefault(CONF_ACTION_ID, None)
|
item.setdefault(CONF_ACTION_ID, None)
|
||||||
key2 = next((x for x in item if x not in (CONF_ACTION_ID, key)), None)
|
key2 = next((x for x in item if x not in (CONF_ACTION_ID, key)), None)
|
||||||
if key2 is not None:
|
if key2 is not None:
|
||||||
raise vol.Invalid(u"Cannot have two actions in one item. Key '{}' overrides '{}'! "
|
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"Did you forget to indent the block inside the action?"
|
||||||
u"".format(key, key2), path)
|
u"".format(key, key2), path)
|
||||||
validator = ACTION_REGISTRY[key][0]
|
validator = ACTION_REGISTRY[key][0]
|
||||||
try:
|
try:
|
||||||
action = validator(item[key] or {})
|
action = validator(item[key] or {})
|
||||||
except vol.Invalid as err:
|
except cv.Invalid as err:
|
||||||
err.prepend(path)
|
err.prepend(path)
|
||||||
raise err
|
raise err
|
||||||
value[i] = {
|
value[i] = {
|
||||||
@@ -116,7 +114,7 @@ LambdaCondition = esphome_ns.class_('LambdaCondition', Condition)
|
|||||||
def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
||||||
if extra_schema is None:
|
if extra_schema is None:
|
||||||
extra_schema = {}
|
extra_schema = {}
|
||||||
if isinstance(extra_schema, vol.Schema):
|
if isinstance(extra_schema, cv.Schema):
|
||||||
extra_schema = extra_schema.schema
|
extra_schema = extra_schema.schema
|
||||||
schema = AUTOMATION_SCHEMA.extend(extra_schema)
|
schema = AUTOMATION_SCHEMA.extend(extra_schema)
|
||||||
|
|
||||||
@@ -125,17 +123,17 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
|||||||
try:
|
try:
|
||||||
# First try as a sequence of actions
|
# First try as a sequence of actions
|
||||||
return [schema({CONF_THEN: value})]
|
return [schema({CONF_THEN: value})]
|
||||||
except vol.Invalid as err:
|
except cv.Invalid as err:
|
||||||
if err.path and err.path[0] == CONF_THEN:
|
if err.path and err.path[0] == CONF_THEN:
|
||||||
err.path.pop(0)
|
err.path.pop(0)
|
||||||
|
|
||||||
# Next try as a sequence of automations
|
# Next try as a sequence of automations
|
||||||
try:
|
try:
|
||||||
return cv.Schema([schema])(value)
|
return cv.Schema([schema])(value)
|
||||||
except vol.Invalid as err2:
|
except cv.Invalid as err2:
|
||||||
if 'Unable to find action' in str(err):
|
if 'Unable to find action' in str(err):
|
||||||
raise err2
|
raise err2
|
||||||
raise vol.MultipleInvalid([err, err2])
|
raise cv.MultipleInvalid([err, err2])
|
||||||
elif isinstance(value, dict):
|
elif isinstance(value, dict):
|
||||||
if CONF_THEN in value:
|
if CONF_THEN in value:
|
||||||
return [schema(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)
|
value = cv.Schema([extra_validators])(value)
|
||||||
if single:
|
if single:
|
||||||
if len(value) != 1:
|
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[0]
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@@ -159,7 +157,7 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
|||||||
AUTOMATION_SCHEMA = cv.Schema({
|
AUTOMATION_SCHEMA = cv.Schema({
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(Trigger),
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(Trigger),
|
||||||
cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_variable_id(Automation),
|
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
|
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)
|
yield Pvariable(condition_id, rhs, type=type)
|
||||||
|
|
||||||
|
|
||||||
RANGE_CONDITION_SCHEMA = vol.All(cv.Schema({
|
RANGE_CONDITION_SCHEMA = cv.All(cv.Schema({
|
||||||
vol.Optional(CONF_ABOVE): cv.templatable(cv.float_),
|
cv.Optional(CONF_ABOVE): cv.templatable(cv.float_),
|
||||||
vol.Optional(CONF_BELOW): cv.templatable(cv.float_),
|
cv.Optional(CONF_BELOW): cv.templatable(cv.float_),
|
||||||
}), cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW))
|
}), 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
|
yield action
|
||||||
|
|
||||||
|
|
||||||
IF_ACTION_SCHEMA = vol.All({
|
IF_ACTION_SCHEMA = cv.All({
|
||||||
vol.Required(CONF_CONDITION): validate_recursive_condition,
|
cv.Required(CONF_CONDITION): validate_recursive_condition,
|
||||||
vol.Optional(CONF_THEN): validate_recursive_action,
|
cv.Optional(CONF_THEN): validate_recursive_action,
|
||||||
vol.Optional(CONF_ELSE): validate_recursive_action,
|
cv.Optional(CONF_ELSE): validate_recursive_action,
|
||||||
}, cv.has_at_least_one_key(CONF_THEN, CONF_ELSE))
|
}, 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({
|
WHILE_ACTION_SCHEMA = cv.Schema({
|
||||||
vol.Required(CONF_CONDITION): validate_recursive_condition,
|
cv.Required(CONF_CONDITION): validate_recursive_condition,
|
||||||
vol.Required(CONF_THEN): validate_recursive_action,
|
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):
|
def validate_wait_until(value):
|
||||||
schema = cv.Schema({
|
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:
|
if isinstance(value, dict) and CONF_CONDITION in value:
|
||||||
return schema(value)
|
return schema(value)
|
||||||
@@ -303,7 +301,7 @@ def lambda_condition_to_code(config, condition_id, template_arg, args):
|
|||||||
|
|
||||||
CONF_COMPONENT_UPDATE = 'component.update'
|
CONF_COMPONENT_UPDATE = 'component.update'
|
||||||
COMPONENT_UPDATE_ACTION_SCHEMA = maybe_simple_id({
|
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
|
@coroutine
|
||||||
def build_automation_(trigger, args, config):
|
def build_automation(trigger, args, config):
|
||||||
arg_types = [arg[0] for arg in args]
|
arg_types = [arg[0] for arg in args]
|
||||||
templ = TemplateArguments(*arg_types)
|
templ = TemplateArguments(*arg_types)
|
||||||
rhs = App.make_automation(templ, trigger)
|
|
||||||
type = Automation.template(templ)
|
type = Automation.template(templ)
|
||||||
|
rhs = type.new(trigger)
|
||||||
obj = Pvariable(config[CONF_AUTOMATION_ID], rhs, type=type)
|
obj = Pvariable(config[CONF_AUTOMATION_ID], rhs, type=type)
|
||||||
actions = yield build_actions(config[CONF_THEN], templ, args)
|
actions = yield build_actions(config[CONF_THEN], templ, args)
|
||||||
add(obj.add_actions(actions))
|
add(obj.add_actions(actions))
|
||||||
yield obj
|
yield obj
|
||||||
|
|
||||||
|
|
||||||
def build_automations(trigger, args, config):
|
|
||||||
CORE.add_job(build_automation_, trigger, args, config)
|
|
||||||
|
|||||||
@@ -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,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
|
||||||
@@ -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
|
||||||
@@ -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,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
|
||||||
@@ -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
|
||||||
@@ -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'
|
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
@@ -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 import sensor
|
||||||
from esphome.components.ads1115 import ADS1115Component
|
from esphome.components.ads1115 import ADS1115Component
|
||||||
import esphome.config_validation as cv
|
from esphome.const import CONF_GAIN, CONF_MULTIPLEXER, CONF_UPDATE_INTERVAL, \
|
||||||
from esphome.const import CONF_ADS1115_ID, CONF_GAIN, CONF_MULTIPLEXER, CONF_NAME, \
|
ICON_FLASH, UNIT_VOLT, CONF_ID, CONF_NAME
|
||||||
CONF_UPDATE_INTERVAL
|
|
||||||
from esphome.cpp_generator import get_variable
|
|
||||||
from esphome.py_compat import string_types
|
from esphome.py_compat import string_types
|
||||||
|
from . import ads1115_ns
|
||||||
|
|
||||||
DEPENDENCIES = ['ads1115']
|
DEPENDENCIES = ['ads1115']
|
||||||
|
|
||||||
ADS1115Multiplexer = sensor.sensor_ns.enum('ADS1115Multiplexer')
|
ADS1115Multiplexer = ads1115_ns.enum('ADS1115Multiplexer')
|
||||||
MUX = {
|
MUX = {
|
||||||
'A0_A1': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1,
|
'A0_A1': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1,
|
||||||
'A0_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3,
|
'A0_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3,
|
||||||
@@ -22,7 +21,7 @@ MUX = {
|
|||||||
'A3_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG,
|
'A3_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG,
|
||||||
}
|
}
|
||||||
|
|
||||||
ADS1115Gain = sensor.sensor_ns.enum('ADS1115Gain')
|
ADS1115Gain = ads1115_ns.enum('ADS1115Gain')
|
||||||
GAIN = {
|
GAIN = {
|
||||||
'6.144': ADS1115Gain.ADS1115_GAIN_6P144,
|
'6.144': ADS1115Gain.ADS1115_GAIN_6P144,
|
||||||
'4.096': ADS1115Gain.ADS1115_GAIN_4P096,
|
'4.096': ADS1115Gain.ADS1115_GAIN_4P096,
|
||||||
@@ -37,35 +36,29 @@ def validate_gain(value):
|
|||||||
if isinstance(value, float):
|
if isinstance(value, float):
|
||||||
value = u'{:0.03f}'.format(value)
|
value = u'{:0.03f}'.format(value)
|
||||||
elif not isinstance(value, string_types):
|
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)
|
return cv.one_of(*GAIN)(value)
|
||||||
|
|
||||||
|
|
||||||
def validate_mux(value):
|
ADS1115Sensor = ads1115_ns.class_('ADS1115Sensor', sensor.Sensor)
|
||||||
value = cv.string(value).upper()
|
|
||||||
value = value.replace(' ', '_')
|
|
||||||
return cv.one_of(*MUX)(value)
|
|
||||||
|
|
||||||
|
|
||||||
ADS1115Sensor = sensor.sensor_ns.class_('ADS1115Sensor', sensor.EmptySensor)
|
CONF_ADS1115_ID = 'ads1115_id'
|
||||||
|
CONFIG_SCHEMA = cv.nameable(sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 3).extend({
|
||||||
PLATFORM_SCHEMA = cv.nameable(sensor.SENSOR_PLATFORM_SCHEMA.extend({
|
|
||||||
cv.GenerateID(): cv.declare_variable_id(ADS1115Sensor),
|
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),
|
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):
|
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]]
|
cg.add(hub.register_sensor(var))
|
||||||
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'
|
|
||||||
@@ -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'
|
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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))
|
||||||
@@ -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 import automation
|
||||||
from esphome.automation import ACTION_REGISTRY, CONDITION_REGISTRY, Condition
|
from esphome.automation import ACTION_REGISTRY, CONDITION_REGISTRY, Condition
|
||||||
import esphome.config_validation as cv
|
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, \
|
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
|
CONF_REBOOT_TIMEOUT, CONF_SERVICE, CONF_VARIABLES, CONF_SERVICES, CONF_TRIGGER_ID
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
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
|
|
||||||
|
|
||||||
api_ns = esphome_ns.namespace('api')
|
api_ns = cg.esphome_ns.namespace('api')
|
||||||
APIServer = api_ns.class_('APIServer', Component, StoringController)
|
APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller)
|
||||||
HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', Action)
|
HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', cg.Action)
|
||||||
KeyValuePair = api_ns.class_('KeyValuePair')
|
KeyValuePair = api_ns.class_('KeyValuePair')
|
||||||
TemplatableKeyValuePair = api_ns.class_('TemplatableKeyValuePair')
|
TemplatableKeyValuePair = api_ns.class_('TemplatableKeyValuePair')
|
||||||
APIConnectedCondition = api_ns.class_('APIConnectedCondition', Condition)
|
APIConnectedCondition = api_ns.class_('APIConnectedCondition', Condition)
|
||||||
|
|
||||||
UserService = api_ns.class_('UserService', Trigger)
|
UserService = api_ns.class_('UserService', cg.Trigger)
|
||||||
ServiceTypeArgument = api_ns.class_('ServiceTypeArgument')
|
ServiceTypeArgument = api_ns.class_('ServiceTypeArgument')
|
||||||
ServiceArgType = api_ns.enum('ServiceArgType')
|
ServiceArgType = api_ns.enum('ServiceArgType')
|
||||||
SERVICE_ARG_TYPES = {
|
SERVICE_ARG_TYPES = {
|
||||||
@@ -28,38 +24,37 @@ SERVICE_ARG_TYPES = {
|
|||||||
'string': ServiceArgType.SERVICE_ARG_TYPE_STRING,
|
'string': ServiceArgType.SERVICE_ARG_TYPE_STRING,
|
||||||
}
|
}
|
||||||
SERVICE_ARG_NATIVE_TYPES = {
|
SERVICE_ARG_NATIVE_TYPES = {
|
||||||
'bool': bool_,
|
'bool': bool,
|
||||||
'int': int32,
|
'int': cg.int32,
|
||||||
'float': float_,
|
'float': float,
|
||||||
'string': std_string,
|
'string': cg.std_string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = cv.Schema({
|
||||||
cv.GenerateID(): cv.declare_variable_id(APIServer),
|
cv.GenerateID(): cv.declare_variable_id(APIServer),
|
||||||
vol.Optional(CONF_PORT, default=6053): cv.port,
|
cv.Optional(CONF_PORT, default=6053): cv.port,
|
||||||
vol.Optional(CONF_PASSWORD, default=''): cv.string_strict,
|
cv.Optional(CONF_PASSWORD, default=''): cv.string_strict,
|
||||||
vol.Optional(CONF_REBOOT_TIMEOUT): cv.positive_time_period_milliseconds,
|
cv.Optional(CONF_REBOOT_TIMEOUT, default='5min'): cv.positive_time_period_milliseconds,
|
||||||
vol.Optional(CONF_SERVICES): automation.validate_automation({
|
cv.Optional(CONF_SERVICES): automation.validate_automation({
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(UserService),
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(UserService),
|
||||||
vol.Required(CONF_SERVICE): cv.valid_name,
|
cv.Required(CONF_SERVICE): cv.valid_name,
|
||||||
vol.Optional(CONF_VARIABLES, default={}): cv.Schema({
|
cv.Optional(CONF_VARIABLES, default={}): cv.Schema({
|
||||||
cv.validate_id_name: cv.one_of(*SERVICE_ARG_TYPES, lower=True),
|
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):
|
def to_code(config):
|
||||||
rhs = App.init_api_server()
|
rhs = APIServer.new()
|
||||||
api = Pvariable(config[CONF_ID], rhs)
|
api = cg.Pvariable(config[CONF_ID], rhs)
|
||||||
|
yield cg.register_component(api, config)
|
||||||
|
|
||||||
if config[CONF_PORT] != 6053:
|
cg.add(api.set_port(config[CONF_PORT]))
|
||||||
add(api.set_port(config[CONF_PORT]))
|
cg.add(api.set_password(config[CONF_PASSWORD]))
|
||||||
if config.get(CONF_PASSWORD):
|
cg.add(api.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
||||||
add(api.set_password(config[CONF_PASSWORD]))
|
|
||||||
if CONF_REBOOT_TIMEOUT in config:
|
|
||||||
add(api.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
|
||||||
|
|
||||||
for conf in config.get(CONF_SERVICES, []):
|
for conf in config.get(CONF_SERVICES, []):
|
||||||
template_args = []
|
template_args = []
|
||||||
@@ -73,58 +68,51 @@ def to_code(config):
|
|||||||
func = api.make_user_service_trigger.template(*template_args)
|
func = api.make_user_service_trigger.template(*template_args)
|
||||||
rhs = func(conf[CONF_SERVICE], service_type_args)
|
rhs = func(conf[CONF_SERVICE], service_type_args)
|
||||||
type_ = UserService.template(*template_args)
|
type_ = UserService.template(*template_args)
|
||||||
trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs, type=type_)
|
trigger = cg.Pvariable(conf[CONF_TRIGGER_ID], rhs, type=type_)
|
||||||
automation.build_automations(trigger, func_args, conf)
|
yield automation.build_automation(trigger, func_args, conf)
|
||||||
|
|
||||||
setup_component(api, config)
|
cg.add_define('USE_API')
|
||||||
|
|
||||||
|
|
||||||
BUILD_FLAGS = '-DUSE_API'
|
|
||||||
|
|
||||||
|
|
||||||
def lib_deps(config):
|
|
||||||
if CORE.is_esp32:
|
if CORE.is_esp32:
|
||||||
return 'AsyncTCP@1.0.3'
|
cg.add_library('AsyncTCP', '1.0.3')
|
||||||
if CORE.is_esp8266:
|
elif CORE.is_esp8266:
|
||||||
return 'ESPAsyncTCP@1.2.0'
|
cg.add_library('ESPAsyncTCP', '1.2.0')
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
CONF_HOMEASSISTANT_SERVICE = 'homeassistant.service'
|
CONF_HOMEASSISTANT_SERVICE = 'homeassistant.service'
|
||||||
HOMEASSISTANT_SERVIC_ACTION_SCHEMA = cv.Schema({
|
HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({
|
||||||
cv.GenerateID(): cv.use_variable_id(APIServer),
|
cv.GenerateID(): cv.use_variable_id(APIServer),
|
||||||
vol.Required(CONF_SERVICE): cv.string,
|
cv.Required(CONF_SERVICE): cv.string,
|
||||||
vol.Optional(CONF_DATA): cv.Schema({
|
cv.Optional(CONF_DATA): cv.Schema({
|
||||||
cv.string: cv.string,
|
cv.string: cv.string,
|
||||||
}),
|
}),
|
||||||
vol.Optional(CONF_DATA_TEMPLATE): cv.Schema({
|
cv.Optional(CONF_DATA_TEMPLATE): cv.Schema({
|
||||||
cv.string: cv.string,
|
cv.string: cv.string,
|
||||||
}),
|
}),
|
||||||
vol.Optional(CONF_VARIABLES): cv.Schema({
|
cv.Optional(CONF_VARIABLES): cv.Schema({
|
||||||
cv.string: cv.lambda_,
|
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):
|
def homeassistant_service_to_code(config, action_id, template_arg, args):
|
||||||
var = yield get_variable(config[CONF_ID])
|
var = yield cg.get_variable(config[CONF_ID])
|
||||||
rhs = var.make_home_assistant_service_call_action(template_arg)
|
|
||||||
type = HomeAssistantServiceCallAction.template(template_arg)
|
type = HomeAssistantServiceCallAction.template(template_arg)
|
||||||
act = Pvariable(action_id, rhs, type=type)
|
rhs = type.new(var)
|
||||||
add(act.set_service(config[CONF_SERVICE]))
|
act = cg.Pvariable(action_id, rhs, type=type)
|
||||||
|
cg.add(act.set_service(config[CONF_SERVICE]))
|
||||||
if CONF_DATA in config:
|
if CONF_DATA in config:
|
||||||
datas = [KeyValuePair(k, v) for k, v in config[CONF_DATA].items()]
|
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:
|
if CONF_DATA_TEMPLATE in config:
|
||||||
datas = [KeyValuePair(k, v) for k, v in config[CONF_DATA_TEMPLATE].items()]
|
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:
|
if CONF_VARIABLES in config:
|
||||||
datas = []
|
datas = []
|
||||||
for key, value in config[CONF_VARIABLES].items():
|
for key, value in config[CONF_VARIABLES].items():
|
||||||
value_ = yield process_lambda(value, [])
|
value_ = yield cg.process_lambda(value, [])
|
||||||
datas.append(TemplatableKeyValuePair(key, value_))
|
datas.append(TemplatableKeyValuePair(key, value_))
|
||||||
add(act.set_variables(datas))
|
cg.add(act.set_variables(datas))
|
||||||
yield act
|
yield act
|
||||||
|
|
||||||
|
|
||||||
@@ -136,4 +124,4 @@ API_CONNECTED_CONDITION_SCHEMA = cv.Schema({})
|
|||||||
def api_connected_to_code(config, condition_id, template_arg, args):
|
def api_connected_to_code(config, condition_id, template_arg, args):
|
||||||
rhs = APIConnectedCondition.new(template_arg)
|
rhs = APIConnectedCondition.new(template_arg)
|
||||||
type = APIConnectedCondition.template(template_arg)
|
type = APIConnectedCondition.template(template_arg)
|
||||||
yield Pvariable(condition_id, rhs, type=type)
|
yield cg.Pvariable(condition_id, rhs, type=type)
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||||
@@ -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
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user