🏗 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:
Otto Winter
2019-04-17 12:06:00 +02:00
committed by GitHub
parent 049807e3ab
commit 6682c43dfa
817 changed files with 54156 additions and 10830 deletions
+137
View File
@@ -0,0 +1,137 @@
Language: Cpp
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: DontAlign
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 120
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: true
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^<ext/.*\.h>'
Priority: 2
- Regex: '^<.*\.h>'
Priority: 1
- Regex: '^<.*'
Priority: 2
- Regex: '.*'
Priority: 3
IncludeIsMainRegex: '([-_](test|unittest))?$'
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 2
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 2000
PointerAlignment: Right
RawStringFormats:
- Language: Cpp
Delimiters:
- cc
- CC
- cpp
- Cpp
- CPP
- 'c++'
- 'C++'
CanonicalDelimiter: ''
BasedOnStyle: google
- Language: TextProto
Delimiters:
- pb
- PB
- proto
- PROTO
EnclosingFunctions:
- EqualsProto
- EquivToProto
- PARSE_PARTIAL_TEXT_PROTO
- PARSE_TEST_PROTO
- PARSE_TEXT_PROTO
- ParseTextOrDie
- ParseTextProtoOrDie
CanonicalDelimiter: ''
BasedOnStyle: google
ReflowComments: true
SortIncludes: false
SortUsingDeclarations: false
SpaceAfterCStyleCast: true
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
TabWidth: 2
UseTab: Never
+127
View File
@@ -0,0 +1,127 @@
---
Checks: >-
*,
-abseil-*,
-android-*,
-boost-*,
-bugprone-macro-parentheses,
-cert-dcl50-cpp,
-cert-err58-cpp,
-clang-analyzer-core.CallAndMessage,
-clang-analyzer-osx.*,
-clang-analyzer-security.*,
-cppcoreguidelines-avoid-goto,
-cppcoreguidelines-c-copy-assignment-signature,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-pro-type-const-cast,
-cppcoreguidelines-pro-type-cstyle-cast,
-cppcoreguidelines-pro-type-member-init,
-cppcoreguidelines-pro-type-reinterpret-cast,
-cppcoreguidelines-pro-type-static-cast-downcast,
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-special-member-functions,
-fuchsia-*,
-fuchsia-default-arguments,
-fuchsia-multiple-inheritance,
-fuchsia-overloaded-operator,
-fuchsia-statically-constructed-objects,
-google-build-using-namespace,
-google-explicit-constructor,
-google-readability-braces-around-statements,
-google-readability-casting,
-google-readability-todo,
-google-runtime-int,
-google-runtime-references,
-hicpp-*,
-llvm-header-guard,
-llvm-include-order,
-misc-unconventional-assign-operator,
-misc-unused-parameters,
-modernize-deprecated-headers,
-modernize-pass-by-value,
-modernize-pass-by-value,
-modernize-return-braced-init-list,
-modernize-use-auto,
-modernize-use-default-member-init,
-modernize-use-equals-default,
-mpi-*,
-objc-*,
-performance-unnecessary-value-param,
-readability-braces-around-statements,
-readability-else-after-return,
-readability-implicit-bool-conversion,
-readability-named-parameter,
-readability-redundant-member-init,
-warnings-as-errors,
-zircon-*
WarningsAsErrors: '*'
HeaderFilterRegex: '^.*/src/esphome/.*'
AnalyzeTemporaryDtors: false
FormatStyle: google
CheckOptions:
- key: google-readability-braces-around-statements.ShortStatementLines
value: '1'
- key: google-readability-function-size.StatementThreshold
value: '800'
- key: google-readability-namespace-comments.ShortNamespaceLines
value: '10'
- key: google-readability-namespace-comments.SpacesBeforeComments
value: '2'
- key: modernize-loop-convert.MaxCopySize
value: '16'
- key: modernize-loop-convert.MinConfidence
value: reasonable
- key: modernize-loop-convert.NamingStyle
value: CamelCase
- key: modernize-pass-by-value.IncludeStyle
value: llvm
- key: modernize-replace-auto-ptr.IncludeStyle
value: llvm
- key: modernize-use-nullptr.NullMacros
value: 'NULL'
- key: readability-identifier-naming.LocalVariableCase
value: 'lower_case'
- key: readability-identifier-naming.ClassCase
value: 'CamelCase'
- key: readability-identifier-naming.StructCase
value: 'CamelCase'
- key: readability-identifier-naming.EnumCase
value: 'CamelCase'
- key: readability-identifier-naming.EnumConstantCase
value: 'UPPER_CASE'
- key: readability-identifier-naming.StaticConstantCase
value: 'UPPER_CASE'
- key: readability-identifier-naming.StaticVariableCase
value: 'UPPER_CASE'
- key: readability-identifier-naming.GlobalConstantCase
value: 'UPPER_CASE'
- key: readability-identifier-naming.ParameterCase
value: 'lower_case'
- key: readability-identifier-naming.PrivateMemberPrefix
value: 'NO_PRIVATE_MEMBERS_ALWAYS_USE_PROTECTED'
- key: readability-identifier-naming.PrivateMethodPrefix
value: 'NO_PRIVATE_METHODS_ALWAYS_USE_PROTECTED'
- key: readability-identifier-naming.ClassMemberCase
value: 'lower_case'
- key: readability-identifier-naming.ClassMemberCase
value: 'lower_case'
- key: readability-identifier-naming.ProtectedMemberCase
value: 'lower_case'
- key: readability-identifier-naming.ProtectedMemberSuffix
value: '_'
- key: readability-identifier-naming.FunctionCase
value: 'lower_case'
- key: readability-identifier-naming.ClassMethodCase
value: 'lower_case'
- key: readability-identifier-naming.ProtectedMethodCase
value: 'lower_case'
- key: readability-identifier-naming.ProtectedMethodSuffix
value: '_'
- key: readability-identifier-naming.VirtualMethodCase
value: 'lower_case'
- key: readability-identifier-naming.VirtualMethodSuffix
value: ''
+37 -43
View File
@@ -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
View File
@@ -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
+690 -2
View File
File diff suppressed because it is too large Load Diff
+2 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+26
View File
@@ -0,0 +1,26 @@
# Base file for all codegen-related imports
# All integrations should have a line in the import section like this
#
# >>> import esphome.codegen as cg
#
# Integrations should specifically *NOT* import directly from the
# other helper modules (cpp_generator etc) directly if they don't
# want to break suddenly due to a rename (this file will get backports for features).
# pylint: disable=unused-import
from esphome.cpp_generator import ( # noqa
Expression, RawExpression, TemplateArguments,
StructInitializer, ArrayInitializer, safe_exp, Statement,
progmem_array, statement, variable, Pvariable, new_Pvariable,
add, add_global, add_library, add_build_flag, add_define,
get_variable, process_lambda, is_template, templatable, MockObj,
MockObjClass)
from esphome.cpp_helpers import ( # noqa
gpio_pin_expression, register_component, build_registry_entry,
build_registry_list)
from esphome.cpp_types import ( # noqa
global_ns, void, nullptr, float_, bool_, std_ns, std_string,
std_vector, uint8, uint16, uint32, int32, const_char_ptr, NAN,
esphome_ns, App, Nameable, Trigger, Action, Component, ComponentPtr,
PollingComponent, Application, optional, arduino_json_ns, JsonObject,
JsonObjectRef, JsonObjectConstRef, Controller, GPIOPin)
+49
View File
@@ -0,0 +1,49 @@
#include "a4988.h"
#include "esphome/core/log.h"
namespace esphome {
namespace a4988 {
static const char *TAG = "a4988.stepper";
void A4988::setup() {
ESP_LOGCONFIG(TAG, "Setting up A4988...");
if (this->sleep_pin_ != nullptr) {
this->sleep_pin_->setup();
this->sleep_pin_->digital_write(false);
}
this->step_pin_->setup();
this->step_pin_->digital_write(false);
this->dir_pin_->setup();
this->dir_pin_->digital_write(false);
}
void A4988::dump_config() {
ESP_LOGCONFIG(TAG, "A4988:");
LOG_PIN(" Step Pin: ", this->step_pin_);
LOG_PIN(" Dir Pin: ", this->dir_pin_);
LOG_PIN(" Sleep Pin: ", this->sleep_pin_);
LOG_STEPPER(this);
}
void A4988::loop() {
bool at_target = this->has_reached_target();
if (this->sleep_pin_ != nullptr) {
this->sleep_pin_->digital_write(!at_target);
}
if (at_target) {
this->high_freq_.stop();
} else {
this->high_freq_.start();
}
int32_t dir = this->should_step_();
if (dir == 0)
return;
this->dir_pin_->digital_write(dir == 1);
this->step_pin_->digital_write(true);
delayMicroseconds(5);
this->step_pin_->digital_write(false);
}
} // namespace a4988
} // namespace esphome
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/esphal.h"
#include "esphome/components/stepper/stepper.h"
namespace esphome {
namespace a4988 {
class A4988 : public stepper::Stepper, public Component {
public:
A4988(GPIOPin *step_pin, GPIOPin *dir_pin) : step_pin_(step_pin), dir_pin_(dir_pin) {}
void set_sleep_pin(GPIOPin *sleep_pin) { this->sleep_pin_ = sleep_pin; }
void setup() override;
void dump_config() override;
void loop() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
protected:
GPIOPin *step_pin_;
GPIOPin *dir_pin_;
GPIOPin *sleep_pin_{nullptr};
HighFrequencyLoopRequester high_freq_;
};
} // namespace a4988
} // namespace esphome
+28
View File
@@ -0,0 +1,28 @@
from esphome import pins
from esphome.components import stepper
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import CONF_DIR_PIN, CONF_ID, CONF_SLEEP_PIN, CONF_STEP_PIN
a4988_ns = cg.esphome_ns.namespace('a4988')
A4988 = a4988_ns.class_('A4988', stepper.Stepper, cg.Component)
CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend({
cv.Required(CONF_ID): cv.declare_variable_id(A4988),
cv.Required(CONF_STEP_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_DIR_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_SLEEP_PIN): pins.gpio_output_pin_schema,
}).extend(cv.COMPONENT_SCHEMA)
def to_code(config):
step_pin = yield cg.gpio_pin_expression(config[CONF_STEP_PIN])
dir_pin = yield cg.gpio_pin_expression(config[CONF_DIR_PIN])
var = cg.new_Pvariable(config[CONF_ID], step_pin, dir_pin)
yield cg.register_component(var, config)
yield stepper.register_stepper(var, config)
if CONF_SLEEP_PIN in config:
sleep_pin = yield cg.gpio_pin_expression(config[CONF_SLEEP_PIN])
cg.add(var.set_sleep_pin(sleep_pin))
View File
+89
View File
@@ -0,0 +1,89 @@
#include "esphome/components/adc/adc_sensor.h"
#include "esphome/core/log.h"
namespace esphome {
namespace adc {
static const char *TAG = "adc";
ADCSensor::ADCSensor(const std::string &name, uint8_t pin, uint32_t update_interval)
: PollingSensorComponent(name, update_interval), pin_(pin) {}
#ifdef ARDUINO_ARCH_ESP32
void ADCSensor::set_attenuation(adc_attenuation_t attenuation) { this->attenuation_ = attenuation; }
#endif
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
GPIOPin(this->pin_, INPUT).setup();
#ifdef ARDUINO_ARCH_ESP32
analogSetPinAttenuation(this->pin_, this->attenuation_);
#endif
}
void ADCSensor::dump_config() {
LOG_SENSOR("", "ADC Sensor", this);
#ifdef ARDUINO_ARCH_ESP8266
#ifdef USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Pin: VCC");
#else
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
#endif
#endif
#ifdef ARDUINO_ARCH_ESP32
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
switch (this->attenuation_) {
case ADC_0db:
ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
break;
case ADC_2_5db:
ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)");
break;
case ADC_6db:
ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)");
break;
case ADC_11db:
ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)");
break;
}
#endif
LOG_UPDATE_INTERVAL(this);
}
float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
void ADCSensor::update() {
#ifdef ARDUINO_ARCH_ESP32
float value_v = analogRead(this->pin_) / 4095.0f;
switch (this->attenuation_) {
case ADC_0db:
value_v *= 1.1;
break;
case ADC_2_5db:
value_v *= 1.5;
break;
case ADC_6db:
value_v *= 2.2;
break;
case ADC_11db:
value_v *= 3.9;
break;
}
#endif
#ifdef ARDUINO_ARCH_ESP8266
#ifdef USE_ADC_SENSOR_VCC
float value_v = ESP.getVcc() / 1024.0f;
#else
float value_v = analogRead(this->pin_) / 1024.0f;
#endif
#endif
ESP_LOGD(TAG, "'%s': Got voltage=%.2fV", this->get_name().c_str(), value_v);
this->publish_state(value_v);
}
#ifdef ARDUINO_ARCH_ESP8266
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
#endif
} // namespace adc
} // namespace esphome
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/esphal.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace adc {
class ADCSensor : public sensor::PollingSensorComponent {
public:
/// Construct the ADCSensor with the provided pin and update interval in ms.
explicit ADCSensor(const std::string &name, uint8_t pin, uint32_t update_interval);
#ifdef ARDUINO_ARCH_ESP32
/// Set the attenuation for this pin. Only available on the ESP32.
void set_attenuation(adc_attenuation_t attenuation);
#endif
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
/// Update adc values.
void update() override;
/// Setup ADc
void setup() override;
void dump_config() override;
/// `HARDWARE_LATE` setup priority.
float get_setup_priority() const override;
#ifdef ARDUINO_ARCH_ESP8266
std::string unique_id() override;
#endif
protected:
uint8_t pin_;
#ifdef ARDUINO_ARCH_ESP32
adc_attenuation_t attenuation_{ADC_0db};
#endif
};
} // namespace adc
} // namespace esphome
+52
View File
@@ -0,0 +1,52 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import sensor
from esphome.const import CONF_ATTENUATION, CONF_ID, CONF_NAME, CONF_PIN, CONF_UPDATE_INTERVAL, \
CONF_ICON, ICON_FLASH, CONF_UNIT_OF_MEASUREMENT, UNIT_VOLT, CONF_ACCURACY_DECIMALS
ATTENUATION_MODES = {
'0db': cg.global_ns.ADC_0db,
'2.5db': cg.global_ns.ADC_2_5db,
'6db': cg.global_ns.ADC_6db,
'11db': cg.global_ns.ADC_11db,
}
def validate_adc_pin(value):
vcc = str(value).upper()
if vcc == 'VCC':
return cv.only_on_esp8266(vcc)
return pins.analog_pin(value)
adc_ns = cg.esphome_ns.namespace('adc')
ADCSensor = adc_ns.class_('ADCSensor', sensor.PollingSensorComponent)
CONFIG_SCHEMA = cv.nameable(sensor.SENSOR_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(ADCSensor),
cv.Required(CONF_PIN): validate_adc_pin,
cv.Optional(CONF_ATTENUATION): cv.All(cv.only_on_esp32, cv.one_of(*ATTENUATION_MODES,
lower=True)),
cv.Optional(CONF_UPDATE_INTERVAL, default='60s'): cv.update_interval,
cv.Optional(CONF_ICON, default=ICON_FLASH): sensor.icon,
cv.Optional(CONF_UNIT_OF_MEASUREMENT, default=UNIT_VOLT): sensor.unit_of_measurement,
cv.Optional(CONF_ACCURACY_DECIMALS, default=2): sensor.accuracy_decimals,
}).extend(cv.COMPONENT_SCHEMA))
def to_code(config):
pin = config[CONF_PIN]
if pin == 'VCC':
pin = 0
cg.add_define('USE_ADC_SENSOR_VCC')
cg.add_global(cg.global_ns.ADC_MODE(cg.global_ns.ADC_VCC))
rhs = ADCSensor.new(config[CONF_NAME], pin, config[CONF_UPDATE_INTERVAL])
adc = cg.Pvariable(config[CONF_ID], rhs)
yield cg.register_component(adc, config)
yield sensor.register_sensor(adc, config)
if CONF_ATTENUATION in config:
cg.add(adc.set_attenuation(ATTENUATION_MODES[config[CONF_ATTENUATION]]))
-27
View File
@@ -1,27 +0,0 @@
import voluptuous as vol
from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_ID
from esphome.cpp_generator import Pvariable
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App, Component
DEPENDENCIES = ['i2c']
MULTI_CONF = True
ADS1115Component = sensor.sensor_ns.class_('ADS1115Component', Component, i2c.I2CDevice)
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_variable_id(ADS1115Component),
vol.Required(CONF_ADDRESS): cv.i2c_address,
}).extend(cv.COMPONENT_SCHEMA.schema)
def to_code(config):
rhs = App.make_ads1115_component(config[CONF_ADDRESS])
var = Pvariable(config[CONF_ID], rhs)
setup_component(var, config)
BUILD_FLAGS = '-DUSE_ADS1115_SENSOR'
+21
View File
@@ -0,0 +1,21 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c
from esphome.const import CONF_ID
DEPENDENCIES = ['i2c']
AUTO_LOAD = ['sensor']
MULTI_CONF = True
ads1115_ns = cg.esphome_ns.namespace('ads1115')
ADS1115Component = ads1115_ns.class_('ADS1115Component', cg.Component, i2c.I2CDevice)
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_variable_id(ADS1115Component),
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(None))
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield i2c.register_i2c_device(var, config)
+158
View File
@@ -0,0 +1,158 @@
#include "ads1115.h"
#include "esphome/core/log.h"
namespace esphome {
namespace ads1115 {
static const char *TAG = "ads1115";
static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00;
static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
static const uint8_t ADS1115_DATA_RATE_860_SPS = 0b111;
void ADS1115Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
uint16_t value;
if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &value)) {
this->mark_failed();
return;
}
uint16_t config = 0;
// Clear single-shot bit
// 0b0xxxxxxxxxxxxxxx
config |= 0b0000000000000000;
// Setup multiplexer
// 0bx000xxxxxxxxxxxx
config |= ADS1115_MULTIPLEXER_P0_N1 << 12;
// Setup Gain
// 0bxxxx000xxxxxxxxx
config |= ADS1115_GAIN_6P144 << 9;
// Set singleshot mode
// 0bxxxxxxx1xxxxxxxx
config |= 0b0000000100000000;
// Set data rate - 860 samples per second (we're in singleshot mode)
// 0bxxxxxxxx100xxxxx
config |= ADS1115_DATA_RATE_860_SPS << 5;
// Set comparator mode - hysteresis
// 0bxxxxxxxxxxx0xxxx
config |= 0b0000000000000000;
// Set comparator polarity - active low
// 0bxxxxxxxxxxxx0xxx
config |= 0b0000000000000000;
// Set comparator latch enabled - false
// 0bxxxxxxxxxxxxx0xx
config |= 0b0000000000000000;
// Set comparator que mode - disabled
// 0bxxxxxxxxxxxxxx11
config |= 0b0000000000000011;
if (!this->write_byte_16(ADS1115_REGISTER_CONFIG, config)) {
this->mark_failed();
return;
}
for (auto *sensor : this->sensors_) {
this->set_interval(sensor->get_name(), sensor->update_interval(),
[this, sensor] { this->request_measurement_(sensor); });
}
}
void ADS1115Component::dump_config() {
ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with ADS1115 failed!");
}
for (auto *sensor : this->sensors_) {
LOG_SENSOR(" ", "Sensor", sensor);
ESP_LOGCONFIG(TAG, " Multiplexer: %u", sensor->get_multiplexer());
ESP_LOGCONFIG(TAG, " Gain: %u", sensor->get_gain());
}
}
float ADS1115Component::get_setup_priority() const { return setup_priority::DATA; }
void ADS1115Component::request_measurement_(ADS1115Sensor *sensor) {
uint16_t config;
if (!this->read_byte_16(ADS1115_REGISTER_CONFIG, &config)) {
this->status_set_warning();
return;
}
// Multiplexer
// 0bxBBBxxxxxxxxxxxx
config &= 0b1000111111111111;
config |= (sensor->get_multiplexer() & 0b111) << 12;
// Gain
// 0bxxxxBBBxxxxxxxxx
config &= 0b1111000111111111;
config |= (sensor->get_gain() & 0b111) << 9;
// Start conversion
config |= 0b1000000000000000;
if (!this->write_byte_16(ADS1115_REGISTER_CONFIG, config)) {
this->status_set_warning();
return;
}
// about 1.6 ms with 860 samples per second
delay(2);
uint32_t start = millis();
while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) {
if (millis() - start > 100) {
ESP_LOGW(TAG, "Reading ADS1115 timed out");
this->status_set_warning();
return;
}
yield();
}
uint16_t raw_conversion;
if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &raw_conversion)) {
this->status_set_warning();
return;
}
auto signed_conversion = static_cast<int16_t>(raw_conversion);
float millivolts;
switch (sensor->get_gain()) {
case ADS1115_GAIN_6P144:
millivolts = signed_conversion * 0.187500f;
break;
case ADS1115_GAIN_4P096:
millivolts = signed_conversion * 0.125000f;
break;
case ADS1115_GAIN_2P048:
millivolts = signed_conversion * 0.062500f;
break;
case ADS1115_GAIN_1P024:
millivolts = signed_conversion * 0.031250f;
break;
case ADS1115_GAIN_0P512:
millivolts = signed_conversion * 0.015625f;
break;
case ADS1115_GAIN_0P256:
millivolts = signed_conversion * 0.007813f;
break;
default:
millivolts = NAN;
}
float v = millivolts / 1000.0f;
ESP_LOGD(TAG, "'%s': Got Voltage=%fV", sensor->get_name().c_str(), v);
sensor->publish_state(v);
this->status_clear_warning();
}
uint8_t ADS1115Sensor::get_multiplexer() const { return this->multiplexer_; }
void ADS1115Sensor::set_multiplexer(ADS1115Multiplexer multiplexer) { this->multiplexer_ = multiplexer; }
uint8_t ADS1115Sensor::get_gain() const { return this->gain_; }
void ADS1115Sensor::set_gain(ADS1115Gain gain) { this->gain_ = gain; }
} // namespace ads1115
} // namespace esphome
+69
View File
@@ -0,0 +1,69 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace ads1115 {
enum ADS1115Multiplexer {
ADS1115_MULTIPLEXER_P0_N1 = 0b000,
ADS1115_MULTIPLEXER_P0_N3 = 0b001,
ADS1115_MULTIPLEXER_P1_N3 = 0b010,
ADS1115_MULTIPLEXER_P2_N3 = 0b011,
ADS1115_MULTIPLEXER_P0_NG = 0b100,
ADS1115_MULTIPLEXER_P1_NG = 0b101,
ADS1115_MULTIPLEXER_P2_NG = 0b110,
ADS1115_MULTIPLEXER_P3_NG = 0b111,
};
enum ADS1115Gain {
ADS1115_GAIN_6P144 = 0b000,
ADS1115_GAIN_4P096 = 0b001,
ADS1115_GAIN_2P048 = 0b010,
ADS1115_GAIN_1P024 = 0b011,
ADS1115_GAIN_0P512 = 0b100,
ADS1115_GAIN_0P256 = 0b101,
};
class ADS1115Sensor;
class ADS1115Component : public Component, public i2c::I2CDevice {
public:
void register_sensor(ADS1115Sensor *obj) { this->sensors_.push_back(obj); }
/// Set up the internal sensor array.
void setup() override;
void dump_config() override;
/// HARDWARE_LATE setup priority
float get_setup_priority() const override;
protected:
/// Helper method to request a measurement from a sensor.
void request_measurement_(ADS1115Sensor *sensor);
std::vector<ADS1115Sensor *> sensors_;
};
/// Internal holder class that is in instance of Sensor so that the hub can create individual sensors.
class ADS1115Sensor : public sensor::Sensor {
public:
ADS1115Sensor(const std::string &name, uint32_t update_interval)
: sensor::Sensor(name), update_interval_(update_interval) {}
void set_multiplexer(ADS1115Multiplexer multiplexer);
void set_gain(ADS1115Gain gain);
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
uint8_t get_multiplexer() const;
uint8_t get_gain() const;
protected:
ADS1115Multiplexer multiplexer_;
ADS1115Gain gain_;
uint32_t update_interval_;
};
} // namespace ads1115
} // namespace esphome
@@ -1,16 +1,15 @@
import voluptuous as vol import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor from esphome.components 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'
-33
View File
@@ -1,33 +0,0 @@
import voluptuous as vol
from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_UPDATE_INTERVAL
from esphome.cpp_generator import Pvariable, add
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App, PollingComponent
DEPENDENCIES = ['i2c']
MULTI_CONF = True
CONF_APDS9960_ID = 'apds9960_id'
APDS9960 = sensor.sensor_ns.class_('APDS9960', PollingComponent, i2c.I2CDevice)
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_variable_id(APDS9960),
vol.Optional(CONF_ADDRESS): cv.i2c_address,
vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval,
}).extend(cv.COMPONENT_SCHEMA.schema)
def to_code(config):
rhs = App.make_apds9960(config.get(CONF_UPDATE_INTERVAL))
var = Pvariable(config[CONF_ID], rhs)
if CONF_ADDRESS in config:
add(var.set_address(config[CONF_ADDRESS]))
setup_component(var, config)
BUILD_FLAGS = '-DUSE_APDS9960'
+24
View File
@@ -0,0 +1,24 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c
from esphome.const import CONF_ID, CONF_UPDATE_INTERVAL
DEPENDENCIES = ['i2c']
AUTO_LOAD = ['sensor', 'binary_sensor']
MULTI_CONF = True
CONF_APDS9960_ID = 'apds9960_id'
apds9960_nds = cg.esphome_ns.namespace('apds9960')
APDS9960 = apds9960_nds.class_('APDS9960', cg.PollingComponent, i2c.I2CDevice)
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_variable_id(APDS9960),
cv.Optional(CONF_UPDATE_INTERVAL, default='60s'): cv.update_interval,
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x39))
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID], config[CONF_UPDATE_INTERVAL])
yield cg.register_component(var, config)
yield i2c.register_i2c_device(var, config)
+374
View File
@@ -0,0 +1,374 @@
#include "apds9960.h"
#include "esphome/core/log.h"
namespace esphome {
namespace apds9960 {
static const char *TAG = "apds9960";
#define APDS9960_ERROR_CHECK(func) \
if (!func) { \
this->mark_failed(); \
return; \
}
#define APDS9960_WRITE_BYTE(reg, value) APDS9960_ERROR_CHECK(this->write_byte(reg, value));
void APDS9960::setup() {
ESP_LOGCONFIG(TAG, "Setting up APDS9960...");
uint8_t id;
if (!this->read_byte(0x92, &id)) { // ID register
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed();
return;
}
if (id != 0xAB && id != 0x9C) { // APDS9960 all should have one of these IDs
this->error_code_ = WRONG_ID;
this->mark_failed();
return;
}
// ATime (ADC integration time, 2.78ms increments, 0x81) -> 0xDB (103ms)
APDS9960_WRITE_BYTE(0x81, 0xDB);
// WTime (Wait time, 0x83) -> 0xF6 (27ms)
APDS9960_WRITE_BYTE(0x83, 0xF6);
// PPulse (0x8E) -> 0x87 (16us, 8 pulses)
APDS9960_WRITE_BYTE(0x8E, 0x87);
// POffset UR (0x9D) -> 0 (no offset)
APDS9960_WRITE_BYTE(0x9D, 0x00);
// POffset DL (0x9E) -> 0 (no offset)
APDS9960_WRITE_BYTE(0x9E, 0x00);
// Config 1 (0x8D) -> 0x60 (no wtime factor)
APDS9960_WRITE_BYTE(0x8D, 0x60);
// Control (0x8F) ->
uint8_t val = 0;
APDS9960_ERROR_CHECK(this->read_byte(0x8F, &val));
val &= 0b00111111;
uint8_t led_drive = 0; // led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA
val |= (led_drive & 0b11) << 6;
val &= 0b11110011;
uint8_t proximity_gain = 2; // proximity gain, 0 -> 1x, 1 -> 2X, 2 -> 4X, 4 -> 8X
val |= (proximity_gain & 0b11) << 2;
val &= 0b11111100;
uint8_t ambient_gain = 1; // ambient light gain, 0 -> 1x, 1 -> 4x, 2 -> 16x, 3 -> 64x
val |= (ambient_gain & 0b11) << 0;
APDS9960_WRITE_BYTE(0x8F, val);
// Pers (0x8C) -> 0x11 (2 consecutive proximity or ALS for interrupt)
APDS9960_WRITE_BYTE(0x8C, 0x11);
// Config 2 (0x90) -> 0x01 (no saturation interrupts or LED boost)
APDS9960_WRITE_BYTE(0x90, 0x01);
// Config 3 (0x9F) -> 0x00 (enable all photodiodes, no SAI)
APDS9960_WRITE_BYTE(0x9F, 0x00);
// GPenTh (0xA0, gesture enter threshold) -> 0x28 (also 0x32)
APDS9960_WRITE_BYTE(0xA0, 0x28);
// GPexTh (0xA1, gesture exit threshold) -> 0x1E
APDS9960_WRITE_BYTE(0xA1, 0x1E);
// GConf 1 (0xA2, gesture config 1) -> 0x40 (4 gesture events for interrupt (GFIFO 3), 1 for exit)
APDS9960_WRITE_BYTE(0xA2, 0x40);
// GConf 2 (0xA3, gesture config 2) ->
APDS9960_ERROR_CHECK(this->read_byte(0xA3, &val));
val &= 0b10011111;
uint8_t gesture_gain = 2; // gesture gain, 0 -> 1x, 1 -> 2x, 2 -> 4x, 3 -> 8x
val |= (gesture_gain & 0b11) << 5;
val &= 0b11100111;
uint8_t gesture_led_drive = 0; // gesture led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA
val |= (gesture_led_drive & 0b11) << 3;
val &= 0b11111000;
// gesture wait time
// 0 -> 0ms, 1 -> 2.8ms, 2 -> 5.6ms, 3 -> 8.4ms
// 4 -> 14.0ms, 5 -> 22.4 ms, 6 -> 30.8ms, 7 -> 39.2 ms
uint8_t gesture_wait_time = 1; // gesture wait time
val |= (gesture_wait_time & 0b111) << 0;
APDS9960_WRITE_BYTE(0xA3, val);
// GOffsetU (0xA4) -> 0x00 (no offset)
APDS9960_WRITE_BYTE(0xA4, 0x00);
// GOffsetD (0xA5) -> 0x00 (no offset)
APDS9960_WRITE_BYTE(0xA5, 0x00);
// GOffsetL (0xA7) -> 0x00 (no offset)
APDS9960_WRITE_BYTE(0xA7, 0x00);
// GOffsetR (0xA9) -> 0x00 (no offset)
APDS9960_WRITE_BYTE(0xA9, 0x00);
// GPulse (0xA6) -> 0xC9 (32 µs, 10 pulses)
APDS9960_WRITE_BYTE(0xA6, 0xC9);
// GConf 3 (0xAA, gesture config 3) -> 0x00 (all photodiodes active during gesture, all gesture dimensions enabled)
// 0x00 -> all dimensions, 0x01 -> up down, 0x02 -> left right
APDS9960_WRITE_BYTE(0xAA, 0x00);
// Enable (0x80) ->
val = 0;
val |= (0b1) << 0; // power on
val |= (this->is_color_enabled_() & 0b1) << 1;
val |= (this->is_proximity_enabled_() & 0b1) << 2;
val |= 0b0 << 3; // wait timer disabled
val |= 0b0 << 4; // color interrupt disabled
val |= 0b0 << 5; // proximity interrupt disabled
val |= (this->is_gesture_enabled_() & 0b1) << 6; // proximity is required for gestures
APDS9960_WRITE_BYTE(0x80, val);
}
bool APDS9960::is_color_enabled_() const {
return this->red_channel_ != nullptr || this->green_channel_ != nullptr || this->blue_channel_ != nullptr ||
this->clear_channel_ != nullptr;
}
void APDS9960::dump_config() {
ESP_LOGCONFIG(TAG, "APDS9960:");
LOG_I2C_DEVICE(this);
LOG_UPDATE_INTERVAL(this);
if (this->is_failed()) {
switch (this->error_code_) {
case COMMUNICATION_FAILED:
ESP_LOGE(TAG, "Communication with APDS9960 failed!");
break;
case WRONG_ID:
ESP_LOGE(TAG, "APDS9960 has invalid id!");
break;
default:
ESP_LOGE(TAG, "Setting up APDS9960 registers failed!");
break;
}
}
}
#define APDS9960_WARNING_CHECK(func, warning) \
if (!(func)) { \
ESP_LOGW(TAG, warning); \
this->status_set_warning(); \
return; \
}
void APDS9960::update() {
uint8_t status;
APDS9960_WARNING_CHECK(this->read_byte(0x93, &status), "Reading status bit failed.");
this->status_clear_warning();
this->read_color_data_(status);
this->read_proximity_data_(status);
}
void APDS9960::loop() { this->read_gesture_data_(); }
void APDS9960::read_color_data_(uint8_t status) {
if (!this->is_color_enabled_())
return;
if ((status & 0x01) == 0x00) {
// color data not ready yet.
return;
}
uint8_t raw[8];
APDS9960_WARNING_CHECK(this->read_bytes(0x94, raw, 8), "Reading color values failed.");
uint16_t uint_clear = (uint16_t(raw[1]) << 8) | raw[0];
uint16_t uint_red = (uint16_t(raw[3]) << 8) | raw[2];
uint16_t uint_green = (uint16_t(raw[5]) << 8) | raw[4];
uint16_t uint_blue = (uint16_t(raw[7]) << 8) | raw[6];
float clear_perc = (uint_clear / float(UINT16_MAX)) * 100.0f;
float red_perc = (uint_red / float(UINT16_MAX)) * 100.0f;
float green_perc = (uint_green / float(UINT16_MAX)) * 100.0f;
float blue_perc = (uint_blue / float(UINT16_MAX)) * 100.0f;
ESP_LOGD(TAG, "Got clear=%.1f%% red=%.1f%% green=%.1f%% blue=%.1f%%", clear_perc, red_perc, green_perc, blue_perc);
if (this->clear_channel_ != nullptr)
this->clear_channel_->publish_state(clear_perc);
if (this->red_channel_ != nullptr)
this->red_channel_->publish_state(red_perc);
if (this->green_channel_ != nullptr)
this->green_channel_->publish_state(green_perc);
if (this->blue_channel_ != nullptr)
this->blue_channel_->publish_state(blue_perc);
}
void APDS9960::read_proximity_data_(uint8_t status) {
if (this->proximity_ == nullptr)
return;
if ((status & 0b10) == 0x00) {
// proximity data not ready yet.
return;
}
uint8_t prox;
APDS9960_WARNING_CHECK(this->read_byte(0x9C, &prox), "Reading proximity values failed.");
float prox_perc = (prox / float(UINT8_MAX)) * 100.0f;
ESP_LOGD(TAG, "Got proximity=%.1f%%", prox_perc);
this->proximity_->publish_state(prox_perc);
}
void APDS9960::read_gesture_data_() {
if (!this->is_gesture_enabled_())
return;
uint8_t status;
APDS9960_WARNING_CHECK(this->read_byte(0xAF, &status), "Reading gesture status failed.");
if ((status & 0b01) == 0) {
// GVALID is false
return;
}
if ((status & 0b10) == 0b10) {
ESP_LOGV(TAG, "FIFO buffer has filled to capacity!");
}
uint8_t fifo_level;
APDS9960_WARNING_CHECK(this->read_byte(0xAE, &fifo_level), "Reading FIFO level failed.");
if (fifo_level == 0)
// no data to process
return;
APDS9960_WARNING_CHECK(fifo_level <= 32, "FIFO level has invalid value.")
uint8_t buf[128];
for (uint8_t pos = 0; pos < fifo_level * 4; pos += 32) {
// The ESP's i2c driver has a limited buffer size.
// This way of retrieving the data should be wrong according to the datasheet
// but it seems to work.
uint8_t read = std::min(32, fifo_level * 4 - pos);
APDS9960_WARNING_CHECK(this->read_bytes(0xFC + pos, buf + pos, read), "Reading FIFO buffer failed.");
}
if (millis() - this->gesture_start_ > 500) {
this->gesture_up_started_ = false;
this->gesture_down_started_ = false;
this->gesture_left_started_ = false;
this->gesture_right_started_ = false;
}
for (uint32_t i = 0; i < fifo_level * 4; i += 4) {
const int up = buf[i + 0]; // NOLINT
const int down = buf[i + 1];
const int left = buf[i + 2];
const int right = buf[i + 3];
this->process_dataset_(up, down, left, right);
}
}
void APDS9960::report_gesture_(int gesture) {
binary_sensor::BinarySensor *bin;
switch (gesture) {
case 1:
bin = this->up_direction_;
this->gesture_up_started_ = false;
this->gesture_down_started_ = false;
ESP_LOGD(TAG, "Got gesture UP");
break;
case 2:
bin = this->down_direction_;
this->gesture_up_started_ = false;
this->gesture_down_started_ = false;
ESP_LOGD(TAG, "Got gesture DOWN");
break;
case 3:
bin = this->left_direction_;
this->gesture_left_started_ = false;
this->gesture_right_started_ = false;
ESP_LOGD(TAG, "Got gesture LEFT");
break;
case 4:
bin = this->right_direction_;
this->gesture_left_started_ = false;
this->gesture_right_started_ = false;
ESP_LOGD(TAG, "Got gesture RIGHT");
break;
default:
return;
}
if (bin != nullptr) {
bin->publish_state(true);
bin->publish_state(false);
}
}
void APDS9960::process_dataset_(int up, int down, int left, int right) {
/* Algorithm: (see Figure 11 in datasheet)
*
* Observation: When a gesture is started, we will see a short amount of time where
* the photodiode in the direction of the motion has a much higher count value
* than where the gesture originates.
*
* In this algorithm we continually check the difference between the count values of opposing
* directions. For example in the down/up direction we continually look at the difference of the
* up count and down count. When DOWN gesture begins, this difference will be positive with a
* high magnitude for a short amount of time (magic value here is the difference is at least 13).
*
* If we see such a pattern, we store that we saw the first part of a gesture (the leading edge).
* After that some time can pass during which the difference is zero again (though the count values
* are not zero). At the end of a gesture, we will see this difference go into the opposite direction
* for a short period of time.
*
* If a gesture is not ended within 500 milliseconds, we consider the initial trailing edge invalid
* and reset the state.
*
* This algorithm does work, but not too well. Some good signal processing algorithms could
* probably improve this a lot, especially since the incoming signal has such a characteristic
* and quite noise-free pattern.
*/
const int up_down_delta = up - down;
const int left_right_delta = left - right;
const bool up_down_significant = abs(up_down_delta) > 13;
const bool left_right_significant = abs(left_right_delta) > 13;
if (up_down_significant) {
if (up_down_delta < 0) {
if (this->gesture_up_started_) {
// trailing edge of gesture up
this->report_gesture_(1); // UP
} else {
// leading edge of gesture down
this->gesture_down_started_ = true;
this->gesture_start_ = millis();
}
} else {
if (this->gesture_down_started_) {
// trailing edge of gesture down
this->report_gesture_(2); // DOWN
} else {
// leading edge of gesture up
this->gesture_up_started_ = true;
this->gesture_start_ = millis();
}
}
}
if (left_right_significant) {
if (left_right_delta < 0) {
if (this->gesture_left_started_) {
// trailing edge of gesture left
this->report_gesture_(3); // LEFT
} else {
// leading edge of gesture right
this->gesture_right_started_ = true;
this->gesture_start_ = millis();
}
} else {
if (this->gesture_right_started_) {
// trailing edge of gesture right
this->report_gesture_(4); // RIGHT
} else {
// leading edge of gesture left
this->gesture_left_started_ = true;
this->gesture_start_ = millis();
}
}
}
}
float APDS9960::get_setup_priority() const { return setup_priority::DATA; }
bool APDS9960::is_proximity_enabled_() const { return this->proximity_ != nullptr || this->is_gesture_enabled_(); }
bool APDS9960::is_gesture_enabled_() const {
return this->up_direction_ != nullptr || this->left_direction_ != nullptr || this->down_direction_ != nullptr ||
this->right_direction_ != nullptr;
}
} // namespace apds9960
} // namespace esphome
+62
View File
@@ -0,0 +1,62 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
namespace esphome {
namespace apds9960 {
class APDS9960 : public PollingComponent, public i2c::I2CDevice {
public:
APDS9960(uint32_t update_interval) : PollingComponent(update_interval) {}
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void loop() override;
void set_red_channel(sensor::Sensor *red_channel) { red_channel_ = red_channel; }
void set_green_channel(sensor::Sensor *green_channel) { green_channel_ = green_channel; }
void set_blue_channel(sensor::Sensor *blue_channel) { blue_channel_ = blue_channel; }
void set_clear_channel(sensor::Sensor *clear_channel) { clear_channel_ = clear_channel; }
void set_up_direction(binary_sensor::BinarySensor *up_direction) { up_direction_ = up_direction; }
void set_right_direction(binary_sensor::BinarySensor *right_direction) { right_direction_ = right_direction; }
void set_down_direction(binary_sensor::BinarySensor *down_direction) { down_direction_ = down_direction; }
void set_left_direction(binary_sensor::BinarySensor *left_direction) { left_direction_ = left_direction; }
void set_proximity(sensor::Sensor *proximity) { proximity_ = proximity; }
protected:
bool is_color_enabled_() const;
bool is_proximity_enabled_() const;
bool is_gesture_enabled_() const;
void read_color_data_(uint8_t status);
void read_proximity_data_(uint8_t status);
void read_gesture_data_();
void report_gesture_(int gesture);
void process_dataset_(int up, int down, int left, int right);
sensor::Sensor *red_channel_{nullptr};
sensor::Sensor *green_channel_{nullptr};
sensor::Sensor *blue_channel_{nullptr};
sensor::Sensor *clear_channel_{nullptr};
binary_sensor::BinarySensor *up_direction_{nullptr};
binary_sensor::BinarySensor *right_direction_{nullptr};
binary_sensor::BinarySensor *down_direction_{nullptr};
binary_sensor::BinarySensor *left_direction_{nullptr};
sensor::Sensor *proximity_{nullptr};
enum ErrorCode {
NONE = 0,
COMMUNICATION_FAILED,
WRONG_ID,
} error_code_{NONE};
bool gesture_up_started_{false};
bool gesture_down_started_{false};
bool gesture_left_started_{false};
bool gesture_right_started_{false};
uint32_t gesture_start_{0};
};
} // namespace apds9960
} // namespace esphome
@@ -0,0 +1,27 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import CONF_DIRECTION, CONF_DEVICE_CLASS, DEVICE_CLASS_MOVING
from . import APDS9960, CONF_APDS9960_ID
DEPENDENCIES = ['apds9960']
DIRECTIONS = {
'UP': 'set_up_direction',
'DOWN': 'set_down_direction',
'LEFT': 'set_left_direction',
'RIGHT': 'set_right_direction',
}
CONFIG_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_SCHEMA.extend({
cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True),
cv.GenerateID(CONF_APDS9960_ID): cv.use_variable_id(APDS9960),
cv.Optional(CONF_DEVICE_CLASS, default=DEVICE_CLASS_MOVING): binary_sensor.device_class,
}))
def to_code(config):
hub = yield cg.get_variable(config[CONF_APDS9960_ID])
var = yield binary_sensor.new_binary_sensor(config)
func = getattr(hub, DIRECTIONS[config[CONF_DIRECTION]])
cg.add(func(var))
+32
View File
@@ -0,0 +1,32 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import CONF_TYPE, CONF_UNIT_OF_MEASUREMENT, CONF_ACCURACY_DECIMALS, CONF_ICON, \
UNIT_PERCENT, ICON_LIGHTBULB
from . import APDS9960, CONF_APDS9960_ID
DEPENDENCIES = ['apds9960']
TYPES = {
'CLEAR': 'set_clear_channel',
'RED': 'set_red_channel',
'GREEN': 'set_green_channel',
'BLUE': 'set_blue_channel',
'PROXIMITY': 'set_proximity',
}
CONFIG_SCHEMA = cv.nameable(sensor.SENSOR_SCHEMA.extend({
cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
cv.GenerateID(CONF_APDS9960_ID): cv.use_variable_id(APDS9960),
cv.Optional(CONF_UNIT_OF_MEASUREMENT, default=UNIT_PERCENT): sensor.unit_of_measurement,
cv.Optional(CONF_ACCURACY_DECIMALS, default=1): sensor.accuracy_decimals,
cv.Optional(CONF_ICON, default=ICON_LIGHTBULB): sensor.icon,
}))
def to_code(config):
hub = yield cg.get_variable(config[CONF_APDS9960_ID])
var = yield sensor.new_sensor(config)
func = getattr(hub, TYPES[config[CONF_TYPE]])
cg.add(func(var))
@@ -1,24 +1,20 @@
import voluptuous as vol
from esphome import automation from esphome 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
+87
View File
@@ -0,0 +1,87 @@
#include "api_message.h"
#include "esphome/core/log.h"
namespace esphome {
namespace api {
static const char *TAG = "api.message";
bool APIMessage::decode_varint(uint32_t field_id, uint32_t value) { return false; }
bool APIMessage::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) { return false; }
bool APIMessage::decode_32bit(uint32_t field_id, uint32_t value) { return false; }
void APIMessage::encode(APIBuffer &buffer) {}
void APIMessage::decode(const uint8_t *buffer, size_t length) {
uint32_t i = 0;
bool error = false;
while (i < length) {
uint32_t consumed;
auto res = proto_decode_varuint32(&buffer[i], length - i, &consumed);
if (!res.has_value()) {
ESP_LOGV(TAG, "Invalid field start at %u", i);
break;
}
uint32_t field_type = (*res) & 0b111;
uint32_t field_id = (*res) >> 3;
i += consumed;
switch (field_type) {
case 0: { // VarInt
res = proto_decode_varuint32(&buffer[i], length - i, &consumed);
if (!res.has_value()) {
ESP_LOGV(TAG, "Invalid VarInt at %u", i);
error = true;
break;
}
if (!this->decode_varint(field_id, *res)) {
ESP_LOGV(TAG, "Cannot decode VarInt field %u with value %u!", field_id, *res);
}
i += consumed;
break;
}
case 2: { // Length-delimited
res = proto_decode_varuint32(&buffer[i], length - i, &consumed);
if (!res.has_value()) {
ESP_LOGV(TAG, "Invalid Length Delimited at %u", i);
error = true;
break;
}
i += consumed;
if (*res > length - i) {
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %u", i);
error = true;
break;
}
if (!this->decode_length_delimited(field_id, &buffer[i], *res)) {
ESP_LOGV(TAG, "Cannot decode Length Delimited field %u!", field_id);
}
i += *res;
break;
}
case 5: { // 32-bit
if (length - i < 4) {
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %u", i);
error = true;
break;
}
uint32_t val = (uint32_t(buffer[i]) << 0) | (uint32_t(buffer[i + 1]) << 8) | (uint32_t(buffer[i + 2]) << 16) |
(uint32_t(buffer[i + 3]) << 24);
if (!this->decode_32bit(field_id, val)) {
ESP_LOGV(TAG, "Cannot decode 32-bit field %u with value %u!", field_id, val);
}
i += 4;
break;
}
default:
ESP_LOGV(TAG, "Invalid field type at %u", i);
error = true;
break;
}
if (error) {
break;
}
}
}
} // namespace api
} // namespace esphome
+79
View File
@@ -0,0 +1,79 @@
#pragma once
#include "esphome/core/component.h"
#include "util.h"
namespace esphome {
namespace api {
enum class APIMessageType {
HELLO_REQUEST = 1,
HELLO_RESPONSE = 2,
CONNECT_REQUEST = 3,
CONNECT_RESPONSE = 4,
DISCONNECT_REQUEST = 5,
DISCONNECT_RESPONSE = 6,
PING_REQUEST = 7,
PING_RESPONSE = 8,
DEVICE_INFO_REQUEST = 9,
DEVICE_INFO_RESPONSE = 10,
LIST_ENTITIES_REQUEST = 11,
LIST_ENTITIES_BINARY_SENSOR_RESPONSE = 12,
LIST_ENTITIES_COVER_RESPONSE = 13,
LIST_ENTITIES_FAN_RESPONSE = 14,
LIST_ENTITIES_LIGHT_RESPONSE = 15,
LIST_ENTITIES_SENSOR_RESPONSE = 16,
LIST_ENTITIES_SWITCH_RESPONSE = 17,
LIST_ENTITIES_TEXT_SENSOR_RESPONSE = 18,
LIST_ENTITIES_SERVICE_RESPONSE = 41,
LIST_ENTITIES_CAMERA_RESPONSE = 43,
LIST_ENTITIES_CLIMATE_RESPONSE = 46,
LIST_ENTITIES_DONE_RESPONSE = 19,
SUBSCRIBE_STATES_REQUEST = 20,
BINARY_SENSOR_STATE_RESPONSE = 21,
COVER_STATE_RESPONSE = 22,
FAN_STATE_RESPONSE = 23,
LIGHT_STATE_RESPONSE = 24,
SENSOR_STATE_RESPONSE = 25,
SWITCH_STATE_RESPONSE = 26,
TEXT_SENSOR_STATE_RESPONSE = 27,
CAMERA_IMAGE_RESPONSE = 44,
CLIMATE_STATE_RESPONSE = 47,
SUBSCRIBE_LOGS_REQUEST = 28,
SUBSCRIBE_LOGS_RESPONSE = 29,
COVER_COMMAND_REQUEST = 30,
FAN_COMMAND_REQUEST = 31,
LIGHT_COMMAND_REQUEST = 32,
SWITCH_COMMAND_REQUEST = 33,
CAMERA_IMAGE_REQUEST = 45,
CLIMATE_COMMAND_REQUEST = 48,
SUBSCRIBE_SERVICE_CALLS_REQUEST = 34,
SERVICE_CALL_RESPONSE = 35,
GET_TIME_REQUEST = 36,
GET_TIME_RESPONSE = 37,
SUBSCRIBE_HOME_ASSISTANT_STATES_REQUEST = 38,
SUBSCRIBE_HOME_ASSISTANT_STATE_RESPONSE = 39,
HOME_ASSISTANT_STATE_RESPONSE = 40,
EXECUTE_SERVICE_REQUEST = 42,
};
class APIMessage {
public:
void decode(const uint8_t *buffer, size_t length);
virtual bool decode_varint(uint32_t field_id, uint32_t value);
virtual bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len);
virtual bool decode_32bit(uint32_t field_id, uint32_t value);
virtual APIMessageType message_type() const = 0;
virtual void encode(APIBuffer &buffer);
};
} // namespace api
} // namespace esphome
File diff suppressed because it is too large Load Diff
+245
View File
@@ -0,0 +1,245 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/controller.h"
#include "esphome/core/defines.h"
#include "esphome/core/log.h"
#include "util.h"
#include "api_message.h"
#include "basic_messages.h"
#include "list_entities.h"
#include "subscribe_state.h"
#include "subscribe_logs.h"
#include "command_messages.h"
#include "service_call_message.h"
#include "user_services.h"
#ifdef ARDUINO_ARCH_ESP32
#include <AsyncTCP.h>
#endif
#ifdef ARDUINO_ARCH_ESP8266
#include <ESPAsyncTCP.h>
#endif
namespace esphome {
namespace api {
class APIServer;
class APIConnection {
public:
APIConnection(AsyncClient *client, APIServer *parent);
~APIConnection();
void disconnect_client();
APIBuffer get_buffer();
bool send_buffer(APIMessageType type);
bool send_message(APIMessage &msg);
bool send_empty_message(APIMessageType type);
void loop();
#ifdef USE_BINARY_SENSOR
bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state);
#endif
#ifdef USE_COVER
bool send_cover_state(cover::Cover *cover);
#endif
#ifdef USE_FAN
bool send_fan_state(fan::FanState *fan);
#endif
#ifdef USE_LIGHT
bool send_light_state(light::LightState *light);
#endif
#ifdef USE_SENSOR
bool send_sensor_state(sensor::Sensor *sensor, float state);
#endif
#ifdef USE_SWITCH
bool send_switch_state(switch_::Switch *a_switch, bool state);
#endif
#ifdef USE_TEXT_SENSOR
bool send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state);
#endif
#ifdef USE_ESP32_CAMERA
void send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image);
#endif
#ifdef USE_CLIMATE
bool send_climate_state(climate::Climate *climate);
#endif
bool send_log_message(int level, const char *tag, const char *line);
bool send_disconnect_request();
bool send_ping_request();
void send_service_call(ServiceCallResponse &call);
#ifdef USE_HOMEASSISTANT_TIME
void send_time_request();
#endif
protected:
friend APIServer;
void on_error_(int8_t error);
void on_disconnect_();
void on_timeout_(uint32_t time);
void on_data_(uint8_t *buf, size_t len);
void fatal_error_();
bool valid_rx_message_type_(uint32_t msg_type);
void read_message_(uint32_t size, uint32_t type, uint8_t *msg);
void parse_recv_buffer_();
// request types
void on_hello_request_(const HelloRequest &req);
void on_connect_request_(const ConnectRequest &req);
void on_disconnect_request_(const DisconnectRequest &req);
void on_disconnect_response_(const DisconnectResponse &req);
void on_ping_request_(const PingRequest &req);
void on_ping_response_(const PingResponse &req);
void on_device_info_request_(const DeviceInfoRequest &req);
void on_list_entities_request_(const ListEntitiesRequest &req);
void on_subscribe_states_request_(const SubscribeStatesRequest &req);
void on_subscribe_logs_request_(const SubscribeLogsRequest &req);
#ifdef USE_COVER
void on_cover_command_request_(const CoverCommandRequest &req);
#endif
#ifdef USE_FAN
void on_fan_command_request_(const FanCommandRequest &req);
#endif
#ifdef USE_LIGHT
void on_light_command_request_(const LightCommandRequest &req);
#endif
#ifdef USE_SWITCH
void on_switch_command_request_(const SwitchCommandRequest &req);
#endif
#ifdef USE_CLIMATE
void on_climate_command_request_(const ClimateCommandRequest &req);
#endif
void on_subscribe_service_calls_request_(const SubscribeServiceCallsRequest &req);
void on_subscribe_home_assistant_states_request_(const SubscribeHomeAssistantStatesRequest &req);
void on_home_assistant_state_response_(const HomeAssistantStateResponse &req);
void on_execute_service_(const ExecuteServiceRequest &req);
#ifdef USE_ESP32_CAMERA
void on_camera_image_request_(const CameraImageRequest &req);
#endif
enum class ConnectionState {
WAITING_FOR_HELLO,
WAITING_FOR_CONNECT,
CONNECTED,
} connection_state_{ConnectionState::WAITING_FOR_HELLO};
bool remove_{false};
std::vector<uint8_t> send_buffer_;
std::vector<uint8_t> recv_buffer_;
std::string client_info_;
#ifdef USE_ESP32_CAMERA
esp32_camera::CameraImageReader image_reader_;
#endif
bool state_subscription_{false};
int log_subscription_{ESPHOME_LOG_LEVEL_NONE};
uint32_t last_traffic_;
bool sent_ping_{false};
bool service_call_subscription_{false};
AsyncClient *client_;
APIServer *parent_;
InitialStateIterator initial_state_iterator_;
ListEntitiesIterator list_entities_iterator_;
};
template<typename... Ts> class HomeAssistantServiceCallAction;
class APIServer : public Component, public Controller {
public:
APIServer();
void setup() override;
uint16_t get_port() const;
float get_setup_priority() const override;
void loop() override;
void dump_config() override;
void on_shutdown() override;
bool check_password(const std::string &password) const;
bool uses_password() const;
void set_port(uint16_t port);
void set_password(const std::string &password);
void set_reboot_timeout(uint32_t reboot_timeout);
void handle_disconnect(APIConnection *conn);
#ifdef USE_BINARY_SENSOR
void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override;
#endif
#ifdef USE_COVER
void on_cover_update(cover::Cover *obj) override;
#endif
#ifdef USE_FAN
void on_fan_update(fan::FanState *obj) override;
#endif
#ifdef USE_LIGHT
void on_light_update(light::LightState *obj) override;
#endif
#ifdef USE_SENSOR
void on_sensor_update(sensor::Sensor *obj, float state) override;
#endif
#ifdef USE_SWITCH
void on_switch_update(switch_::Switch *obj, bool state) override;
#endif
#ifdef USE_TEXT_SENSOR
void on_text_sensor_update(text_sensor::TextSensor *obj, std::string state) override;
#endif
#ifdef USE_CLIMATE
void on_climate_update(climate::Climate *obj) override;
#endif
void send_service_call(ServiceCallResponse &call);
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
#ifdef USE_HOMEASSISTANT_TIME
void request_time();
#endif
bool is_connected() const;
struct HomeAssistantStateSubscription {
std::string entity_id;
std::function<void(std::string)> callback;
};
void subscribe_home_assistant_state(std::string entity_id, std::function<void(std::string)> f);
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }
protected:
AsyncServer server_{0};
uint16_t port_{6053};
uint32_t reboot_timeout_{300000};
uint32_t last_connected_{0};
std::vector<APIConnection *> clients_;
std::string password_;
std::vector<HomeAssistantStateSubscription> state_subs_;
std::vector<UserServiceDescriptor *> user_services_;
};
extern APIServer *global_api_server;
template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts...> {
public:
explicit HomeAssistantServiceCallAction(APIServer *parent) : parent_(parent) {}
void set_service(const std::string &service) { this->resp_.set_service(service); }
void set_data(const std::vector<KeyValuePair> &data) { this->resp_.set_data(data); }
void set_data_template(const std::vector<KeyValuePair> &data_template) {
this->resp_.set_data_template(data_template);
}
void set_variables(const std::vector<TemplatableKeyValuePair> &variables) { this->resp_.set_variables(variables); }
void play(Ts... x) override {
this->parent_->send_service_call(this->resp_);
this->play_next(x...);
}
protected:
APIServer *parent_;
ServiceCallResponse resp_;
};
template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> {
public:
bool check(Ts... x) override { return global_api_server->is_connected(); }
};
} // namespace api
} // namespace esphome
+57
View File
@@ -0,0 +1,57 @@
#include "basic_messages.h"
#include "esphome/core/log.h"
namespace esphome {
namespace api {
// Hello
bool HelloRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
switch (field_id) {
case 1: // string client_info = 1;
this->client_info_ = as_string(value, len);
return true;
default:
return false;
}
}
const std::string &HelloRequest::get_client_info() const { return this->client_info_; }
void HelloRequest::set_client_info(const std::string &client_info) { this->client_info_ = client_info; }
APIMessageType HelloRequest::message_type() const { return APIMessageType::HELLO_REQUEST; }
// Connect
bool ConnectRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
switch (field_id) {
case 1: // string password = 1;
this->password_ = as_string(value, len);
return true;
default:
return false;
}
}
const std::string &ConnectRequest::get_password() const { return this->password_; }
void ConnectRequest::set_password(const std::string &password) { this->password_ = password; }
APIMessageType ConnectRequest::message_type() const { return APIMessageType::CONNECT_REQUEST; }
APIMessageType DeviceInfoRequest::message_type() const { return APIMessageType::DEVICE_INFO_REQUEST; }
APIMessageType DisconnectRequest::message_type() const { return APIMessageType::DISCONNECT_REQUEST; }
bool DisconnectRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
switch (field_id) {
case 1: // string reason = 1;
this->reason_ = as_string(value, len);
return true;
default:
return false;
}
}
const std::string &DisconnectRequest::get_reason() const { return this->reason_; }
void DisconnectRequest::set_reason(const std::string &reason) { this->reason_ = reason; }
void DisconnectRequest::encode(APIBuffer &buffer) {
// string reason = 1;
buffer.encode_string(1, this->reason_);
}
APIMessageType DisconnectResponse::message_type() const { return APIMessageType::DISCONNECT_RESPONSE; }
APIMessageType PingRequest::message_type() const { return APIMessageType::PING_REQUEST; }
APIMessageType PingResponse::message_type() const { return APIMessageType::PING_RESPONSE; }
} // namespace api
} // namespace esphome
+63
View File
@@ -0,0 +1,63 @@
#pragma once
#include "api_message.h"
namespace esphome {
namespace api {
class HelloRequest : public APIMessage {
public:
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
const std::string &get_client_info() const;
void set_client_info(const std::string &client_info);
APIMessageType message_type() const override;
protected:
std::string client_info_;
};
class ConnectRequest : public APIMessage {
public:
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
const std::string &get_password() const;
void set_password(const std::string &password);
APIMessageType message_type() const override;
protected:
std::string password_;
};
class DeviceInfoRequest : public APIMessage {
public:
APIMessageType message_type() const override;
};
class DisconnectRequest : public APIMessage {
public:
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
void encode(APIBuffer &buffer) override;
APIMessageType message_type() const override;
const std::string &get_reason() const;
void set_reason(const std::string &reason);
protected:
std::string reason_;
};
class DisconnectResponse : public APIMessage {
public:
APIMessageType message_type() const override;
};
class PingRequest : public APIMessage {
public:
APIMessageType message_type() const override;
};
class PingResponse : public APIMessage {
public:
APIMessageType message_type() const override;
};
} // namespace api
} // namespace esphome
+417
View File
@@ -0,0 +1,417 @@
#include "command_messages.h"
#include "esphome/core/log.h"
namespace esphome {
namespace api {
#ifdef USE_COVER
bool CoverCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 2:
// bool has_legacy_command = 2;
this->has_legacy_command_ = value;
return true;
case 3:
// enum LegacyCoverCommand {
// OPEN = 0;
// CLOSE = 1;
// STOP = 2;
// }
// LegacyCoverCommand legacy_command_ = 3;
this->legacy_command_ = static_cast<LegacyCoverCommand>(value);
return true;
case 4:
// bool has_position = 4;
this->has_position_ = value;
return true;
case 6:
// bool has_tilt = 6;
this->has_tilt_ = value;
return true;
case 8:
// bool stop = 8;
this->stop_ = value;
default:
return false;
}
}
bool CoverCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 1:
// fixed32 key = 1;
this->key_ = value;
return true;
case 5:
// float position = 5;
this->position_ = as_float(value);
return true;
case 7:
// float tilt = 7;
this->tilt_ = as_float(value);
return true;
default:
return false;
}
}
APIMessageType CoverCommandRequest::message_type() const { return APIMessageType ::COVER_COMMAND_REQUEST; }
uint32_t CoverCommandRequest::get_key() const { return this->key_; }
optional<LegacyCoverCommand> CoverCommandRequest::get_legacy_command() const {
if (!this->has_legacy_command_)
return {};
return this->legacy_command_;
}
optional<float> CoverCommandRequest::get_position() const {
if (!this->has_position_)
return {};
return this->position_;
}
optional<float> CoverCommandRequest::get_tilt() const {
if (!this->has_tilt_)
return {};
return this->tilt_;
}
#endif
#ifdef USE_FAN
bool FanCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 2:
// bool has_state = 2;
this->has_state_ = value;
return true;
case 3:
// bool state = 3;
this->state_ = value;
return true;
case 4:
// bool has_speed = 4;
this->has_speed_ = value;
return true;
case 5:
// FanSpeed speed = 5;
this->speed_ = static_cast<fan::FanSpeed>(value);
return true;
case 6:
// bool has_oscillating = 6;
this->has_oscillating_ = value;
return true;
case 7:
// bool oscillating = 7;
this->oscillating_ = value;
return true;
default:
return false;
}
}
bool FanCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 1:
// fixed32 key = 1;
this->key_ = value;
return true;
default:
return false;
}
}
APIMessageType FanCommandRequest::message_type() const { return APIMessageType::FAN_COMMAND_REQUEST; }
uint32_t FanCommandRequest::get_key() const { return this->key_; }
optional<bool> FanCommandRequest::get_state() const {
if (!this->has_state_)
return {};
return this->state_;
}
optional<fan::FanSpeed> FanCommandRequest::get_speed() const {
if (!this->has_speed_)
return {};
return this->speed_;
}
optional<bool> FanCommandRequest::get_oscillating() const {
if (!this->has_oscillating_)
return {};
return this->oscillating_;
}
#endif
#ifdef USE_LIGHT
bool LightCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 2:
// bool has_state = 2;
this->has_state_ = value;
return true;
case 3:
// bool state = 3;
this->state_ = value;
return true;
case 4:
// bool has_brightness = 4;
this->has_brightness_ = value;
return true;
case 6:
// bool has_rgb = 6;
this->has_rgb_ = value;
return true;
case 10:
// bool has_white = 10;
this->has_white_ = value;
return true;
case 12:
// bool has_color_temperature = 12;
this->has_color_temperature_ = value;
return true;
case 14:
// bool has_transition_length = 14;
this->has_transition_length_ = value;
return true;
case 15:
// uint32 transition_length = 15;
this->transition_length_ = value;
return true;
case 16:
// bool has_flash_length = 16;
this->has_flash_length_ = value;
return true;
case 17:
// uint32 flash_length = 17;
this->flash_length_ = value;
return true;
case 18:
// bool has_effect = 18;
this->has_effect_ = value;
return true;
default:
return false;
}
}
bool LightCommandRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
switch (field_id) {
case 19:
// string effect = 19;
this->effect_ = as_string(value, len);
return true;
default:
return false;
}
}
bool LightCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 1:
// fixed32 key = 1;
this->key_ = value;
return true;
case 5:
// float brightness = 5;
this->brightness_ = as_float(value);
return true;
case 7:
// float red = 7;
this->red_ = as_float(value);
return true;
case 8:
// float green = 8;
this->green_ = as_float(value);
return true;
case 9:
// float blue = 9;
this->blue_ = as_float(value);
return true;
case 11:
// float white = 11;
this->white_ = as_float(value);
return true;
case 13:
// float color_temperature = 13;
this->color_temperature_ = as_float(value);
return true;
default:
return false;
}
}
APIMessageType LightCommandRequest::message_type() const { return APIMessageType::LIGHT_COMMAND_REQUEST; }
uint32_t LightCommandRequest::get_key() const { return this->key_; }
optional<bool> LightCommandRequest::get_state() const {
if (!this->has_state_)
return {};
return this->state_;
}
optional<float> LightCommandRequest::get_brightness() const {
if (!this->has_brightness_)
return {};
return this->brightness_;
}
optional<float> LightCommandRequest::get_red() const {
if (!this->has_rgb_)
return {};
return this->red_;
}
optional<float> LightCommandRequest::get_green() const {
if (!this->has_rgb_)
return {};
return this->green_;
}
optional<float> LightCommandRequest::get_blue() const {
if (!this->has_rgb_)
return {};
return this->blue_;
}
optional<float> LightCommandRequest::get_white() const {
if (!this->has_white_)
return {};
return this->white_;
}
optional<float> LightCommandRequest::get_color_temperature() const {
if (!this->has_color_temperature_)
return {};
return this->color_temperature_;
}
optional<uint32_t> LightCommandRequest::get_transition_length() const {
if (!this->has_transition_length_)
return {};
return this->transition_length_;
}
optional<uint32_t> LightCommandRequest::get_flash_length() const {
if (!this->has_flash_length_)
return {};
return this->flash_length_;
}
optional<std::string> LightCommandRequest::get_effect() const {
if (!this->has_effect_)
return {};
return this->effect_;
}
#endif
#ifdef USE_SWITCH
bool SwitchCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 2:
// bool state = 2;
this->state_ = value;
return true;
default:
return false;
}
}
bool SwitchCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 1:
// fixed32 key = 1;
this->key_ = value;
return true;
default:
return false;
}
}
APIMessageType SwitchCommandRequest::message_type() const { return APIMessageType::SWITCH_COMMAND_REQUEST; }
uint32_t SwitchCommandRequest::get_key() const { return this->key_; }
bool SwitchCommandRequest::get_state() const { return this->state_; }
#endif
#ifdef USE_ESP32_CAMERA
bool CameraImageRequest::get_single() const { return this->single_; }
bool CameraImageRequest::get_stream() const { return this->stream_; }
bool CameraImageRequest::decode_varint(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 1:
// bool single = 1;
this->single_ = value;
return true;
case 2:
// bool stream = 2;
this->stream_ = value;
return true;
default:
return false;
}
}
APIMessageType CameraImageRequest::message_type() const { return APIMessageType::CAMERA_IMAGE_REQUEST; }
#endif
#ifdef USE_CLIMATE
bool ClimateCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 2:
// bool has_mode = 2;
this->has_mode_ = value;
return true;
case 3:
// ClimateMode mode = 3;
this->mode_ = static_cast<climate::ClimateMode>(value);
return true;
case 4:
// bool has_target_temperature = 4;
this->has_target_temperature_ = value;
return true;
case 6:
// bool has_target_temperature_low = 6;
this->has_target_temperature_low_ = value;
return true;
case 8:
// bool has_target_temperature_high = 8;
this->has_target_temperature_high_ = value;
return true;
case 10:
// bool has_away = 10;
this->has_away_ = value;
return true;
case 11:
// bool away = 11;
this->away_ = value;
return true;
default:
return false;
}
}
bool ClimateCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 1:
// fixed32 key = 1;
this->key_ = value;
return true;
case 5:
// float target_temperature = 5;
this->target_temperature_ = as_float(value);
return true;
case 7:
// float target_temperature_low = 7;
this->target_temperature_low_ = as_float(value);
return true;
case 9:
// float target_temperature_high = 9;
this->target_temperature_high_ = as_float(value);
return true;
default:
return false;
}
}
APIMessageType ClimateCommandRequest::message_type() const { return APIMessageType::CLIMATE_COMMAND_REQUEST; }
uint32_t ClimateCommandRequest::get_key() const { return this->key_; }
optional<climate::ClimateMode> ClimateCommandRequest::get_mode() const {
if (!this->has_mode_)
return {};
return this->mode_;
}
optional<float> ClimateCommandRequest::get_target_temperature() const {
if (!this->has_target_temperature_)
return {};
return this->target_temperature_;
}
optional<float> ClimateCommandRequest::get_target_temperature_low() const {
if (!this->has_target_temperature_low_)
return {};
return this->target_temperature_low_;
}
optional<float> ClimateCommandRequest::get_target_temperature_high() const {
if (!this->has_target_temperature_high_)
return {};
return this->target_temperature_high_;
}
optional<bool> ClimateCommandRequest::get_away() const {
if (!this->has_away_)
return {};
return this->away_;
}
#endif
} // namespace api
} // namespace esphome
+162
View File
@@ -0,0 +1,162 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "api_message.h"
namespace esphome {
namespace api {
#ifdef USE_COVER
enum LegacyCoverCommand {
LEGACY_COVER_COMMAND_OPEN = 0,
LEGACY_COVER_COMMAND_CLOSE = 1,
LEGACY_COVER_COMMAND_STOP = 2,
};
class CoverCommandRequest : public APIMessage {
public:
bool decode_varint(uint32_t field_id, uint32_t value) override;
bool decode_32bit(uint32_t field_id, uint32_t value) override;
APIMessageType message_type() const override;
uint32_t get_key() const;
optional<LegacyCoverCommand> get_legacy_command() const;
optional<float> get_position() const;
optional<float> get_tilt() const;
bool get_stop() const { return this->stop_; }
protected:
uint32_t key_{0};
bool has_legacy_command_{false};
LegacyCoverCommand legacy_command_{LEGACY_COVER_COMMAND_OPEN};
bool has_position_{false};
float position_{0.0f};
bool has_tilt_{false};
float tilt_{0.0f};
bool stop_{false};
};
#endif
#ifdef USE_FAN
class FanCommandRequest : public APIMessage {
public:
bool decode_varint(uint32_t field_id, uint32_t value) override;
bool decode_32bit(uint32_t field_id, uint32_t value) override;
APIMessageType message_type() const override;
uint32_t get_key() const;
optional<bool> get_state() const;
optional<fan::FanSpeed> get_speed() const;
optional<bool> get_oscillating() const;
protected:
uint32_t key_{0};
bool has_state_{false};
bool state_{false};
bool has_speed_{false};
fan::FanSpeed speed_{fan::FAN_SPEED_LOW};
bool has_oscillating_{false};
bool oscillating_{false};
};
#endif
#ifdef USE_LIGHT
class LightCommandRequest : public APIMessage {
public:
bool decode_varint(uint32_t field_id, uint32_t value) override;
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
bool decode_32bit(uint32_t field_id, uint32_t value) override;
APIMessageType message_type() const override;
uint32_t get_key() const;
optional<bool> get_state() const;
optional<float> get_brightness() const;
optional<float> get_red() const;
optional<float> get_green() const;
optional<float> get_blue() const;
optional<float> get_white() const;
optional<float> get_color_temperature() const;
optional<uint32_t> get_transition_length() const;
optional<uint32_t> get_flash_length() const;
optional<std::string> get_effect() const;
protected:
uint32_t key_{0};
bool has_state_{false};
bool state_{false};
bool has_brightness_{false};
float brightness_{0.0f};
bool has_rgb_{false};
float red_{0.0f};
float green_{0.0f};
float blue_{0.0f};
bool has_white_{false};
float white_{0.0f};
bool has_color_temperature_{false};
float color_temperature_{0.0f};
bool has_transition_length_{false};
uint32_t transition_length_{0};
bool has_flash_length_{false};
uint32_t flash_length_{0};
bool has_effect_{false};
std::string effect_{};
};
#endif
#ifdef USE_SWITCH
class SwitchCommandRequest : public APIMessage {
public:
bool decode_varint(uint32_t field_id, uint32_t value) override;
bool decode_32bit(uint32_t field_id, uint32_t value) override;
APIMessageType message_type() const override;
uint32_t get_key() const;
bool get_state() const;
protected:
uint32_t key_{0};
bool state_{false};
};
#endif
#ifdef USE_ESP32_CAMERA
class CameraImageRequest : public APIMessage {
public:
bool decode_varint(uint32_t field_id, uint32_t value) override;
bool get_single() const;
bool get_stream() const;
APIMessageType message_type() const override;
protected:
bool single_{false};
bool stream_{false};
};
#endif
#ifdef USE_CLIMATE
class ClimateCommandRequest : public APIMessage {
public:
bool decode_varint(uint32_t field_id, uint32_t value) override;
bool decode_32bit(uint32_t field_id, uint32_t value) override;
APIMessageType message_type() const override;
uint32_t get_key() const;
optional<climate::ClimateMode> get_mode() const;
optional<float> get_target_temperature() const;
optional<float> get_target_temperature_low() const;
optional<float> get_target_temperature_high() const;
optional<bool> get_away() const;
protected:
uint32_t key_{0};
bool has_mode_{false};
climate::ClimateMode mode_{climate::CLIMATE_MODE_OFF};
bool has_target_temperature_{false};
float target_temperature_{0.0f};
bool has_target_temperature_low_{false};
float target_temperature_low_{0.0f};
bool has_target_temperature_high_{false};
float target_temperature_high_{0.0f};
bool has_away_{false};
bool away_{false};
};
#endif
} // namespace api
} // namespace esphome
+190
View File
@@ -0,0 +1,190 @@
#include "list_entities.h"
#include "esphome/core/util.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace api {
std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) {
return App.get_name() + component_type + nameable->get_object_id();
}
#ifdef USE_BINARY_SENSOR
bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
auto buffer = this->client_->get_buffer();
buffer.encode_nameable(binary_sensor);
// string unique_id = 4;
buffer.encode_string(4, get_default_unique_id("binary_sensor", binary_sensor));
// string device_class = 5;
buffer.encode_string(5, binary_sensor->get_device_class());
// bool is_status_binary_sensor = 6;
buffer.encode_bool(6, binary_sensor->is_status_binary_sensor());
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_BINARY_SENSOR_RESPONSE);
}
#endif
#ifdef USE_COVER
bool ListEntitiesIterator::on_cover(cover::Cover *cover) {
auto buffer = this->client_->get_buffer();
buffer.encode_nameable(cover);
// string unique_id = 4;
buffer.encode_string(4, get_default_unique_id("cover", cover));
auto traits = cover->get_traits();
// bool assumed_state = 5;
buffer.encode_bool(5, traits.get_is_assumed_state());
// bool supports_position = 6;
buffer.encode_bool(6, traits.get_supports_position());
// bool supports_tilt = 7;
buffer.encode_bool(7, traits.get_supports_tilt());
// string device_class = 8;
buffer.encode_string(8, cover->get_device_class());
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_COVER_RESPONSE);
}
#endif
#ifdef USE_FAN
bool ListEntitiesIterator::on_fan(fan::FanState *fan) {
auto buffer = this->client_->get_buffer();
buffer.encode_nameable(fan);
// string unique_id = 4;
buffer.encode_string(4, get_default_unique_id("fan", fan));
// bool supports_oscillation = 5;
buffer.encode_bool(5, fan->get_traits().supports_oscillation());
// bool supports_speed = 6;
buffer.encode_bool(6, fan->get_traits().supports_speed());
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_FAN_RESPONSE);
}
#endif
#ifdef USE_LIGHT
bool ListEntitiesIterator::on_light(light::LightState *light) {
auto buffer = this->client_->get_buffer();
buffer.encode_nameable(light);
// string unique_id = 4;
buffer.encode_string(4, get_default_unique_id("light", light));
// bool supports_brightness = 5;
auto traits = light->get_traits();
buffer.encode_bool(5, traits.get_supports_brightness());
// bool supports_rgb = 6;
buffer.encode_bool(6, traits.get_supports_rgb());
// bool supports_white_value = 7;
buffer.encode_bool(7, traits.get_supports_rgb_white_value());
// bool supports_color_temperature = 8;
buffer.encode_bool(8, traits.get_supports_color_temperature());
if (traits.get_supports_color_temperature()) {
// float min_mireds = 9;
buffer.encode_float(9, traits.get_min_mireds());
// float max_mireds = 10;
buffer.encode_float(10, traits.get_max_mireds());
}
// repeated string effects = 11;
if (light->supports_effects()) {
buffer.encode_string(11, "None");
for (auto *effect : light->get_effects()) {
buffer.encode_string(11, effect->get_name());
}
}
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_LIGHT_RESPONSE);
}
#endif
#ifdef USE_SENSOR
bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) {
auto buffer = this->client_->get_buffer();
buffer.encode_nameable(sensor);
// string unique_id = 4;
std::string unique_id = sensor->unique_id();
if (unique_id.empty())
unique_id = get_default_unique_id("sensor", sensor);
buffer.encode_string(4, unique_id);
// string icon = 5;
buffer.encode_string(5, sensor->get_icon());
// string unit_of_measurement = 6;
buffer.encode_string(6, sensor->get_unit_of_measurement());
// int32 accuracy_decimals = 7;
buffer.encode_int32(7, sensor->get_accuracy_decimals());
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_SENSOR_RESPONSE);
}
#endif
#ifdef USE_SWITCH
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) {
auto buffer = this->client_->get_buffer();
buffer.encode_nameable(a_switch);
// string unique_id = 4;
buffer.encode_string(4, get_default_unique_id("switch", a_switch));
// string icon = 5;
buffer.encode_string(5, a_switch->get_icon());
// bool assumed_state = 6;
buffer.encode_bool(6, a_switch->assumed_state());
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_SWITCH_RESPONSE);
}
#endif
#ifdef USE_TEXT_SENSOR
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
auto buffer = this->client_->get_buffer();
buffer.encode_nameable(text_sensor);
// string unique_id = 4;
std::string unique_id = text_sensor->unique_id();
if (unique_id.empty())
unique_id = get_default_unique_id("text_sensor", text_sensor);
buffer.encode_string(4, unique_id);
// string icon = 5;
buffer.encode_string(5, text_sensor->get_icon());
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_TEXT_SENSOR_RESPONSE);
}
#endif
bool ListEntitiesIterator::on_end() {
return this->client_->send_empty_message(APIMessageType::LIST_ENTITIES_DONE_RESPONSE);
}
ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client)
: ComponentIterator(server), client_(client) {}
bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
auto buffer = this->client_->get_buffer();
service->encode_list_service_response(buffer);
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_SERVICE_RESPONSE);
}
#ifdef USE_ESP32_CAMERA
bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) {
auto buffer = this->client_->get_buffer();
buffer.encode_nameable(camera);
// string unique_id = 4;
buffer.encode_string(4, get_default_unique_id("camera", camera));
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_CAMERA_RESPONSE);
}
#endif
#ifdef USE_CLIMATE
bool ListEntitiesIterator::on_climate(climate::Climate *climate) {
auto buffer = this->client_->get_buffer();
buffer.encode_nameable(climate);
// string unique_id = 4;
buffer.encode_string(4, get_default_unique_id("climate", climate));
auto traits = climate->get_traits();
// bool supports_current_temperature = 5;
buffer.encode_bool(5, traits.get_supports_current_temperature());
// bool supports_two_point_target_temperature = 6;
buffer.encode_bool(6, traits.get_supports_two_point_target_temperature());
// repeated ClimateMode supported_modes = 7;
for (auto mode : {climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL,
climate::CLIMATE_MODE_HEAT}) {
if (traits.supports_mode(mode))
buffer.encode_uint32(7, mode, true);
}
// float visual_min_temperature = 8;
buffer.encode_float(8, traits.get_visual_min_temperature());
// float visual_max_temperature = 9;
buffer.encode_float(9, traits.get_visual_max_temperature());
// float visual_temperature_step = 10;
buffer.encode_float(10, traits.get_visual_temperature_step());
// bool supports_away = 11;
buffer.encode_bool(11, traits.get_supports_away());
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_CLIMATE_RESPONSE);
}
#endif
APIMessageType ListEntitiesRequest::message_type() const { return APIMessageType::LIST_ENTITIES_REQUEST; }
} // namespace api
} // namespace esphome
+57
View File
@@ -0,0 +1,57 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "api_message.h"
namespace esphome {
namespace api {
class ListEntitiesRequest : public APIMessage {
public:
APIMessageType message_type() const override;
};
class APIConnection;
class ListEntitiesIterator : public ComponentIterator {
public:
ListEntitiesIterator(APIServer *server, APIConnection *client);
#ifdef USE_BINARY_SENSOR
bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override;
#endif
#ifdef USE_COVER
bool on_cover(cover::Cover *cover) override;
#endif
#ifdef USE_FAN
bool on_fan(fan::FanState *fan) override;
#endif
#ifdef USE_LIGHT
bool on_light(light::LightState *light) override;
#endif
#ifdef USE_SENSOR
bool on_sensor(sensor::Sensor *sensor) override;
#endif
#ifdef USE_SWITCH
bool on_switch(switch_::Switch *a_switch) override;
#endif
#ifdef USE_TEXT_SENSOR
bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
#endif
bool on_service(UserServiceDescriptor *service) override;
#ifdef USE_ESP32_CAMERA
bool on_camera(esp32_camera::ESP32Camera *camera) override;
#endif
#ifdef USE_CLIMATE
bool on_climate(climate::Climate *climate) override;
#endif
bool on_end() override;
protected:
APIConnection *client_;
};
} // namespace api
} // namespace esphome
#include "api_server.h"
@@ -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
+26
View File
@@ -0,0 +1,26 @@
#include "subscribe_logs.h"
#include "esphome/core/log.h"
namespace esphome {
namespace api {
APIMessageType SubscribeLogsRequest::message_type() const { return APIMessageType::SUBSCRIBE_LOGS_REQUEST; }
bool SubscribeLogsRequest::decode_varint(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 1: // LogLevel level = 1;
this->level_ = value;
return true;
case 2: // bool dump_config = 2;
this->dump_config_ = value;
return true;
default:
return false;
}
}
uint32_t SubscribeLogsRequest::get_level() const { return this->level_; }
void SubscribeLogsRequest::set_level(uint32_t level) { this->level_ = level; }
bool SubscribeLogsRequest::get_dump_config() const { return this->dump_config_; }
void SubscribeLogsRequest::set_dump_config(bool dump_config) { this->dump_config_ = dump_config; }
} // namespace api
} // namespace esphome
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "esphome/core/component.h"
#include "api_message.h"
namespace esphome {
namespace api {
class SubscribeLogsRequest : public APIMessage {
public:
bool decode_varint(uint32_t field_id, uint32_t value) override;
APIMessageType message_type() const override;
uint32_t get_level() const;
void set_level(uint32_t level);
bool get_dump_config() const;
void set_dump_config(bool dump_config);
protected:
uint32_t level_{6};
bool dump_config_{false};
};
} // namespace api
} // namespace esphome
@@ -0,0 +1,77 @@
#include "subscribe_state.h"
#include "esphome/core/log.h"
namespace esphome {
namespace api {
#ifdef USE_BINARY_SENSOR
bool InitialStateIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
if (!binary_sensor->has_state())
return true;
return this->client_->send_binary_sensor_state(binary_sensor, binary_sensor->state);
}
#endif
#ifdef USE_COVER
bool InitialStateIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_state(cover); }
#endif
#ifdef USE_FAN
bool InitialStateIterator::on_fan(fan::FanState *fan) { return this->client_->send_fan_state(fan); }
#endif
#ifdef USE_LIGHT
bool InitialStateIterator::on_light(light::LightState *light) { return this->client_->send_light_state(light); }
#endif
#ifdef USE_SENSOR
bool InitialStateIterator::on_sensor(sensor::Sensor *sensor) {
if (!sensor->has_state())
return true;
return this->client_->send_sensor_state(sensor, sensor->state);
}
#endif
#ifdef USE_SWITCH
bool InitialStateIterator::on_switch(switch_::Switch *a_switch) {
return this->client_->send_switch_state(a_switch, a_switch->state);
}
#endif
#ifdef USE_TEXT_SENSOR
bool InitialStateIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
if (!text_sensor->has_state())
return true;
return this->client_->send_text_sensor_state(text_sensor, text_sensor->state);
}
#endif
#ifdef USE_CLIMATE
bool InitialStateIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_state(climate); }
#endif
InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client)
: ComponentIterator(server), client_(client) {}
APIMessageType SubscribeStatesRequest::message_type() const { return APIMessageType::SUBSCRIBE_STATES_REQUEST; }
bool HomeAssistantStateResponse::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
switch (field_id) {
case 1:
// string entity_id = 1;
this->entity_id_ = as_string(value, len);
return true;
case 2:
// string state = 2;
this->state_ = as_string(value, len);
return true;
default:
return false;
}
}
APIMessageType HomeAssistantStateResponse::message_type() const {
return APIMessageType::HOME_ASSISTANT_STATE_RESPONSE;
}
const std::string &HomeAssistantStateResponse::get_entity_id() const { return this->entity_id_; }
const std::string &HomeAssistantStateResponse::get_state() const { return this->state_; }
APIMessageType SubscribeHomeAssistantStatesRequest::message_type() const {
return APIMessageType::SUBSCRIBE_HOME_ASSISTANT_STATES_REQUEST;
}
} // namespace api
} // namespace esphome
+70
View File
@@ -0,0 +1,70 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/controller.h"
#include "esphome/core/defines.h"
#include "util.h"
#include "api_message.h"
namespace esphome {
namespace api {
class SubscribeStatesRequest : public APIMessage {
public:
APIMessageType message_type() const override;
};
class APIConnection;
class InitialStateIterator : public ComponentIterator {
public:
InitialStateIterator(APIServer *server, APIConnection *client);
#ifdef USE_BINARY_SENSOR
bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override;
#endif
#ifdef USE_COVER
bool on_cover(cover::Cover *cover) override;
#endif
#ifdef USE_FAN
bool on_fan(fan::FanState *fan) override;
#endif
#ifdef USE_LIGHT
bool on_light(light::LightState *light) override;
#endif
#ifdef USE_SENSOR
bool on_sensor(sensor::Sensor *sensor) override;
#endif
#ifdef USE_SWITCH
bool on_switch(switch_::Switch *a_switch) override;
#endif
#ifdef USE_TEXT_SENSOR
bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
#endif
#ifdef USE_CLIMATE
bool on_climate(climate::Climate *climate) override;
#endif
protected:
APIConnection *client_;
};
class SubscribeHomeAssistantStatesRequest : public APIMessage {
public:
APIMessageType message_type() const override;
};
class HomeAssistantStateResponse : public APIMessage {
public:
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
APIMessageType message_type() const override;
const std::string &get_entity_id() const;
const std::string &get_state() const;
protected:
std::string entity_id_;
std::string state_;
};
} // namespace api
} // namespace esphome
#include "api_server.h"
+74
View File
@@ -0,0 +1,74 @@
#include "user_services.h"
#include "esphome/core/log.h"
namespace esphome {
namespace api {
template<> bool ExecuteServiceArgument::get_value<bool>() { return this->value_bool_; }
template<> int ExecuteServiceArgument::get_value<int>() { return this->value_int_; }
template<> float ExecuteServiceArgument::get_value<float>() { return this->value_float_; }
template<> std::string ExecuteServiceArgument::get_value<std::string>() { return this->value_string_; }
APIMessageType ExecuteServiceArgument::message_type() const { return APIMessageType::EXECUTE_SERVICE_REQUEST; }
bool ExecuteServiceArgument::decode_varint(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 1: // bool bool_ = 1;
this->value_bool_ = value;
return true;
case 2: // int32 int_ = 2;
this->value_int_ = value;
return true;
default:
return false;
}
}
bool ExecuteServiceArgument::decode_32bit(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 3: // float float_ = 3;
this->value_float_ = as_float(value);
return true;
default:
return false;
}
}
bool ExecuteServiceArgument::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
switch (field_id) {
case 4: // string string_ = 4;
this->value_string_ = as_string(value, len);
return true;
default:
return false;
}
}
bool ExecuteServiceRequest::decode_32bit(uint32_t field_id, uint32_t value) {
switch (field_id) {
case 1: // fixed32 key = 1;
this->key_ = value;
return true;
default:
return false;
}
}
bool ExecuteServiceRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
switch (field_id) {
case 2: { // repeated ExecuteServiceArgument args = 2;
ExecuteServiceArgument arg;
arg.decode(value, len);
this->args_.push_back(arg);
return true;
}
default:
return false;
}
}
APIMessageType ExecuteServiceRequest::message_type() const { return APIMessageType::EXECUTE_SERVICE_REQUEST; }
const std::vector<ExecuteServiceArgument> &ExecuteServiceRequest::get_args() const { return this->args_; }
uint32_t ExecuteServiceRequest::get_key() const { return this->key_; }
ServiceTypeArgument::ServiceTypeArgument(const std::string &name, ServiceArgType type) : name_(name), type_(type) {}
const std::string &ServiceTypeArgument::get_name() const { return this->name_; }
ServiceArgType ServiceTypeArgument::get_type() const { return this->type_; }
} // namespace api
} // namespace esphome
+125
View File
@@ -0,0 +1,125 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "api_message.h"
namespace esphome {
namespace api {
enum ServiceArgType {
SERVICE_ARG_TYPE_BOOL = 0,
SERVICE_ARG_TYPE_INT = 1,
SERVICE_ARG_TYPE_FLOAT = 2,
SERVICE_ARG_TYPE_STRING = 3,
};
class ServiceTypeArgument {
public:
ServiceTypeArgument(const std::string &name, ServiceArgType type);
const std::string &get_name() const;
ServiceArgType get_type() const;
protected:
std::string name_;
ServiceArgType type_;
};
class ExecuteServiceArgument : public APIMessage {
public:
APIMessageType message_type() const override;
template<typename T> T get_value();
bool decode_varint(uint32_t field_id, uint32_t value) override;
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
bool decode_32bit(uint32_t field_id, uint32_t value) override;
protected:
bool value_bool_{false};
int value_int_{0};
float value_float_{0.0f};
std::string value_string_{};
};
class ExecuteServiceRequest : public APIMessage {
public:
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
bool decode_32bit(uint32_t field_id, uint32_t value) override;
APIMessageType message_type() const override;
uint32_t get_key() const;
const std::vector<ExecuteServiceArgument> &get_args() const;
protected:
uint32_t key_;
std::vector<ExecuteServiceArgument> args_;
};
class UserServiceDescriptor {
public:
virtual void encode_list_service_response(APIBuffer &buffer) = 0;
virtual bool execute_service(const ExecuteServiceRequest &req) = 0;
};
template<typename... Ts> class UserService : public UserServiceDescriptor, public Trigger<Ts...> {
public:
UserService(const std::string &name, const std::array<ServiceTypeArgument, sizeof...(Ts)> &args);
void encode_list_service_response(APIBuffer &buffer) override;
bool execute_service(const ExecuteServiceRequest &req) override;
protected:
template<int... S> void execute_(std::vector<ExecuteServiceArgument> args, seq<S...>);
std::string name_;
uint32_t key_{0};
std::array<ServiceTypeArgument, sizeof...(Ts)> args_;
};
template<typename... Ts>
template<int... S>
void UserService<Ts...>::execute_(std::vector<ExecuteServiceArgument> args, seq<S...>) {
this->trigger((args[S].get_value<Ts>())...);
}
template<typename... Ts> void UserService<Ts...>::encode_list_service_response(APIBuffer &buffer) {
// string name = 1;
buffer.encode_string(1, this->name_);
// fixed32 key = 2;
buffer.encode_fixed32(2, this->key_);
// repeated ListServicesArgument args = 3;
for (auto &arg : this->args_) {
auto nested = buffer.begin_nested(3);
// string name = 1;
buffer.encode_string(1, arg.get_name());
// Type type = 2;
buffer.encode_int32(2, arg.get_type());
buffer.end_nested(nested);
}
}
template<typename... Ts> bool UserService<Ts...>::execute_service(const ExecuteServiceRequest &req) {
if (req.get_key() != this->key_)
return false;
if (req.get_args().size() != this->args_.size()) {
return false;
}
this->execute_(req.get_args(), typename gens<sizeof...(Ts)>::type());
return true;
}
template<typename... Ts>
UserService<Ts...>::UserService(const std::string &name, const std::array<ServiceTypeArgument, sizeof...(Ts)> &args)
: name_(name), args_(args) {
this->key_ = fnv1_hash(this->name_);
}
template<> bool ExecuteServiceArgument::get_value<bool>();
template<> int ExecuteServiceArgument::get_value<int>();
template<> float ExecuteServiceArgument::get_value<float>();
template<> std::string ExecuteServiceArgument::get_value<std::string>();
} // namespace api
} // namespace esphome
+353
View File
@@ -0,0 +1,353 @@
#include "util.h"
#include "api_server.h"
#include "user_services.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace api {
APIBuffer::APIBuffer(std::vector<uint8_t> *buffer) : buffer_(buffer) {}
size_t APIBuffer::get_length() const { return this->buffer_->size(); }
void APIBuffer::write(uint8_t value) { this->buffer_->push_back(value); }
void APIBuffer::encode_uint32(uint32_t field, uint32_t value, bool force) {
if (value == 0 && !force)
return;
this->encode_field_raw(field, 0);
this->encode_varint_raw(value);
}
void APIBuffer::encode_int32(uint32_t field, int32_t value, bool force) {
this->encode_uint32(field, static_cast<uint32_t>(value), force);
}
void APIBuffer::encode_bool(uint32_t field, bool value, bool force) {
if (!value && !force)
return;
this->encode_field_raw(field, 0);
this->write(0x01);
}
void APIBuffer::encode_string(uint32_t field, const std::string &value) {
this->encode_string(field, value.data(), value.size());
}
void APIBuffer::encode_bytes(uint32_t field, const uint8_t *data, size_t len) {
this->encode_string(field, reinterpret_cast<const char *>(data), len);
}
void APIBuffer::encode_string(uint32_t field, const char *string, size_t len) {
if (len == 0)
return;
this->encode_field_raw(field, 2);
this->encode_varint_raw(len);
const uint8_t *data = reinterpret_cast<const uint8_t *>(string);
for (size_t i = 0; i < len; i++) {
this->write(data[i]);
}
}
void APIBuffer::encode_fixed32(uint32_t field, uint32_t value, bool force) {
if (value == 0 && !force)
return;
this->encode_field_raw(field, 5);
this->write((value >> 0) & 0xFF);
this->write((value >> 8) & 0xFF);
this->write((value >> 16) & 0xFF);
this->write((value >> 24) & 0xFF);
}
void APIBuffer::encode_float(uint32_t field, float value, bool force) {
if (value == 0.0f && !force)
return;
union {
float value_f;
uint32_t value_raw;
} val;
val.value_f = value;
this->encode_fixed32(field, val.value_raw);
}
void APIBuffer::encode_field_raw(uint32_t field, uint32_t type) {
uint32_t val = (field << 3) | (type & 0b111);
this->encode_varint_raw(val);
}
void APIBuffer::encode_varint_raw(uint32_t value) {
if (value <= 0x7F) {
this->write(value);
return;
}
while (value) {
uint8_t temp = value & 0x7F;
value >>= 7;
if (value) {
this->write(temp | 0x80);
} else {
this->write(temp);
}
}
}
void APIBuffer::encode_sint32(uint32_t field, int32_t value, bool force) {
if (value < 0)
this->encode_uint32(field, ~(uint32_t(value) << 1), force);
else
this->encode_uint32(field, uint32_t(value) << 1, force);
}
void APIBuffer::encode_nameable(Nameable *nameable) {
// string object_id = 1;
this->encode_string(1, nameable->get_object_id());
// fixed32 key = 2;
this->encode_fixed32(2, nameable->get_object_id_hash());
// string name = 3;
this->encode_string(3, nameable->get_name());
}
size_t APIBuffer::begin_nested(uint32_t field) {
this->encode_field_raw(field, 2);
return this->buffer_->size();
}
void APIBuffer::end_nested(size_t begin_index) {
const uint32_t nested_length = this->buffer_->size() - begin_index;
// add varint
std::vector<uint8_t> var;
uint32_t val = nested_length;
if (val <= 0x7F) {
var.push_back(val);
} else {
while (val) {
uint8_t temp = val & 0x7F;
val >>= 7;
if (val) {
var.push_back(temp | 0x80);
} else {
var.push_back(temp);
}
}
}
this->buffer_->insert(this->buffer_->begin() + begin_index, var.begin(), var.end());
}
optional<uint32_t> proto_decode_varuint32(const uint8_t *buf, size_t len, uint32_t *consumed) {
if (len == 0)
return {};
uint32_t result = 0;
uint8_t bitpos = 0;
for (uint32_t i = 0; i < len; i++) {
uint8_t val = buf[i];
result |= uint32_t(val & 0x7F) << bitpos;
bitpos += 7;
if ((val & 0x80) == 0) {
if (consumed != nullptr) {
*consumed = i + 1;
}
return result;
}
}
return {};
}
std::string as_string(const uint8_t *value, size_t len) {
return std::string(reinterpret_cast<const char *>(value), len);
}
int32_t as_sint32(uint32_t val) {
if (val & 1)
return uint32_t(~(val >> 1));
else
return uint32_t(val >> 1);
}
float as_float(uint32_t val) {
static_assert(sizeof(uint32_t) == sizeof(float), "float must be 32bit long");
union {
uint32_t raw;
float value;
} x;
x.raw = val;
return x.value;
}
ComponentIterator::ComponentIterator(APIServer *server) : server_(server) {}
void ComponentIterator::begin() {
this->state_ = IteratorState::BEGIN;
this->at_ = 0;
}
void ComponentIterator::advance() {
bool advance_platform = false;
bool success = true;
switch (this->state_) {
case IteratorState::NONE:
// not started
return;
case IteratorState::BEGIN:
if (this->on_begin()) {
advance_platform = true;
} else {
return;
}
break;
#ifdef USE_BINARY_SENSOR
case IteratorState::BINARY_SENSOR:
if (this->at_ >= App.get_binary_sensors().size()) {
advance_platform = true;
} else {
auto *binary_sensor = App.get_binary_sensors()[this->at_];
if (binary_sensor->is_internal()) {
success = true;
break;
} else {
success = this->on_binary_sensor(binary_sensor);
}
}
break;
#endif
#ifdef USE_COVER
case IteratorState::COVER:
if (this->at_ >= App.get_covers().size()) {
advance_platform = true;
} else {
auto *cover = App.get_covers()[this->at_];
if (cover->is_internal()) {
success = true;
break;
} else {
success = this->on_cover(cover);
}
}
break;
#endif
#ifdef USE_FAN
case IteratorState::FAN:
if (this->at_ >= App.get_fans().size()) {
advance_platform = true;
} else {
auto *fan = App.get_fans()[this->at_];
if (fan->is_internal()) {
success = true;
break;
} else {
success = this->on_fan(fan);
}
}
break;
#endif
#ifdef USE_LIGHT
case IteratorState::LIGHT:
if (this->at_ >= App.get_lights().size()) {
advance_platform = true;
} else {
auto *light = App.get_lights()[this->at_];
if (light->is_internal()) {
success = true;
break;
} else {
success = this->on_light(light);
}
}
break;
#endif
#ifdef USE_SENSOR
case IteratorState::SENSOR:
if (this->at_ >= App.get_sensors().size()) {
advance_platform = true;
} else {
auto *sensor = App.get_sensors()[this->at_];
if (sensor->is_internal()) {
success = true;
break;
} else {
success = this->on_sensor(sensor);
}
}
break;
#endif
#ifdef USE_SWITCH
case IteratorState::SWITCH:
if (this->at_ >= App.get_switches().size()) {
advance_platform = true;
} else {
auto *a_switch = App.get_switches()[this->at_];
if (a_switch->is_internal()) {
success = true;
break;
} else {
success = this->on_switch(a_switch);
}
}
break;
#endif
#ifdef USE_TEXT_SENSOR
case IteratorState::TEXT_SENSOR:
if (this->at_ >= App.get_text_sensors().size()) {
advance_platform = true;
} else {
auto *text_sensor = App.get_text_sensors()[this->at_];
if (text_sensor->is_internal()) {
success = true;
break;
} else {
success = this->on_text_sensor(text_sensor);
}
}
break;
#endif
case IteratorState ::SERVICE:
if (this->at_ >= this->server_->get_user_services().size()) {
advance_platform = true;
} else {
auto *service = this->server_->get_user_services()[this->at_];
success = this->on_service(service);
}
break;
#ifdef USE_ESP32_CAMERA
case IteratorState::CAMERA:
if (esp32_camera::global_esp32_camera == nullptr) {
advance_platform = true;
} else {
if (esp32_camera::global_esp32_camera->is_internal()) {
advance_platform = success = true;
break;
} else {
advance_platform = success = this->on_camera(esp32_camera::global_esp32_camera);
}
}
break;
#endif
#ifdef USE_CLIMATE
case IteratorState::CLIMATE:
if (this->at_ >= App.get_climates().size()) {
advance_platform = true;
} else {
auto *climate = App.get_climates()[this->at_];
if (climate->is_internal()) {
success = true;
break;
} else {
success = this->on_climate(climate);
}
}
break;
#endif
case IteratorState::MAX:
if (this->on_end()) {
this->state_ = IteratorState::NONE;
}
return;
}
if (advance_platform) {
this->state_ = static_cast<IteratorState>(static_cast<uint32_t>(this->state_) + 1);
this->at_ = 0;
} else if (success) {
this->at_++;
}
}
bool ComponentIterator::on_end() { return true; }
bool ComponentIterator::on_begin() { return true; }
bool ComponentIterator::on_service(UserServiceDescriptor *service) { return true; }
#ifdef USE_ESP32_CAMERA
bool ComponentIterator::on_camera(esp32_camera::ESP32Camera *camera) { return true; }
#endif
} // namespace api
} // namespace esphome
+127
View File
@@ -0,0 +1,127 @@
#pragma once
#include "esphome/core/helpers.h"
#include "esphome/core/component.h"
#include "esphome/core/controller.h"
#ifdef USE_ESP32_CAMERA
#include "esphome/components/esp32_camera/esp32_camera.h"
#endif
namespace esphome {
namespace api {
class APIBuffer {
public:
APIBuffer(std::vector<uint8_t> *buffer);
size_t get_length() const;
void write(uint8_t value);
void encode_int32(uint32_t field, int32_t value, bool force = false);
void encode_uint32(uint32_t field, uint32_t value, bool force = false);
void encode_sint32(uint32_t field, int32_t value, bool force = false);
void encode_bool(uint32_t field, bool value, bool force = false);
void encode_string(uint32_t field, const std::string &value);
void encode_string(uint32_t field, const char *string, size_t len);
void encode_bytes(uint32_t field, const uint8_t *data, size_t len);
void encode_fixed32(uint32_t field, uint32_t value, bool force = false);
void encode_float(uint32_t field, float value, bool force = false);
void encode_nameable(Nameable *nameable);
size_t begin_nested(uint32_t field);
void end_nested(size_t begin_index);
void encode_field_raw(uint32_t field, uint32_t type);
void encode_varint_raw(uint32_t value);
protected:
std::vector<uint8_t> *buffer_;
};
optional<uint32_t> proto_decode_varuint32(const uint8_t *buf, size_t len, uint32_t *consumed = nullptr);
std::string as_string(const uint8_t *value, size_t len);
int32_t as_sint32(uint32_t val);
float as_float(uint32_t val);
class APIServer;
class UserServiceDescriptor;
class ComponentIterator {
public:
ComponentIterator(APIServer *server);
void begin();
void advance();
virtual bool on_begin();
#ifdef USE_BINARY_SENSOR
virtual bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) = 0;
#endif
#ifdef USE_COVER
virtual bool on_cover(cover::Cover *cover) = 0;
#endif
#ifdef USE_FAN
virtual bool on_fan(fan::FanState *fan) = 0;
#endif
#ifdef USE_LIGHT
virtual bool on_light(light::LightState *light) = 0;
#endif
#ifdef USE_SENSOR
virtual bool on_sensor(sensor::Sensor *sensor) = 0;
#endif
#ifdef USE_SWITCH
virtual bool on_switch(switch_::Switch *a_switch) = 0;
#endif
#ifdef USE_TEXT_SENSOR
virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0;
#endif
virtual bool on_service(UserServiceDescriptor *service);
#ifdef USE_ESP32_CAMERA
virtual bool on_camera(esp32_camera::ESP32Camera *camera);
#endif
#ifdef USE_CLIMATE
virtual bool on_climate(climate::Climate *climate) = 0;
#endif
virtual bool on_end();
protected:
enum class IteratorState {
NONE = 0,
BEGIN,
#ifdef USE_BINARY_SENSOR
BINARY_SENSOR,
#endif
#ifdef USE_COVER
COVER,
#endif
#ifdef USE_FAN
FAN,
#endif
#ifdef USE_LIGHT
LIGHT,
#endif
#ifdef USE_SENSOR
SENSOR,
#endif
#ifdef USE_SWITCH
SWITCH,
#endif
#ifdef USE_TEXT_SENSOR
TEXT_SENSOR,
#endif
SERVICE,
#ifdef USE_ESP32_CAMERA
CAMERA,
#endif
#ifdef USE_CLIMATE
CLIMATE,
#endif
MAX,
} state_{IteratorState::NONE};
size_t at_{0};
APIServer *server_;
};
} // namespace api
} // namespace esphome

Some files were not shown because too many files have changed in this diff Show More