diff --git a/.clang-format b/.clang-format
index a7c337f80e..f2d86c57cd 100644
--- a/.clang-format
+++ b/.clang-format
@@ -49,7 +49,7 @@ ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
-DerivePointerAlignment: true
+DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
diff --git a/.clang-tidy b/.clang-tidy
index 5e486e6a0c..b40e606121 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -2,17 +2,29 @@
Checks: >-
*,
-abseil-*,
+ -altera-*,
-android-*,
-boost-*,
- -bugprone-macro-parentheses,
+ -bugprone-narrowing-conversions,
+ -bugprone-signed-char-misuse,
-cert-dcl50-cpp,
-cert-err58-cpp,
- -clang-analyzer-core.CallAndMessage,
+ -cert-oop57-cpp,
+ -cert-str34-c,
+ -clang-analyzer-optin.cplusplus.UninitializedObject,
-clang-analyzer-osx.*,
- -clang-analyzer-security.*,
- -cppcoreguidelines-avoid-goto,
- -cppcoreguidelines-c-copy-assignment-signature,
- -cppcoreguidelines-owning-memory,
+ -clang-diagnostic-delete-abstract-non-virtual-dtor,
+ -clang-diagnostic-delete-non-abstract-non-virtual-dtor,
+ -clang-diagnostic-shadow-field,
+ -clang-diagnostic-unused-const-variable,
+ -clang-diagnostic-unused-parameter,
+ -concurrency-*,
+ -cppcoreguidelines-avoid-c-arrays,
+ -cppcoreguidelines-avoid-magic-numbers,
+ -cppcoreguidelines-init-variables,
+ -cppcoreguidelines-macro-usage,
+ -cppcoreguidelines-narrowing-conversions,
+ -cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
@@ -24,42 +36,55 @@ Checks: >-
-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,
+ -fuchsia-default-arguments-declarations,
+ -fuchsia-default-arguments-calls,
-google-build-using-namespace,
-google-explicit-constructor,
-google-readability-braces-around-statements,
-google-readability-casting,
+ -google-readability-namespace-comments,
-google-readability-todo,
- -google-runtime-int,
-google-runtime-references,
-hicpp-*,
+ -llvm-else-after-return,
-llvm-header-guard,
-llvm-include-order,
- -misc-unconventional-assign-operator,
+ -llvm-qualified-auto,
+ -llvmlibc-*,
+ -misc-non-private-member-variables-in-classes,
+ -misc-no-recursion,
-misc-unused-parameters,
- -modernize-deprecated-headers,
- -modernize-pass-by-value,
- -modernize-pass-by-value,
+ -modernize-avoid-c-arrays,
+ -modernize-avoid-bind,
+ -modernize-concat-nested-namespaces,
-modernize-return-braced-init-list,
-modernize-use-auto,
-modernize-use-default-member-init,
-modernize-use-equals-default,
+ -modernize-use-trailing-return-type,
+ -modernize-use-nodiscard,
-mpi-*,
-objc-*,
- -performance-unnecessary-value-param,
-readability-braces-around-statements,
+ -readability-const-return-type,
+ -readability-convert-member-functions-to-static,
-readability-else-after-return,
+ -readability-function-cognitive-complexity,
-readability-implicit-bool-conversion,
+ -readability-isolate-declaration,
+ -readability-magic-numbers,
+ -readability-make-member-function-const,
-readability-named-parameter,
+ -readability-qualified-auto,
+ -readability-redundant-access-specifiers,
-readability-redundant-member-init,
- -warnings-as-errors,
- -zircon-*
+ -readability-redundant-string-init,
+ -readability-uppercase-literal-suffix,
+ -readability-use-anyofallof,
WarningsAsErrors: '*'
-HeaderFilterRegex: '^.*/src/esphome/.*'
AnalyzeTemporaryDtors: false
FormatStyle: google
CheckOptions:
@@ -67,9 +92,11 @@ CheckOptions:
value: '1'
- key: google-readability-function-size.StatementThreshold
value: '800'
- - key: google-readability-namespace-comments.ShortNamespaceLines
+ - key: google-runtime-int.TypeSuffix
+ value: '_t'
+ - key: llvm-namespace-comment.ShortNamespaceLines
value: '10'
- - key: google-readability-namespace-comments.SpacesBeforeComments
+ - key: llvm-namespace-comment.SpacesBeforeComments
value: '2'
- key: modernize-loop-convert.MaxCopySize
value: '16'
@@ -83,6 +110,10 @@ CheckOptions:
value: llvm
- key: modernize-use-nullptr.NullMacros
value: 'NULL'
+ - key: modernize-make-unique.MakeSmartPtrFunction
+ value: 'make_unique'
+ - key: modernize-make-unique.MakeSmartPtrFunctionHeader
+ value: 'esphome/core/helpers.h'
- key: readability-identifier-naming.LocalVariableCase
value: 'lower_case'
- key: readability-identifier-naming.ClassCase
@@ -96,15 +127,19 @@ CheckOptions:
- key: readability-identifier-naming.StaticConstantCase
value: 'UPPER_CASE'
- key: readability-identifier-naming.StaticVariableCase
- value: 'UPPER_CASE'
+ value: 'lower_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.PrivateMemberCase
+ value: 'lower_case'
+ - key: readability-identifier-naming.PrivateMemberSuffix
+ value: '_'
+ - key: readability-identifier-naming.PrivateMethodCase
+ value: 'lower_case'
+ - key: readability-identifier-naming.PrivateMethodSuffix
+ value: '_'
- key: readability-identifier-naming.ClassMemberCase
value: 'lower_case'
- key: readability-identifier-naming.ClassMemberCase
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 5ce1768f5f..433e5d2792 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -1,17 +1,29 @@
{
"name": "ESPHome Dev",
- "context": "..",
- "dockerFile": "../docker/Dockerfile.dev",
- "postCreateCommand": "mkdir -p config && pip3 install -e .",
- "runArgs": ["--privileged", "-e", "ESPHOME_DASHBOARD_USE_PING=1"],
+ "image": "esphome/esphome-lint:dev",
+ "postCreateCommand": [
+ "script/devcontainer-post-create"
+ ],
+ "runArgs": [
+ "--privileged",
+ "-e",
+ "ESPHOME_DASHBOARD_USE_PING=1"
+ ],
"appPort": 6052,
"extensions": [
+ // python
"ms-python.python",
"visualstudioexptteam.vscodeintellicode",
- "redhat.vscode-yaml"
+ // yaml
+ "redhat.vscode-yaml",
+ // cpp
+ "ms-vscode.cpptools",
+ // editorconfig
+ "editorconfig.editorconfig",
],
"settings": {
- "python.pythonPath": "/usr/local/bin/python",
+ "python.languageServer": "Pylance",
+ "python.pythonPath": "/usr/bin/python3",
"python.linting.pylintEnabled": true,
"python.linting.enabled": true,
"python.formatting.provider": "black",
@@ -19,13 +31,26 @@
"editor.formatOnSave": true,
"editor.formatOnType": true,
"files.trimTrailingWhitespace": true,
- "terminal.integrated.shell.linux": "/bin/bash",
+ "terminal.integrated.defaultProfile.linux": "bash",
"yaml.customTags": [
"!secret scalar",
+ "!lambda scalar",
"!include_dir_named scalar",
"!include_dir_list scalar",
"!include_dir_merge_list scalar",
"!include_dir_merge_named scalar"
- ]
+ ],
+ "files.exclude": {
+ "**/.git": true,
+ "**/.DS_Store": true,
+ "**/*.pyc": {
+ "when": "$(basename).py"
+ },
+ "**/__pycache__": true
+ },
+ "files.associations": {
+ "**/.vscode/*.json": "jsonc"
+ },
+ "C_Cpp.clang_format_path": "/usr/bin/clang-format-11",
}
}
diff --git a/.dockerignore b/.dockerignore
index e1baed38ca..9f14b98059 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -103,6 +103,10 @@ venv.bak/
# mypy
.mypy_cache/
+# PlatformIO
+.pio/
+
+# ESPHome
config/
examples/
Dockerfile
diff --git a/.editorconfig b/.editorconfig
index f24d70487a..8ccf1eeebc 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -7,7 +7,7 @@ insert_final_newline = true
charset = utf-8
# python
-[*.{py}]
+[*.py]
indent_style = space
indent_size = 4
@@ -25,3 +25,10 @@ indent_size = 2
[*.{yaml,yml}]
indent_style = space
indent_size = 2
+quote_type = single
+
+# JSON
+[*.json]
+indent_style = space
+indent_size = 2
+
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000..dad0966222
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Normalize line endings to LF in the repository
+* text eol=lf
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 52ac3648b0..864586fe6b 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,8 +1,3 @@
# These are supported funding model platforms
-github:
-patreon: ottowinter
-open_collective:
-ko_fi:
-tidelift:
-custom: https://esphome.io/guides/supporters.html
+custom: https://www.nabucasa.com
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000000..4add58dfbe
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,12 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Issue Tracker
+ url: https://github.com/esphome/issues
+ about: Please create bug reports in the dedicated issue tracker.
+ - name: Feature Request Tracker
+ url: https://github.com/esphome/feature-requests
+ about: Please create feature requests in the dedicated feature request tracker.
+ - name: Frequently Asked Question
+ url: https://esphome.io/guides/faq.html
+ about: Please view the FAQ for common questions and what to include in a bug report.
+
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 94a5b7284e..25411c19f5 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,13 +1,40 @@
-## Description:
+# What does this implement/fix?
+Quick description and explanation of changes
-**Related issue (if applicable):** fixes
+## Types of changes
+
+- [ ] Bugfix (non-breaking change which fixes an issue)
+- [ ] New feature (non-breaking change which adds functionality)
+- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
+- [ ] Other
+
+**Related issue or feature (if applicable):** fixes
**Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs#
+## Test Environment
+
+- [ ] ESP32
+- [ ] ESP32 IDF
+- [ ] ESP8266
+
+## Example entry for `config.yaml`:
+
+
+```yaml
+# Example config.yaml
+
+```
+
## 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 [esphome-docs](https://github.com/esphome/esphome-docs).
diff --git a/.github/ci-reporter.yml b/.github/ci-reporter.yml
deleted file mode 100644
index 243e671532..0000000000
--- a/.github/ci-reporter.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-# Set to false to create a new comment instead of updating the app's first one
-updateComment: true
-
-# Use a custom string, or set to false to disable
-before: "✨ Good work on this PR so far! ✨ Unfortunately, the [ build]() is failing as of . Here's the output:"
-
-# Use a custom string, or set to false to disable
-after: "Thanks for contributing to this project!"
diff --git a/.github/config.yml b/.github/config.yml
deleted file mode 100644
index f2b357cc95..0000000000
--- a/.github/config.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-# Configuration for sentiment-bot - https://github.com/behaviorbot/sentiment-bot
-
-# *Required* toxicity threshold between 0 and .99 with the higher numbers being the most toxic
-# Anything higher than this threshold will be marked as toxic and commented on
-sentimentBotToxicityThreshold: .8
-
-# *Required* Comment to reply with
-sentimentBotReplyComment: >
- Please be sure to review the code of conduct and be respectful of other users.
-
-# Note: the bot will only work if your repository has a Code of Conduct
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000000..d73adbfa30
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,9 @@
+version: 2
+updates:
+ - package-ecosystem: "pip"
+ directory: "/"
+ schedule:
+ interval: "daily"
+ ignore:
+ # Hypotehsis is only used for testing and is updated quite often
+ - dependency-name: hypothesis
diff --git a/.github/issue-close-app.yml b/.github/issue-close-app.yml
deleted file mode 100644
index 5f5fb7572d..0000000000
--- a/.github/issue-close-app.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-comment: >-
- https://github.com/esphome/esphome/issues/430
-issueConfigs:
-- content:
- - "OTHERWISE THE ISSUE WILL BE CLOSED AUTOMATICALLY"
-
-caseInsensitive: false
diff --git a/.github/lock.yml b/.github/lock.yml
deleted file mode 100644
index 0680577b2e..0000000000
--- a/.github/lock.yml
+++ /dev/null
@@ -1,36 +0,0 @@
-# Configuration for Lock Threads - https://github.com/dessant/lock-threads
-
-# Number of days of inactivity before a closed issue or pull request is locked
-daysUntilLock: 7
-
-# Skip issues and pull requests created before a given timestamp. Timestamp must
-# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
-skipCreatedBefore: false
-
-# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
-exemptLabels:
- - keep-open
-
-# Label to add before locking, such as `outdated`. Set to `false` to disable
-lockLabel: false
-
-# Comment to post before locking. Set to `false` to disable
-lockComment: false
-
-# Assign `resolved` as the reason for locking. Set to `false` to disable
-setLockReason: false
-
-# Limit to only `issues` or `pulls`
-# only: issues
-
-# Optionally, specify configuration settings just for `issues` or `pulls`
-# issues:
-# exemptLabels:
-# - help-wanted
-# lockLabel: outdated
-
-# pulls:
-# daysUntilLock: 30
-
-# Repository to extend settings from
-# _extends: repo
diff --git a/.github/stale.yml b/.github/stale.yml
deleted file mode 100644
index 225c029bd9..0000000000
--- a/.github/stale.yml
+++ /dev/null
@@ -1,59 +0,0 @@
-# Configuration for probot-stale - https://github.com/probot/stale
-
-# Number of days of inactivity before an Issue or Pull Request becomes stale
-daysUntilStale: 60
-
-# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
-# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
-daysUntilClose: 7
-
-# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
-onlyLabels: []
-
-# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
-exemptLabels:
- - not-stale
-
-# Set to true to ignore issues in a project (defaults to false)
-exemptProjects: false
-
-# Set to true to ignore issues in a milestone (defaults to false)
-exemptMilestones: true
-
-# Set to true to ignore issues with an assignee (defaults to false)
-exemptAssignees: false
-
-# Label to use when marking as stale
-staleLabel: stale
-
-# Comment to post when marking as stale. Set to `false` to disable
-markComment: >
- This issue has been automatically marked as stale because it has not had
- recent activity. It will be closed if no further activity occurs. Thank you
- for your contributions.
-
-# Comment to post when removing the stale label.
-# unmarkComment: >
-# Your comment here.
-
-# Comment to post when closing a stale Issue or Pull Request.
-# closeComment: >
-# Your comment here.
-
-# Limit the number of actions per hour, from 1-30. Default is 30
-limitPerRun: 10
-
-# Limit to only `issues` or `pulls`
-only: pulls
-
-# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
-# pulls:
-# daysUntilStale: 30
-# markComment: >
-# This pull request has been automatically marked as stale because it has not had
-# recent activity. It will be closed if no further activity occurs. Thank you
-# for your contributions.
-
-# issues:
-# exemptLabels:
-# - confirmed
diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml
new file mode 100644
index 0000000000..1d1cc169b2
--- /dev/null
+++ b/.github/workflows/ci-docker.yml
@@ -0,0 +1,53 @@
+name: CI for docker images
+
+# Only run when docker paths change
+on:
+ push:
+ branches: [dev, beta, release]
+ paths:
+ - 'docker/**'
+ - '.github/workflows/**'
+ - 'requirements*.txt'
+ - 'platformio.ini'
+
+ pull_request:
+ paths:
+ - 'docker/**'
+ - '.github/workflows/**'
+ - 'requirements*.txt'
+ - 'platformio.ini'
+
+permissions:
+ contents: read
+ packages: read
+
+jobs:
+ check-docker:
+ name: Build docker containers
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ arch: [amd64, armv7, aarch64]
+ build_type: ["ha-addon", "docker", "lint"]
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python
+ uses: actions/setup-python@v2
+ with:
+ python-version: '3.9'
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v1
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v1
+
+ - name: Set TAG
+ run: |
+ echo "TAG=check" >> $GITHUB_ENV
+
+ - name: Run build
+ run: |
+ docker/build.py \
+ --tag "${TAG}" \
+ --arch "${{ matrix.arch }}" \
+ --build-type "${{ matrix.build_type }}" \
+ build
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000000..93a29874f7
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,166 @@
+name: CI
+
+on:
+ push:
+ branches: [dev, beta, release]
+
+ pull_request:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ ci:
+ name: ${{ matrix.name }}
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - id: ci-custom
+ name: Run script/ci-custom
+ - id: lint-python
+ name: Run script/lint-python
+ - id: test
+ file: tests/test1.yaml
+ name: Test tests/test1.yaml
+ pio_cache_key: test1
+ - id: test
+ file: tests/test2.yaml
+ name: Test tests/test2.yaml
+ pio_cache_key: test2
+ - id: test
+ file: tests/test3.yaml
+ name: Test tests/test3.yaml
+ pio_cache_key: test3
+ - id: test
+ file: tests/test4.yaml
+ name: Test tests/test4.yaml
+ pio_cache_key: test4
+ - id: test
+ file: tests/test5.yaml
+ name: Test tests/test5.yaml
+ pio_cache_key: test5
+ - id: pytest
+ name: Run pytest
+ - id: clang-format
+ name: Run script/clang-format
+ - id: clang-tidy
+ name: Run script/clang-tidy for ESP8266
+ options: --environment esp8266-tidy --grep USE_ESP8266
+ pio_cache_key: tidyesp8266
+ - id: clang-tidy
+ name: Run script/clang-tidy for ESP32 1/4
+ options: --environment esp32-tidy --split-num 4 --split-at 1
+ pio_cache_key: tidyesp32
+ - id: clang-tidy
+ name: Run script/clang-tidy for ESP32 2/4
+ options: --environment esp32-tidy --split-num 4 --split-at 2
+ pio_cache_key: tidyesp32
+ - id: clang-tidy
+ name: Run script/clang-tidy for ESP32 3/4
+ options: --environment esp32-tidy --split-num 4 --split-at 3
+ pio_cache_key: tidyesp32
+ - id: clang-tidy
+ name: Run script/clang-tidy for ESP32 4/4
+ options: --environment esp32-tidy --split-num 4 --split-at 4
+ pio_cache_key: tidyesp32
+ - id: clang-tidy
+ name: Run script/clang-tidy for ESP32 esp-idf
+ options: --environment esp32-idf-tidy --grep USE_ESP_IDF
+ pio_cache_key: tidyesp32-idf
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python
+ uses: actions/setup-python@v2
+ id: python
+ with:
+ python-version: '3.7'
+
+ - name: Cache virtualenv
+ uses: actions/cache@v2
+ with:
+ path: .venv
+ key: venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }}
+ restore-keys: |
+ venv-${{ steps.python.outputs.python-version }}-
+
+ - name: Set up virtualenv
+ run: |
+ python -m venv .venv
+ source .venv/bin/activate
+ pip install -U pip
+ pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
+ pip install -e .
+ echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH
+ echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> $GITHUB_ENV
+
+ # Use per check platformio cache because checks use different parts
+ - name: Cache platformio
+ uses: actions/cache@v2
+ with:
+ path: ~/.platformio
+ key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
+ if: matrix.id == 'test' || matrix.id == 'clang-tidy'
+
+ - name: Install clang tools
+ run: |
+ sudo apt-get install \
+ clang-format-11 \
+ clang-tidy-11
+ if: matrix.id == 'clang-tidy' || matrix.id == 'clang-format'
+
+ - name: Register problem matchers
+ run: |
+ echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
+ echo "::add-matcher::.github/workflows/matchers/lint-python.json"
+ echo "::add-matcher::.github/workflows/matchers/python.json"
+ echo "::add-matcher::.github/workflows/matchers/pytest.json"
+ echo "::add-matcher::.github/workflows/matchers/gcc.json"
+ echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
+
+ - name: Lint Custom
+ run: |
+ script/ci-custom.py
+ script/build_codeowners.py --check
+ if: matrix.id == 'ci-custom'
+
+ - name: Lint Python
+ run: script/lint-python
+ if: matrix.id == 'lint-python'
+
+ - run: esphome compile ${{ matrix.file }}
+ if: matrix.id == 'test'
+ env:
+ # Also cache libdeps, store them in a ~/.platformio subfolder
+ PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
+
+ - name: Run pytest
+ run: |
+ pytest -vv --tb=native tests
+ if: matrix.id == 'pytest'
+
+ # Also run git-diff-index so that the step is marked as failed on formatting errors,
+ # since clang-format doesn't do anything but change files if -i is passed.
+ - name: Run clang-format
+ run: |
+ script/clang-format -i
+ git diff-index --quiet HEAD --
+ if: matrix.id == 'clang-format'
+
+ - name: Run clang-tidy
+ run: |
+ script/clang-tidy --all-headers --fix ${{ matrix.options }}
+ if: matrix.id == 'clang-tidy'
+ env:
+ # Also cache libdeps, store them in a ~/.platformio subfolder
+ PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
+
+ - name: Suggested changes
+ run: script/ci-suggest-changes
+ if: always() && (matrix.id == 'clang-tidy' || matrix.id == 'clang-format')
diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml
new file mode 100644
index 0000000000..ceb45b2a91
--- /dev/null
+++ b/.github/workflows/lock.yml
@@ -0,0 +1,27 @@
+name: Lock
+
+on:
+ schedule:
+ - cron: '30 0 * * *'
+ workflow_dispatch:
+
+permissions:
+ issues: write
+ pull-requests: write
+
+concurrency:
+ group: lock
+
+jobs:
+ lock:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: dessant/lock-threads@v3
+ with:
+ pr-inactive-days: "1"
+ pr-lock-reason: ""
+ exclude-any-pr-labels: keep-open
+
+ issue-inactive-days: "7"
+ issue-lock-reason: ""
+ exclude-any-issue-labels: keep-open
diff --git a/.github/workflows/matchers/ci-custom.json b/.github/workflows/matchers/ci-custom.json
new file mode 100644
index 0000000000..1d5f2551cd
--- /dev/null
+++ b/.github/workflows/matchers/ci-custom.json
@@ -0,0 +1,16 @@
+{
+ "problemMatcher": [
+ {
+ "owner": "ci-custom",
+ "pattern": [
+ {
+ "regexp": "^(.*):(\\d+):(\\d+):\\s+lint:\\s+(.*)$",
+ "file": 1,
+ "line": 2,
+ "column": 3,
+ "message": 4
+ }
+ ]
+ }
+ ]
+}
diff --git a/.github/workflows/matchers/clang-tidy.json b/.github/workflows/matchers/clang-tidy.json
new file mode 100644
index 0000000000..03e77341a5
--- /dev/null
+++ b/.github/workflows/matchers/clang-tidy.json
@@ -0,0 +1,17 @@
+{
+ "problemMatcher": [
+ {
+ "owner": "clang-tidy",
+ "pattern": [
+ {
+ "regexp": "^(.*):(\\d+):(\\d+):\\s+(error):\\s+(.*) \\[([a-z0-9,\\-]+)\\]\\s*$",
+ "file": 1,
+ "line": 2,
+ "column": 3,
+ "severity": 4,
+ "message": 5
+ }
+ ]
+ }
+ ]
+}
diff --git a/.github/workflows/matchers/gcc.json b/.github/workflows/matchers/gcc.json
new file mode 100644
index 0000000000..a00d9c33f4
--- /dev/null
+++ b/.github/workflows/matchers/gcc.json
@@ -0,0 +1,18 @@
+{
+ "problemMatcher": [
+ {
+ "owner": "gcc",
+ "severity": "error",
+ "pattern": [
+ {
+ "regexp": "^src/(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
+ "file": 1,
+ "line": 2,
+ "column": 3,
+ "severity": 4,
+ "message": 5
+ }
+ ]
+ }
+ ]
+}
diff --git a/.github/workflows/matchers/lint-python.json b/.github/workflows/matchers/lint-python.json
new file mode 100644
index 0000000000..6a09f04770
--- /dev/null
+++ b/.github/workflows/matchers/lint-python.json
@@ -0,0 +1,39 @@
+{
+ "problemMatcher": [
+ {
+ "owner": "black",
+ "severity": "error",
+ "pattern": [
+ {
+ "regexp": "^(.*): (Please format this file with the black formatter)",
+ "file": 1,
+ "message": 2
+ }
+ ]
+ },
+ {
+ "owner": "flake8",
+ "severity": "error",
+ "pattern": [
+ {
+ "regexp": "^(.*):(\\d+): ([EFCDNW]\\d{3}.*)$",
+ "file": 1,
+ "line": 2,
+ "message": 3
+ }
+ ]
+ },
+ {
+ "owner": "pylint",
+ "severity": "error",
+ "pattern": [
+ {
+ "regexp": "^(.*):(\\d+): (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$",
+ "file": 1,
+ "line": 2,
+ "message": 3
+ }
+ ]
+ }
+ ]
+}
diff --git a/.github/workflows/matchers/pytest.json b/.github/workflows/matchers/pytest.json
new file mode 100644
index 0000000000..0eb8f050e6
--- /dev/null
+++ b/.github/workflows/matchers/pytest.json
@@ -0,0 +1,19 @@
+{
+ "problemMatcher": [
+ {
+ "owner": "pytest",
+ "fileLocation": "absolute",
+ "pattern": [
+ {
+ "regexp": "^\\s+File \"(.*)\", line (\\d+), in (.*)$",
+ "file": 1,
+ "line": 2
+ },
+ {
+ "regexp": "^\\s+(.*)$",
+ "message": 1
+ }
+ ]
+ }
+ ]
+}
diff --git a/.github/workflows/matchers/python.json b/.github/workflows/matchers/python.json
new file mode 100644
index 0000000000..9c3095c0c9
--- /dev/null
+++ b/.github/workflows/matchers/python.json
@@ -0,0 +1,18 @@
+{
+ "problemMatcher": [
+ {
+ "owner": "python",
+ "pattern": [
+ {
+ "regexp": "^\\s*File\\s\\\"(.*)\\\",\\sline\\s(\\d+),\\sin\\s(.*)$",
+ "file": 1,
+ "line": 2
+ },
+ {
+ "regexp": "^\\s*raise\\s(.*)\\(\\'(.*)\\'\\)$",
+ "message": 2
+ }
+ ]
+ }
+ ]
+}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000000..d6895becc0
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,154 @@
+name: Publish Release
+
+on:
+ workflow_dispatch:
+ release:
+ types: [published]
+ schedule:
+ - cron: "0 2 * * *"
+
+permissions:
+ contents: read
+
+jobs:
+ init:
+ name: Initialize build
+ runs-on: ubuntu-latest
+ outputs:
+ tag: ${{ steps.tag.outputs.tag }}
+ steps:
+ - uses: actions/checkout@v2
+ - name: Get tag
+ id: tag
+ run: |
+ if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then
+ TAG="${GITHUB_REF#refs/tags/}"
+ else
+ TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p")
+ today="$(date --utc '+%Y%m%d')"
+ TAG="${TAG}${today}"
+ fi
+ echo "::set-output name=tag::${TAG}"
+
+ deploy-pypi:
+ name: Build and publish to PyPi
+ if: github.repository == 'esphome/esphome' && github.event_name == 'release'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python
+ uses: actions/setup-python@v1
+ with:
+ python-version: '3.x'
+ - name: Set up python environment
+ run: |
+ script/setup
+ pip install setuptools wheel twine
+ - name: Build
+ run: python setup.py sdist bdist_wheel
+ - name: Upload
+ env:
+ TWINE_USERNAME: __token__
+ TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
+ run: twine upload dist/*
+
+ deploy-docker:
+ name: Build and publish docker containers
+ if: github.repository == 'esphome/esphome'
+ permissions:
+ contents: read
+ packages: write
+ runs-on: ubuntu-latest
+ needs: [init]
+ strategy:
+ matrix:
+ arch: [amd64, armv7, aarch64]
+ build_type: ["ha-addon", "docker", "lint"]
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python
+ uses: actions/setup-python@v2
+ with:
+ python-version: '3.9'
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v1
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v1
+
+ - name: Log in to docker hub
+ uses: docker/login-action@v1
+ with:
+ username: ${{ secrets.DOCKER_USER }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+ - name: Log in to the GitHub container registry
+ uses: docker/login-action@v1
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Build and push
+ run: |
+ docker/build.py \
+ --tag "${{ needs.init.outputs.tag }}" \
+ --arch "${{ matrix.arch }}" \
+ --build-type "${{ matrix.build_type }}" \
+ build \
+ --push
+
+ deploy-docker-manifest:
+ if: github.repository == 'esphome/esphome'
+ permissions:
+ contents: read
+ packages: write
+ runs-on: ubuntu-latest
+ needs: [init, deploy-docker]
+ strategy:
+ matrix:
+ build_type: ["ha-addon", "docker", "lint"]
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python
+ uses: actions/setup-python@v2
+ with:
+ python-version: '3.9'
+ - name: Enable experimental manifest support
+ run: |
+ mkdir -p ~/.docker
+ echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
+
+ - name: Log in to docker hub
+ uses: docker/login-action@v1
+ with:
+ username: ${{ secrets.DOCKER_USER }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+ - name: Log in to the GitHub container registry
+ uses: docker/login-action@v1
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Run manifest
+ run: |
+ docker/build.py \
+ --tag "${{ needs.init.outputs.tag }}" \
+ --build-type "${{ matrix.build_type }}" \
+ manifest
+
+ deploy-hassio-repo:
+ if: github.repository == 'esphome/esphome' && github.event_name == 'release'
+ runs-on: ubuntu-latest
+ needs: [deploy-docker]
+ steps:
+ - env:
+ TOKEN: ${{ secrets.DEPLOY_HASSIO_TOKEN }}
+ run: |
+ TAG="${GITHUB_REF#refs/tags/}"
+ curl \
+ -u ":$TOKEN" \
+ -X POST \
+ -H "Accept: application/vnd.github.v3+json" \
+ https://api.github.com/repos/esphome/hassio/actions/workflows/bump-version.yml/dispatches \
+ -d "{\"ref\":\"main\",\"inputs\":{\"version\":\"$TAG\"}}"
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 0000000000..c3e450d0cf
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,48 @@
+name: Stale
+
+on:
+ schedule:
+ - cron: '30 0 * * *'
+ workflow_dispatch:
+
+permissions:
+ issues: write
+ pull-requests: write
+
+concurrency:
+ group: lock
+
+jobs:
+ stale:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/stale@v4
+ with:
+ days-before-pr-stale: 90
+ days-before-pr-close: 7
+ days-before-issue-stale: -1
+ days-before-issue-close: -1
+ remove-stale-when-updated: true
+ stale-pr-label: "stale"
+ exempt-pr-labels: "no-stale"
+ stale-pr-message: >
+ There hasn't been any activity on this pull request recently. This
+ pull request has been automatically marked as stale because of that
+ and will be closed if no further activity occurs within 7 days.
+ Thank you for your contributions.
+
+ # Use stale to automatically close issues with a reference to the issue tracker
+ close-issues:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/stale@v4
+ with:
+ days-before-pr-stale: -1
+ days-before-pr-close: -1
+ days-before-issue-stale: 1
+ days-before-issue-close: 1
+ remove-stale-when-updated: true
+ stale-issue-label: "stale"
+ exempt-issue-labels: "not-stale"
+ stale-issue-message: >
+ https://github.com/esphome/esphome/issues/430
diff --git a/.gitignore b/.gitignore
index fa4670769b..57b8478bd7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,9 @@ __pycache__/
# Intellij Idea
.idea
+# Vim
+*.swp
+
# Hide some OS X stuff
.DS_Store
.AppleDouble
@@ -51,8 +54,10 @@ htmlcov/
.coverage
.coverage.*
.cache
+.esphome
nosetests.xml
coverage.xml
+cov.xml
*.cover
.hypothesis/
.pytest_cache/
@@ -79,7 +84,8 @@ venv.bak/
.pioenvs
.piolibdeps
.pio
-.vscode
+.vscode/
+!.vscode/tasks.json
CMakeListsPrivate.txt
CMakeLists.txt
@@ -96,8 +102,7 @@ CMakeLists.txt
.idea/**/dynamic.xml
# CMake
-cmake-build-debug/
-cmake-build-release/
+cmake-build-*/
CMakeCache.txt
CMakeFiles
@@ -117,3 +122,8 @@ config/
tests/build/
tests/.esphome/
/.temp-clang-tidy.cpp
+/.temp/
+.pio/
+
+sdkconfig.*
+!sdkconfig.defaults
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
deleted file mode 100644
index 3db0b982ae..0000000000
--- a/.gitlab-ci.yml
+++ /dev/null
@@ -1,342 +0,0 @@
----
-# Based on https://gitlab.com/hassio-addons/addon-node-red/blob/master/.gitlab-ci.yml
-variables:
- DOCKER_DRIVER: overlay2
- DOCKER_HOST: tcp://docker:2375/
- BASE_VERSION: '2.0.1'
- TZ: UTC
-
-stages:
- - lint
- - test
- - deploy
-
-.lint: &lint
- image: esphome/esphome-lint:latest
- stage: lint
- before_script:
- - script/setup
- tags:
- - docker
-
-.test: &test
- image: esphome/esphome-lint:latest
- stage: test
- before_script:
- - script/setup
- tags:
- - docker
-
-.docker-base: &docker-base
- image: esphome/esphome-base-builder
- before_script:
- - docker info
- - docker login -u "$DOCKER_USER" -p "$DOCKER_PASSWORD"
- script:
- - docker run --rm --privileged multiarch/qemu-user-static:4.1.0-1 --reset -p yes
- - TAG="${CI_COMMIT_TAG#v}"
- - TAG="${TAG:-${CI_COMMIT_SHA:0:7}}"
- - echo "Tag ${TAG}"
-
- - |
- if [[ "${IS_HASSIO}" == "YES" ]]; then
- BUILD_FROM=esphome/esphome-hassio-base-${BUILD_ARCH}:${BASE_VERSION}
- BUILD_TO=esphome/esphome-hassio-${BUILD_ARCH}
- DOCKERFILE=docker/Dockerfile.hassio
- else
- BUILD_FROM=esphome/esphome-base-${BUILD_ARCH}:${BASE_VERSION}
- if [[ "${BUILD_ARCH}" == "amd64" ]]; then
- BUILD_TO=esphome/esphome
- else
- BUILD_TO=esphome/esphome-${BUILD_ARCH}
- fi
- DOCKERFILE=docker/Dockerfile
- fi
-
- - |
- docker build \
- --build-arg "BUILD_FROM=${BUILD_FROM}" \
- --build-arg "BUILD_VERSION=${TAG}" \
- --tag "${BUILD_TO}:${TAG}" \
- --file "${DOCKERFILE}" \
- .
- - |
- if [[ "${RELEASE}" = "YES" ]]; then
- echo "Pushing to ${BUILD_TO}:${TAG}"
- docker push "${BUILD_TO}:${TAG}"
- fi
- - |
- if [[ "${LATEST}" = "YES" ]]; then
- echo "Pushing to :latest"
- docker tag ${BUILD_TO}:${TAG} ${BUILD_TO}:latest
- docker push ${BUILD_TO}:latest
- fi
- - |
- if [[ "${BETA}" = "YES" ]]; then
- echo "Pushing to :beta"
- docker tag \
- ${BUILD_TO}:${TAG} \
- ${BUILD_TO}:beta
- docker push ${BUILD_TO}:beta
- fi
- - |
- if [[ "${DEV}" = "YES" ]]; then
- echo "Pushing to :dev"
- docker tag \
- ${BUILD_TO}:${TAG} \
- ${BUILD_TO}:dev
- docker push ${BUILD_TO}:dev
- fi
- services:
- - docker:dind
- tags:
- - docker
- stage: deploy
-
-lint-custom:
- <<: *lint
- script:
- - script/ci-custom.py
-
-lint-python:
- <<: *lint
- script:
- - script/lint-python
-
-lint-tidy:
- <<: *lint
- script:
- - pio init --ide atom
- - script/clang-tidy --all-headers --fix
- - script/ci-suggest-changes
-
-lint-format:
- <<: *lint
- script:
- - script/clang-format -i
- - script/ci-suggest-changes
-
-test1:
- <<: *test
- script:
- - esphome tests/test1.yaml compile
-
-test2:
- <<: *test
- script:
- - esphome tests/test2.yaml compile
-
-test3:
- <<: *test
- script:
- - esphome tests/test3.yaml compile
-
-.deploy-pypi: &deploy-pypi
- <<: *lint
- stage: deploy
- script:
- - pip install twine wheel
- - python setup.py sdist bdist_wheel
- - twine upload dist/*
-
-deploy-release:pypi:
- <<: *deploy-pypi
- only:
- - /^v\d+\.\d+\.\d+$/
- except:
- - /^(?!master).+@/
-
-deploy-beta:pypi:
- <<: *deploy-pypi
- only:
- - /^v\d+\.\d+\.\d+b\d+$/
- except:
- - /^(?!rc).+@/
-
-.latest: &latest
- <<: *docker-base
- only:
- - /^v([0-9\.]+)$/
- except:
- - branches
-
-.beta: &beta
- <<: *docker-base
- only:
- - /^v([0-9\.]+b\d+)$/
- except:
- - branches
-
-.dev: &dev
- <<: *docker-base
- only:
- - dev
-
-aarch64-beta-docker:
- <<: *beta
- variables:
- BETA: "YES"
- BUILD_ARCH: aarch64
- IS_HASSIO: "NO"
- RELEASE: "YES"
-aarch64-beta-hassio:
- <<: *beta
- variables:
- BETA: "YES"
- BUILD_ARCH: aarch64
- IS_HASSIO: "YES"
- RELEASE: "YES"
-aarch64-dev-docker:
- <<: *dev
- variables:
- BUILD_ARCH: aarch64
- DEV: "YES"
- IS_HASSIO: "NO"
-aarch64-dev-hassio:
- <<: *dev
- variables:
- BUILD_ARCH: aarch64
- DEV: "YES"
- IS_HASSIO: "YES"
-aarch64-latest-docker:
- <<: *latest
- variables:
- BETA: "YES"
- BUILD_ARCH: aarch64
- IS_HASSIO: "NO"
- LATEST: "YES"
- RELEASE: "YES"
-aarch64-latest-hassio:
- <<: *latest
- variables:
- BETA: "YES"
- BUILD_ARCH: aarch64
- IS_HASSIO: "YES"
- LATEST: "YES"
- RELEASE: "YES"
-amd64-beta-docker:
- <<: *beta
- variables:
- BETA: "YES"
- BUILD_ARCH: amd64
- IS_HASSIO: "NO"
- RELEASE: "YES"
-amd64-beta-hassio:
- <<: *beta
- variables:
- BETA: "YES"
- BUILD_ARCH: amd64
- IS_HASSIO: "YES"
- RELEASE: "YES"
-amd64-dev-docker:
- <<: *dev
- variables:
- BUILD_ARCH: amd64
- DEV: "YES"
- IS_HASSIO: "NO"
-amd64-dev-hassio:
- <<: *dev
- variables:
- BUILD_ARCH: amd64
- DEV: "YES"
- IS_HASSIO: "YES"
-amd64-latest-docker:
- <<: *latest
- variables:
- BETA: "YES"
- BUILD_ARCH: amd64
- IS_HASSIO: "NO"
- LATEST: "YES"
- RELEASE: "YES"
-amd64-latest-hassio:
- <<: *latest
- variables:
- BETA: "YES"
- BUILD_ARCH: amd64
- IS_HASSIO: "YES"
- LATEST: "YES"
- RELEASE: "YES"
-armv7-beta-docker:
- <<: *beta
- variables:
- BETA: "YES"
- BUILD_ARCH: armv7
- IS_HASSIO: "NO"
- RELEASE: "YES"
-armv7-beta-hassio:
- <<: *beta
- variables:
- BETA: "YES"
- BUILD_ARCH: armv7
- IS_HASSIO: "YES"
- RELEASE: "YES"
-armv7-dev-docker:
- <<: *dev
- variables:
- BUILD_ARCH: armv7
- DEV: "YES"
- IS_HASSIO: "NO"
-armv7-dev-hassio:
- <<: *dev
- variables:
- BUILD_ARCH: armv7
- DEV: "YES"
- IS_HASSIO: "YES"
-armv7-latest-docker:
- <<: *latest
- variables:
- BETA: "YES"
- BUILD_ARCH: armv7
- IS_HASSIO: "NO"
- LATEST: "YES"
- RELEASE: "YES"
-armv7-latest-hassio:
- <<: *latest
- variables:
- BETA: "YES"
- BUILD_ARCH: armv7
- IS_HASSIO: "YES"
- LATEST: "YES"
- RELEASE: "YES"
-i386-beta-docker:
- <<: *beta
- variables:
- BETA: "YES"
- BUILD_ARCH: i386
- IS_HASSIO: "NO"
- RELEASE: "YES"
-i386-beta-hassio:
- <<: *beta
- variables:
- BETA: "YES"
- BUILD_ARCH: i386
- IS_HASSIO: "YES"
- RELEASE: "YES"
-i386-dev-docker:
- <<: *dev
- variables:
- BUILD_ARCH: i386
- DEV: "YES"
- IS_HASSIO: "NO"
-i386-dev-hassio:
- <<: *dev
- variables:
- BUILD_ARCH: i386
- DEV: "YES"
- IS_HASSIO: "YES"
-i386-latest-docker:
- <<: *latest
- variables:
- BETA: "YES"
- BUILD_ARCH: i386
- IS_HASSIO: "NO"
- LATEST: "YES"
- RELEASE: "YES"
-i386-latest-hassio:
- <<: *latest
- variables:
- BETA: "YES"
- BUILD_ARCH: i386
- IS_HASSIO: "YES"
- LATEST: "YES"
- RELEASE: "YES"
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index d57da791fd..a821c21fa7 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,11 +1,27 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
-- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v2.4.0
+ - repo: https://github.com/ambv/black
+ rev: 20.8b1
hooks:
- - id: trailing-whitespace
- - id: end-of-file-fixer
- - id: check-yaml
- - id: check-added-large-files
- - id: flake8
+ - id: black
+ args:
+ - --safe
+ - --quiet
+ files: ^((esphome|script|tests)/.+)?[^/]+\.py$
+ - repo: https://gitlab.com/pycqa/flake8
+ rev: 3.8.4
+ hooks:
+ - id: flake8
+ additional_dependencies:
+ - flake8-docstrings==1.5.0
+ - pydocstyle==5.1.1
+ files: ^(esphome|tests)/.+\.py$
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v3.4.0
+ hooks:
+ - id: no-commit-to-branch
+ args:
+ - --branch=dev
+ - --branch=release
+ - --branch=beta
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index ca0a3082db..0000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,43 +0,0 @@
-sudo: false
-language: python
-python: '3.6'
-install: script/setup
-cache:
- directories:
- - "~/.platformio"
-
-matrix:
- fast_finish: true
- include:
- - python: "3.7"
- env: TARGET=Lint3.7
- script:
- - script/ci-custom.py
- - flake8 esphome
- - pylint esphome
- - python: "3.6"
- env: TARGET=Test3.6
- script:
- - esphome tests/test1.yaml compile
- - esphome tests/test2.yaml compile
- - esphome tests/test3.yaml compile
- - env: TARGET=Cpp-Lint
- dist: trusty
- sudo: required
- addons:
- apt:
- sources:
- - ubuntu-toolchain-r-test
- - llvm-toolchain-trusty-7
- packages:
- - clang-tidy-7
- - clang-format-7
- before_script:
- - pio init --ide atom
- - clang-tidy-7 -version
- - clang-format-7 -version
- - clang-apply-replacements-7 -version
- script:
- - script/clang-tidy --all-headers -j 2 --fix
- - script/clang-format -i -j 2
- - script/ci-suggest-changes
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000000..b6584bc735
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,32 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "run",
+ "type": "shell",
+ "command": "python3 -m esphome dashboard config/",
+ "problemMatcher": []
+ },
+ {
+ "label": "clang-tidy",
+ "type": "shell",
+ "command": "./script/clang-tidy",
+ "problemMatcher": [
+ {
+ "owner": "clang-tidy",
+ "fileLocation": "absolute",
+ "pattern": [
+ {
+ "regexp": "^(.*):(\\d+):(\\d+):\\s+(error):\\s+(.*) \\[([a-z0-9,\\-]+)\\]\\s*$",
+ "file": 1,
+ "line": 2,
+ "column": 3,
+ "severity": 4,
+ "message": 5
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/CODEOWNERS b/CODEOWNERS
new file mode 100644
index 0000000000..452c560938
--- /dev/null
+++ b/CODEOWNERS
@@ -0,0 +1,194 @@
+# This file is generated by script/build_codeowners.py
+# People marked here will be automatically requested for a review
+# when the code that they own is touched.
+#
+# Every time an issue is created with a label corresponding to an integration,
+# the integration's code owner is automatically notified.
+
+# Core Code
+setup.py @esphome/core
+esphome/*.py @esphome/core
+esphome/core/* @esphome/core
+
+# Integrations
+esphome/components/ac_dimmer/* @glmnet
+esphome/components/adc/* @esphome/core
+esphome/components/addressable_light/* @justfalter
+esphome/components/airthings_ble/* @jeromelaban
+esphome/components/airthings_wave_mini/* @ncareau
+esphome/components/airthings_wave_plus/* @jeromelaban
+esphome/components/am43/* @buxtronix
+esphome/components/am43/cover/* @buxtronix
+esphome/components/animation/* @syndlex
+esphome/components/anova/* @buxtronix
+esphome/components/api/* @OttoWinter
+esphome/components/async_tcp/* @OttoWinter
+esphome/components/atc_mithermometer/* @ahpohl
+esphome/components/b_parasite/* @rbaron
+esphome/components/ballu/* @bazuchan
+esphome/components/bang_bang/* @OttoWinter
+esphome/components/binary_sensor/* @esphome/core
+esphome/components/ble_client/* @buxtronix
+esphome/components/bme680_bsec/* @trvrnrth
+esphome/components/button/* @esphome/core
+esphome/components/canbus/* @danielschramm @mvturnho
+esphome/components/cap1188/* @MrEditor97
+esphome/components/captive_portal/* @OttoWinter
+esphome/components/ccs811/* @habbie
+esphome/components/climate/* @esphome/core
+esphome/components/climate_ir/* @glmnet
+esphome/components/color_temperature/* @jesserockz
+esphome/components/coolix/* @glmnet
+esphome/components/cover/* @esphome/core
+esphome/components/cs5460a/* @balrog-kun
+esphome/components/cse7761/* @berfenger
+esphome/components/ct_clamp/* @jesserockz
+esphome/components/current_based/* @djwmarcx
+esphome/components/daly_bms/* @s1lvi0
+esphome/components/dashboard_import/* @esphome/core
+esphome/components/debug/* @OttoWinter
+esphome/components/dfplayer/* @glmnet
+esphome/components/dht/* @OttoWinter
+esphome/components/ds1307/* @badbadc0ffee
+esphome/components/dsmr/* @glmnet @zuidwijk
+esphome/components/esp32/* @esphome/core
+esphome/components/esp32_ble/* @jesserockz
+esphome/components/esp32_ble_server/* @jesserockz
+esphome/components/esp32_camera_web_server/* @ayufan
+esphome/components/esp32_improv/* @jesserockz
+esphome/components/esp8266/* @esphome/core
+esphome/components/exposure_notifications/* @OttoWinter
+esphome/components/ezo/* @ssieb
+esphome/components/fastled_base/* @OttoWinter
+esphome/components/fingerprint_grow/* @OnFreund @loongyh
+esphome/components/globals/* @esphome/core
+esphome/components/gpio/* @esphome/core
+esphome/components/gps/* @coogle
+esphome/components/graph/* @synco
+esphome/components/growatt_solar/* @leeuwte
+esphome/components/havells_solar/* @sourabhjaiswal
+esphome/components/hbridge/fan/* @WeekendWarrior
+esphome/components/hbridge/light/* @DotNetDann
+esphome/components/heatpumpir/* @rob-deutsch
+esphome/components/hitachi_ac424/* @sourabhjaiswal
+esphome/components/homeassistant/* @OttoWinter
+esphome/components/hrxl_maxsonar_wr/* @netmikey
+esphome/components/i2c/* @esphome/core
+esphome/components/improv_serial/* @esphome/core
+esphome/components/inkbird_ibsth1_mini/* @fkirill
+esphome/components/inkplate6/* @jesserockz
+esphome/components/integration/* @OttoWinter
+esphome/components/interval/* @esphome/core
+esphome/components/json/* @OttoWinter
+esphome/components/ledc/* @OttoWinter
+esphome/components/light/* @esphome/core
+esphome/components/logger/* @esphome/core
+esphome/components/ltr390/* @sjtrny
+esphome/components/max7219digit/* @rspaargaren
+esphome/components/mcp23008/* @jesserockz
+esphome/components/mcp23017/* @jesserockz
+esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz
+esphome/components/mcp23s17/* @SenexCrenshaw @jesserockz
+esphome/components/mcp23x08_base/* @jesserockz
+esphome/components/mcp23x17_base/* @jesserockz
+esphome/components/mcp23xxx_base/* @jesserockz
+esphome/components/mcp2515/* @danielschramm @mvturnho
+esphome/components/mcp9808/* @k7hpn
+esphome/components/md5/* @esphome/core
+esphome/components/mdns/* @esphome/core
+esphome/components/midea/* @dudanov
+esphome/components/mitsubishi/* @RubyBailey
+esphome/components/modbus_controller/* @martgras
+esphome/components/modbus_controller/binary_sensor/* @martgras
+esphome/components/modbus_controller/number/* @martgras
+esphome/components/modbus_controller/output/* @martgras
+esphome/components/modbus_controller/sensor/* @martgras
+esphome/components/modbus_controller/switch/* @martgras
+esphome/components/modbus_controller/text_sensor/* @martgras
+esphome/components/network/* @esphome/core
+esphome/components/nextion/* @senexcrenshaw
+esphome/components/nextion/binary_sensor/* @senexcrenshaw
+esphome/components/nextion/sensor/* @senexcrenshaw
+esphome/components/nextion/switch/* @senexcrenshaw
+esphome/components/nextion/text_sensor/* @senexcrenshaw
+esphome/components/nfc/* @jesserockz
+esphome/components/number/* @esphome/core
+esphome/components/ota/* @esphome/core
+esphome/components/output/* @esphome/core
+esphome/components/pid/* @OttoWinter
+esphome/components/pipsolar/* @andreashergert1984
+esphome/components/pm1006/* @habbie
+esphome/components/pmsa003i/* @sjtrny
+esphome/components/pn532/* @OttoWinter @jesserockz
+esphome/components/pn532_i2c/* @OttoWinter @jesserockz
+esphome/components/pn532_spi/* @OttoWinter @jesserockz
+esphome/components/power_supply/* @esphome/core
+esphome/components/preferences/* @esphome/core
+esphome/components/pulse_meter/* @stevebaxter
+esphome/components/pvvx_mithermometer/* @pasiz
+esphome/components/rc522/* @glmnet
+esphome/components/rc522_i2c/* @glmnet
+esphome/components/rc522_spi/* @glmnet
+esphome/components/restart/* @esphome/core
+esphome/components/rf_bridge/* @jesserockz
+esphome/components/rgbct/* @jesserockz
+esphome/components/rtttl/* @glmnet
+esphome/components/safe_mode/* @jsuanet @paulmonigatti
+esphome/components/scd4x/* @sjtrny
+esphome/components/script/* @esphome/core
+esphome/components/sdm_meter/* @jesserockz @polyfaces
+esphome/components/sdp3x/* @Azimath
+esphome/components/selec_meter/* @sourabhjaiswal
+esphome/components/select/* @esphome/core
+esphome/components/sensor/* @esphome/core
+esphome/components/sgp40/* @SenexCrenshaw
+esphome/components/sht4x/* @sjtrny
+esphome/components/shutdown/* @esphome/core @jsuanet
+esphome/components/sim800l/* @glmnet
+esphome/components/sm2135/* @BoukeHaarsma23
+esphome/components/socket/* @esphome/core
+esphome/components/spi/* @esphome/core
+esphome/components/ssd1322_base/* @kbx81
+esphome/components/ssd1322_spi/* @kbx81
+esphome/components/ssd1325_base/* @kbx81
+esphome/components/ssd1325_spi/* @kbx81
+esphome/components/ssd1327_base/* @kbx81
+esphome/components/ssd1327_i2c/* @kbx81
+esphome/components/ssd1327_spi/* @kbx81
+esphome/components/ssd1331_base/* @kbx81
+esphome/components/ssd1331_spi/* @kbx81
+esphome/components/ssd1351_base/* @kbx81
+esphome/components/ssd1351_spi/* @kbx81
+esphome/components/st7735/* @SenexCrenshaw
+esphome/components/st7789v/* @kbx81
+esphome/components/st7920/* @marsjan155
+esphome/components/substitutions/* @esphome/core
+esphome/components/sun/* @OttoWinter
+esphome/components/switch/* @esphome/core
+esphome/components/t6615/* @tylermenezes
+esphome/components/tca9548a/* @andreashergert1984
+esphome/components/tcl112/* @glmnet
+esphome/components/teleinfo/* @0hax
+esphome/components/thermostat/* @kbx81
+esphome/components/time/* @OttoWinter
+esphome/components/tlc5947/* @rnauber
+esphome/components/tm1637/* @glmnet
+esphome/components/tmp102/* @timsavage
+esphome/components/tmp117/* @Azimath
+esphome/components/tof10120/* @wstrzalka
+esphome/components/toshiba/* @kbx81
+esphome/components/tsl2591/* @wjcarpenter
+esphome/components/tuya/binary_sensor/* @jesserockz
+esphome/components/tuya/climate/* @jesserockz
+esphome/components/tuya/number/* @frankiboy1
+esphome/components/tuya/sensor/* @jesserockz
+esphome/components/tuya/switch/* @jesserockz
+esphome/components/tuya/text_sensor/* @dentra
+esphome/components/uart/* @esphome/core
+esphome/components/ultrasonic/* @OttoWinter
+esphome/components/version/* @esphome/core
+esphome/components/web_server_base/* @OttoWinter
+esphome/components/whirlpool/* @glmnet
+esphome/components/xiaomi_lywsd03mmc/* @ahpohl
+esphome/components/xiaomi_mhoc401/* @vevsvevs
+esphome/components/xpt2046/* @numo68
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index c5be227278..b91a3b4f83 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -8,19 +8,19 @@ In the interest of fostering an open and welcoming environment, we as contributo
Examples of behavior that contributes to creating a positive environment include:
-* Using welcoming and inclusive language
-* Being respectful of differing viewpoints and experiences
-* Gracefully accepting constructive criticism
-* Focusing on what is best for the community
-* Showing empathy towards other community members
+- Using welcoming and inclusive language
+- Being respectful of differing viewpoints and experiences
+- Gracefully accepting constructive criticism
+- Focusing on what is best for the community
+- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
-* The use of sexualized language or imagery and unwelcome sexual attention or advances
-* Trolling, insulting/derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or electronic address, without explicit permission
-* Other conduct which could reasonably be considered inappropriate in a professional setting
+- The use of sexualized language or imagery and unwelcome sexual attention or advances
+- Trolling, insulting/derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or electronic address, without explicit permission
+- Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
@@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe
## Enforcement
-Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@otto-winter.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at esphome@nabucasa.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 43d5e79074..e96bb5745b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,10 +1,6 @@
# Contributing to ESPHome
-This python project is responsible for reading in YAML configuration files,
-converting them to C++ code. This code is then converted to a platformio project and compiled
-with [esphome-core](https://github.com/esphome/esphome-core), the C++ framework behind the project.
-
-For a detailed guide, please see https://esphome.io/guides/contributing.html#contributing-to-esphomeyaml
+For a detailed guide, please see https://esphome.io/guides/contributing.html#contributing-to-esphome
Things to note when contributing:
diff --git a/MANIFEST.in b/MANIFEST.in
index cdea2df2a6..0fe80762b3 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,5 +1,6 @@
include LICENSE
include README.md
+include requirements.txt
include esphome/dashboard/templates/*.html
recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE
recursive-include esphome *.cpp *.h *.tcc
diff --git a/README.md b/README.md
index f21e748d40..bb6fb37d3a 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# ESPHome [](https://travis-ci.org/esphome/esphome) [](https://discord.gg/KhAMKrd) [](https://GitHub.com/esphome/esphome/releases/)
+# ESPHome [](https://discord.gg/KhAMKrd) [](https://GitHub.com/esphome/esphome/releases/)
[](https://esphome.io/)
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 11bbeeda2b..62a64c851d 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -1,12 +1,156 @@
-ARG BUILD_FROM=esphome/esphome-base-amd64:2.0.1
-FROM ${BUILD_FROM}
+# Build these with the build.py script
+# Example:
+# python3 docker/build.py --tag dev --arch amd64 --build-type docker build
-COPY . .
-RUN pip3 install --no-cache-dir -e .
+# One of "docker", "hassio"
+ARG BASEIMGTYPE=docker
-ENV USERNAME=""
-ENV PASSWORD=""
+FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.1 AS base-hassio-amd64
+FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.1 AS base-hassio-arm64
+FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.1 AS base-hassio-armv7
+FROM debian:bullseye-20211011-slim AS base-docker-amd64
+FROM debian:bullseye-20211011-slim AS base-docker-arm64
+FROM debian:bullseye-20211011-slim AS base-docker-armv7
+# Use TARGETARCH/TARGETVARIANT defined by docker
+# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
+FROM base-${BASEIMGTYPE}-${TARGETARCH}${TARGETVARIANT} AS base
+
+RUN \
+ apt-get update \
+ # Use pinned versions so that we get updates with build caching
+ && apt-get install -y --no-install-recommends \
+ python3=3.9.2-3 \
+ python3-pip=20.3.4-4 \
+ python3-setuptools=52.0.0-4 \
+ python3-pil=8.1.2+dfsg-0.3 \
+ python3-cryptography=3.3.2-1 \
+ iputils-ping=3:20210202-1 \
+ git=1:2.30.2-1 \
+ curl=7.74.0-1.3+b1 \
+ && rm -rf \
+ /tmp/* \
+ /var/{cache,log}/* \
+ /var/lib/apt/lists/*
+
+ENV \
+ # Fix click python3 lang warning https://click.palletsprojects.com/en/7.x/python3/
+ LANG=C.UTF-8 LC_ALL=C.UTF-8 \
+ # Store globally installed pio libs in /piolibs
+ PLATFORMIO_GLOBALLIB_DIR=/piolibs
+
+RUN \
+ # Ubuntu python3-pip is missing wheel
+ pip3 install --no-cache-dir \
+ wheel==0.36.2 \
+ platformio==5.2.2 \
+ # Change some platformio settings
+ && platformio settings set enable_telemetry No \
+ && platformio settings set check_libraries_interval 1000000 \
+ && platformio settings set check_platformio_interval 1000000 \
+ && platformio settings set check_platforms_interval 1000000 \
+ && mkdir -p /piolibs
+
+
+
+# ======================= docker-type image =======================
+FROM base AS docker
+
+# First install requirements to leverage caching when requirements don't change
+COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
+RUN \
+ pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
+ && /platformio_install_deps.py /platformio.ini
+
+# Copy esphome and install
+COPY . /esphome
+RUN pip3 install --no-cache-dir -e /esphome
+
+# Settings for dashboard
+ENV USERNAME="" PASSWORD=""
+
+# Expose the dashboard to Docker
+EXPOSE 6052
+
+COPY docker/docker_entrypoint.sh /entrypoint.sh
+
+# The directory the user should mount their configuration files to
+VOLUME /config
WORKDIR /config
-ENTRYPOINT ["esphome"]
-CMD ["/config", "dashboard"]
+# Set entrypoint to esphome (via a script) so that the user doesn't have to type 'esphome'
+# in every docker command twice
+ENTRYPOINT ["/entrypoint.sh"]
+# When no arguments given, start the dashboard in the workdir
+CMD ["dashboard", "/config"]
+
+
+
+
+# ======================= hassio-type image =======================
+FROM base AS hassio
+
+RUN \
+ apt-get update \
+ # Use pinned versions so that we get updates with build caching
+ && apt-get install -y --no-install-recommends \
+ nginx=1.18.0-6.1 \
+ && rm -rf \
+ /tmp/* \
+ /var/{cache,log}/* \
+ /var/lib/apt/lists/*
+
+ARG BUILD_VERSION=dev
+
+# Copy root filesystem
+COPY docker/hassio-rootfs/ /
+
+# First install requirements to leverage caching when requirements don't change
+COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
+RUN \
+ pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
+ && /platformio_install_deps.py /platformio.ini
+
+# Copy esphome and install
+COPY . /esphome
+RUN pip3 install --no-cache-dir -e /esphome
+
+# Labels
+LABEL \
+ io.hass.name="ESPHome" \
+ io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \
+ io.hass.type="addon" \
+ io.hass.version="${BUILD_VERSION}"
+ # io.hass.arch is inherited from addon-debian-base
+
+
+
+
+# ======================= lint-type image =======================
+FROM base AS lint
+
+ENV \
+ PLATFORMIO_CORE_DIR=/esphome/.temp/platformio
+
+RUN \
+ apt-get update \
+ # Use pinned versions so that we get updates with build caching
+ && apt-get install -y --no-install-recommends \
+ clang-format-11=1:11.0.1-2 \
+ clang-tidy-11=1:11.0.1-2 \
+ patch=2.7.6-7 \
+ software-properties-common=0.96.20.2-2.1 \
+ nano=5.4-2 \
+ build-essential=12.9 \
+ python3-dev=3.9.2-3 \
+ && rm -rf \
+ /tmp/* \
+ /var/{cache,log}/* \
+ /var/lib/apt/lists/*
+
+COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini /
+RUN \
+ pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt -r /requirements_test.txt \
+ && /platformio_install_deps.py /platformio.ini
+
+VOLUME ["/esphome"]
+WORKDIR /esphome
diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev
deleted file mode 100644
index a3871e2513..0000000000
--- a/docker/Dockerfile.dev
+++ /dev/null
@@ -1,13 +0,0 @@
-FROM esphome/esphome-base-amd64:2.0.1
-
-COPY . .
-
-RUN apt-get update \
- && apt-get install -y --no-install-recommends \
- python3-wheel \
- net-tools \
- && apt-get clean \
- && rm -rf /var/lib/apt/lists/*
-
-WORKDIR /workspaces
-ENV SHELL /bin/bash
diff --git a/docker/Dockerfile.hassio b/docker/Dockerfile.hassio
deleted file mode 100644
index e5c9625680..0000000000
--- a/docker/Dockerfile.hassio
+++ /dev/null
@@ -1,19 +0,0 @@
-ARG BUILD_FROM
-FROM ${BUILD_FROM}
-
-# Copy root filesystem
-COPY docker/rootfs/ /
-COPY setup.py setup.cfg MANIFEST.in /opt/esphome/
-COPY esphome /opt/esphome/esphome
-
-RUN pip3 install --no-cache-dir -e /opt/esphome
-
-# Build arguments
-ARG BUILD_VERSION=dev
-
-# Labels
-LABEL \
- io.hass.name="ESPHome" \
- io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \
- io.hass.type="addon" \
- io.hass.version=${BUILD_VERSION}
diff --git a/docker/Dockerfile.lint b/docker/Dockerfile.lint
deleted file mode 100644
index 2d77502dc2..0000000000
--- a/docker/Dockerfile.lint
+++ /dev/null
@@ -1,18 +0,0 @@
-FROM esphome/esphome-base-amd64:2.0.1
-
-RUN \
- apt-get update \
- && apt-get install -y --no-install-recommends \
- clang-format-7 \
- clang-tidy-7 \
- patch \
- && rm -rf \
- /tmp/* \
- /var/{cache,log}/* \
- /var/lib/apt/lists/*
-
-COPY requirements_test.txt /requirements_test.txt
-RUN pip3 install --no-cache-dir wheel && pip3 install --no-cache-dir -r /requirements_test.txt
-
-VOLUME ["/esphome"]
-WORKDIR /esphome
diff --git a/docker/build.py b/docker/build.py
new file mode 100755
index 0000000000..1157d8287a
--- /dev/null
+++ b/docker/build.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python3
+from dataclasses import dataclass
+import subprocess
+import argparse
+from platform import machine
+import shlex
+import re
+import sys
+
+
+CHANNEL_DEV = 'dev'
+CHANNEL_BETA = 'beta'
+CHANNEL_RELEASE = 'release'
+CHANNELS = [CHANNEL_DEV, CHANNEL_BETA, CHANNEL_RELEASE]
+
+ARCH_AMD64 = 'amd64'
+ARCH_ARMV7 = 'armv7'
+ARCH_AARCH64 = 'aarch64'
+ARCHS = [ARCH_AMD64, ARCH_ARMV7, ARCH_AARCH64]
+
+TYPE_DOCKER = 'docker'
+TYPE_HA_ADDON = 'ha-addon'
+TYPE_LINT = 'lint'
+TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT]
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument("--tag", type=str, required=True, help="The main docker tag to push to. If a version number also adds latest and/or beta tag")
+parser.add_argument("--arch", choices=ARCHS, required=False, help="The architecture to build for")
+parser.add_argument("--build-type", choices=TYPES, required=True, help="The type of build to run")
+parser.add_argument("--dry-run", action="store_true", help="Don't run any commands, just print them")
+subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True)
+build_parser = subparsers.add_parser("build", help="Build the image")
+build_parser.add_argument("--push", help="Also push the images", action="store_true")
+manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images")
+
+
+@dataclass(frozen=True)
+class DockerParams:
+ build_to: str
+ manifest_to: str
+ baseimgtype: str
+ platform: str
+ target: str
+
+ @classmethod
+ def for_type_arch(cls, build_type, arch):
+ prefix = {
+ TYPE_DOCKER: "esphome/esphome",
+ TYPE_HA_ADDON: "esphome/esphome-hassio",
+ TYPE_LINT: "esphome/esphome-lint"
+ }[build_type]
+ build_to = f"{prefix}-{arch}"
+ baseimgtype = {
+ TYPE_DOCKER: "docker",
+ TYPE_HA_ADDON: "hassio",
+ TYPE_LINT: "docker",
+ }[build_type]
+ platform = {
+ ARCH_AMD64: "linux/amd64",
+ ARCH_ARMV7: "linux/arm/v7",
+ ARCH_AARCH64: "linux/arm64",
+ }[arch]
+ target = {
+ TYPE_DOCKER: "docker",
+ TYPE_HA_ADDON: "hassio",
+ TYPE_LINT: "lint",
+ }[build_type]
+ return cls(
+ build_to=build_to,
+ manifest_to=prefix,
+ baseimgtype=baseimgtype,
+ platform=platform,
+ target=target,
+ )
+
+
+def main():
+ args = parser.parse_args()
+
+ def run_command(*cmd, ignore_error: bool = False):
+ print(f"$ {shlex.join(list(cmd))}")
+ if not args.dry_run:
+ rc = subprocess.call(list(cmd))
+ if rc != 0 and not ignore_error:
+ print("Command failed")
+ sys.exit(1)
+
+ # detect channel from tag
+ match = re.match(r'^\d+\.\d+(?:\.\d+)?(b\d+)?$', args.tag)
+ if match is None:
+ channel = CHANNEL_DEV
+ elif match.group(1) is None:
+ channel = CHANNEL_RELEASE
+ else:
+ channel = CHANNEL_BETA
+
+ tags_to_push = [args.tag]
+ if channel == CHANNEL_DEV:
+ tags_to_push.append("dev")
+ elif channel == CHANNEL_BETA:
+ tags_to_push.append("beta")
+ elif channel == CHANNEL_RELEASE:
+ # Additionally push to beta
+ tags_to_push.append("beta")
+ tags_to_push.append("latest")
+
+ if args.command == "build":
+ # 1. pull cache image
+ params = DockerParams.for_type_arch(args.build_type, args.arch)
+ cache_tag = {
+ CHANNEL_DEV: "cache-dev",
+ CHANNEL_BETA: "cache-beta",
+ CHANNEL_RELEASE: "cache-latest",
+ }[channel]
+ cache_img = f"ghcr.io/{params.build_to}:{cache_tag}"
+
+ imgs = [f"{params.build_to}:{tag}" for tag in tags_to_push]
+ imgs += [f"ghcr.io/{params.build_to}:{tag}" for tag in tags_to_push]
+
+ # 3. build
+ cmd = [
+ "docker", "buildx", "build",
+ "--build-arg", f"BASEIMGTYPE={params.baseimgtype}",
+ "--build-arg", f"BUILD_VERSION={args.tag}",
+ "--cache-from", f"type=registry,ref={cache_img}",
+ "--file", "docker/Dockerfile",
+ "--platform", params.platform,
+ "--target", params.target,
+ ]
+ for img in imgs:
+ cmd += ["--tag", img]
+ if args.push:
+ cmd += ["--push", "--cache-to", f"type=registry,ref={cache_img},mode=max"]
+
+ run_command(*cmd, ".")
+ elif args.command == "manifest":
+ manifest = DockerParams.for_type_arch(args.build_type, ARCH_AMD64).manifest_to
+
+ targets = [f"{manifest}:{tag}" for tag in tags_to_push]
+ targets += [f"ghcr.io/{manifest}:{tag}" for tag in tags_to_push]
+ # 1. Create manifests
+ for target in targets:
+ cmd = ["docker", "manifest", "create", target]
+ for arch in ARCHS:
+ src = f"{DockerParams.for_type_arch(args.build_type, arch).build_to}:{args.tag}"
+ if target.startswith("ghcr.io"):
+ src = f"ghcr.io/{src}"
+ cmd.append(src)
+ run_command(*cmd)
+ # 2. Push manifests
+ for target in targets:
+ run_command(
+ "docker", "manifest", "push", target
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/docker/docker_entrypoint.sh b/docker/docker_entrypoint.sh
new file mode 100755
index 0000000000..75d5e0b7b5
--- /dev/null
+++ b/docker/docker_entrypoint.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+# If /cache is mounted, use that as PIO's coredir
+# otherwise use path in /config (so that PIO packages aren't downloaded on each compile)
+
+if [[ -d /cache ]]; then
+ pio_cache_base=/cache/platformio
+else
+ pio_cache_base=/config/.esphome/platformio
+fi
+
+if [[ ! -d "${pio_cache_base}" ]]; then
+ echo "Creating cache directory ${pio_cache_base}"
+ echo "You can change this behavior by mounting a directory to the container's /cache directory."
+ mkdir -p "${pio_cache_base}"
+fi
+
+# we can't set core_dir, because the settings file is stored in `core_dir/appstate.json`
+# setting `core_dir` would therefore prevent pio from accessing
+export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
+export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
+export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
+
+exec esphome "$@"
diff --git a/docker/rootfs/etc/cont-init.d/10-requirements.sh b/docker/hassio-rootfs/etc/cont-init.d/10-requirements.sh
similarity index 100%
rename from docker/rootfs/etc/cont-init.d/10-requirements.sh
rename to docker/hassio-rootfs/etc/cont-init.d/10-requirements.sh
diff --git a/docker/rootfs/etc/cont-init.d/20-nginx.sh b/docker/hassio-rootfs/etc/cont-init.d/20-nginx.sh
similarity index 100%
rename from docker/rootfs/etc/cont-init.d/20-nginx.sh
rename to docker/hassio-rootfs/etc/cont-init.d/20-nginx.sh
diff --git a/docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh b/docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh
new file mode 100644
index 0000000000..1073a2fa45
--- /dev/null
+++ b/docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/with-contenv bashio
+# ==============================================================================
+# Community Hass.io Add-ons: ESPHome
+# This files creates all directories used by esphome
+# ==============================================================================
+
+pio_cache_base=/data/cache/platformio
+
+mkdir -p "${pio_cache_base}"
diff --git a/docker/rootfs/etc/nginx/includes/mime.types b/docker/hassio-rootfs/etc/nginx/includes/mime.types
similarity index 100%
rename from docker/rootfs/etc/nginx/includes/mime.types
rename to docker/hassio-rootfs/etc/nginx/includes/mime.types
diff --git a/docker/rootfs/etc/nginx/includes/proxy_params.conf b/docker/hassio-rootfs/etc/nginx/includes/proxy_params.conf
similarity index 100%
rename from docker/rootfs/etc/nginx/includes/proxy_params.conf
rename to docker/hassio-rootfs/etc/nginx/includes/proxy_params.conf
diff --git a/docker/rootfs/etc/nginx/includes/server_params.conf b/docker/hassio-rootfs/etc/nginx/includes/server_params.conf
similarity index 100%
rename from docker/rootfs/etc/nginx/includes/server_params.conf
rename to docker/hassio-rootfs/etc/nginx/includes/server_params.conf
diff --git a/docker/rootfs/etc/nginx/includes/ssl_params.conf b/docker/hassio-rootfs/etc/nginx/includes/ssl_params.conf
similarity index 100%
rename from docker/rootfs/etc/nginx/includes/ssl_params.conf
rename to docker/hassio-rootfs/etc/nginx/includes/ssl_params.conf
diff --git a/docker/rootfs/etc/nginx/nginx.conf b/docker/hassio-rootfs/etc/nginx/nginx.conf
old mode 100755
new mode 100644
similarity index 100%
rename from docker/rootfs/etc/nginx/nginx.conf
rename to docker/hassio-rootfs/etc/nginx/nginx.conf
diff --git a/docker/rootfs/etc/nginx/servers/direct-ssl.disabled b/docker/hassio-rootfs/etc/nginx/servers/direct-ssl.disabled
similarity index 100%
rename from docker/rootfs/etc/nginx/servers/direct-ssl.disabled
rename to docker/hassio-rootfs/etc/nginx/servers/direct-ssl.disabled
diff --git a/docker/rootfs/etc/nginx/servers/direct.disabled b/docker/hassio-rootfs/etc/nginx/servers/direct.disabled
similarity index 100%
rename from docker/rootfs/etc/nginx/servers/direct.disabled
rename to docker/hassio-rootfs/etc/nginx/servers/direct.disabled
diff --git a/docker/rootfs/etc/nginx/servers/ingress.conf b/docker/hassio-rootfs/etc/nginx/servers/ingress.conf
similarity index 100%
rename from docker/rootfs/etc/nginx/servers/ingress.conf
rename to docker/hassio-rootfs/etc/nginx/servers/ingress.conf
diff --git a/docker/rootfs/etc/services.d/esphome/finish b/docker/hassio-rootfs/etc/services.d/esphome/finish
similarity index 100%
rename from docker/rootfs/etc/services.d/esphome/finish
rename to docker/hassio-rootfs/etc/services.d/esphome/finish
diff --git a/docker/rootfs/etc/services.d/esphome/run b/docker/hassio-rootfs/etc/services.d/esphome/run
similarity index 61%
rename from docker/rootfs/etc/services.d/esphome/run
rename to docker/hassio-rootfs/etc/services.d/esphome/run
index 6257bec6a3..a0f20d63d6 100755
--- a/docker/rootfs/etc/services.d/esphome/run
+++ b/docker/hassio-rootfs/etc/services.d/esphome/run
@@ -22,5 +22,14 @@ if bashio::config.has_value 'relative_url'; then
export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url')
fi
+pio_cache_base=/data/cache/platformio
+# we can't set core_dir, because the settings file is stored in `core_dir/appstate.json`
+# setting `core_dir` would therefore prevent pio from accessing
+export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
+export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
+export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
+
+export PLATFORMIO_GLOBALLIB_DIR=/piolibs
+
bashio::log.info "Starting ESPHome dashboard..."
-exec esphome /config/esphome dashboard --socket /var/run/esphome.sock --hassio
+exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --hassio
diff --git a/docker/rootfs/etc/services.d/nginx/finish b/docker/hassio-rootfs/etc/services.d/nginx/finish
similarity index 100%
rename from docker/rootfs/etc/services.d/nginx/finish
rename to docker/hassio-rootfs/etc/services.d/nginx/finish
diff --git a/docker/rootfs/etc/services.d/nginx/run b/docker/hassio-rootfs/etc/services.d/nginx/run
similarity index 100%
rename from docker/rootfs/etc/services.d/nginx/run
rename to docker/hassio-rootfs/etc/services.d/nginx/run
diff --git a/docker/platformio_install_deps.py b/docker/platformio_install_deps.py
new file mode 100755
index 0000000000..c7b11cf321
--- /dev/null
+++ b/docker/platformio_install_deps.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+# This script is used in the docker containers to preinstall
+# all platformio libraries in the global storage
+
+import configparser
+import subprocess
+import sys
+
+config = configparser.ConfigParser(inline_comment_prefixes=(';', ))
+config.read(sys.argv[1])
+
+libs = []
+# Extract from every lib_deps key in all sections
+for section in config.sections():
+ conf = config[section]
+ if "lib_deps" not in conf:
+ continue
+ for lib_dep in conf["lib_deps"].splitlines():
+ if not lib_dep:
+ # Empty line or comment
+ continue
+ if lib_dep.startswith("${"):
+ # Extending from another section
+ continue
+ if "@" not in lib_dep:
+ # No version pinned, this is an internal lib
+ continue
+ libs.append(lib_dep)
+
+subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs])
diff --git a/docker/rootfs/etc/cont-init.d/30-esphome.sh b/docker/rootfs/etc/cont-init.d/30-esphome.sh
deleted file mode 100644
index d9a80cde2e..0000000000
--- a/docker/rootfs/etc/cont-init.d/30-esphome.sh
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/with-contenv bashio
-# ==============================================================================
-# Community Hass.io Add-ons: ESPHome
-# This files installs the user ESPHome version if specified
-# ==============================================================================
-
-declare esphome_version
-
-if bashio::config.has_value 'esphome_version'; then
- esphome_version=$(bashio::config 'esphome_version')
- if [[ $esphome_version == *":"* ]]; then
- IFS=':' read -r -a array <<< "$esphome_version"
- username=${array[0]}
- ref=${array[1]}
- else
- username="esphome"
- ref=$esphome_version
- fi
- full_url="https://github.com/${username}/esphome/archive/${ref}.zip"
- bashio::log.info "Installing esphome version '${esphome_version}' (${full_url})..."
- pip3 install -U --no-cache-dir "${full_url}" \
- || bashio::exit.nok "Failed installing esphome pinned version."
-fi
diff --git a/docker/rootfs/etc/cont-init.d/40-migrate.sh b/docker/rootfs/etc/cont-init.d/40-migrate.sh
deleted file mode 100644
index 88e8be26b9..0000000000
--- a/docker/rootfs/etc/cont-init.d/40-migrate.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/usr/bin/with-contenv bashio
-# ==============================================================================
-# Community Hass.io Add-ons: ESPHome
-# This files migrates the esphome config directory from the old path
-# ==============================================================================
-
-if [[ ! -d /config/esphome && -d /config/esphomeyaml ]]; then
- echo "Moving config directory from /config/esphomeyaml to /config/esphome"
- mv /config/esphomeyaml /config/esphome
- mv /config/esphome/.esphomeyaml /config/esphome/.esphome
-fi
diff --git a/esphome/__main__.py b/esphome/__main__.py
index 73723dfa00..6f57791480 100644
--- a/esphome/__main__.py
+++ b/esphome/__main__.py
@@ -8,33 +8,39 @@ from datetime import datetime
from esphome import const, writer, yaml_util
import esphome.codegen as cg
from esphome.config import iter_components, read_config, strip_default_ids
-from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \
- CONF_PASSWORD, CONF_PORT, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS
-from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
-from esphome.helpers import color, indent
-from esphome.util import run_external_command, run_external_process, safe_print, list_yaml_files
+from esphome.const import (
+ CONF_BAUD_RATE,
+ CONF_BROKER,
+ CONF_DEASSERT_RTS_DTR,
+ CONF_LOGGER,
+ CONF_OTA,
+ CONF_PASSWORD,
+ CONF_PORT,
+ CONF_ESPHOME,
+ CONF_PLATFORMIO_OPTIONS,
+ SECRETS_FILES,
+)
+from esphome.core import CORE, EsphomeError, coroutine
+from esphome.helpers import indent
+from esphome.util import (
+ run_external_command,
+ run_external_process,
+ safe_print,
+ list_yaml_files,
+ get_serial_ports,
+)
+from esphome.log import color, setup_log, Fore
_LOGGER = logging.getLogger(__name__)
-def get_serial_ports():
- # from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py
- from serial.tools.list_ports import comports
- result = []
- for port, desc, info in comports(include_links=True):
- if not port:
- continue
- if "VID:PID" in info:
- result.append((port, desc))
- result.sort(key=lambda x: x[0])
- return result
-
-
def choose_prompt(options):
if not options:
- raise EsphomeError("Found no valid options for upload/logging, please make sure relevant "
- "sections (ota, mqtt, ...) are in your configuration and/or the device "
- "is plugged in.")
+ raise EsphomeError(
+ "Found no valid options for upload/logging, please make sure relevant "
+ "sections (ota, api, mqtt, ...) are in your configuration and/or the "
+ "device is plugged in."
+ )
if len(options) == 1:
return options[0][1]
@@ -44,7 +50,7 @@ def choose_prompt(options):
safe_print(f" [{i+1}] {desc}")
while True:
- opt = input('(number): ')
+ opt = input("(number): ")
if opt in options:
opt = options.index(opt)
break
@@ -54,22 +60,22 @@ def choose_prompt(options):
raise ValueError
break
except ValueError:
- safe_print(color('red', f"Invalid option: '{opt}'"))
+ safe_print(color(Fore.RED, f"Invalid option: '{opt}'"))
return options[opt - 1][1]
def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api):
options = []
- for res, desc in get_serial_ports():
- options.append((f"{res} ({desc})", res))
- if (show_ota and 'ota' in CORE.config) or (show_api and 'api' in CORE.config):
+ for port in get_serial_ports():
+ options.append((f"{port.path} ({port.description})", port.path))
+ if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config):
options.append((f"Over The Air ({CORE.address})", CORE.address))
- if default == 'OTA':
+ if default == "OTA":
return CORE.address
- if show_mqtt and 'mqtt' in CORE.config:
- options.append(("MQTT ({})".format(CORE.config['mqtt'][CONF_BROKER]), 'MQTT'))
- if default == 'OTA':
- return 'MQTT'
+ if show_mqtt and "mqtt" in CORE.config:
+ options.append((f"MQTT ({CORE.config['mqtt'][CONF_BROKER]})", "MQTT"))
+ if default == "OTA":
+ return "MQTT"
if default is not None:
return default
if check_default is not None and check_default in [opt[1] for opt in options]:
@@ -78,11 +84,11 @@ def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api
def get_port_type(port):
- if port.startswith('/') or port.startswith('COM'):
- return 'SERIAL'
- if port == 'MQTT':
- return 'MQTT'
- return 'NETWORK'
+ if port.startswith("/") or port.startswith("COM"):
+ return "SERIAL"
+ if port == "MQTT":
+ return "MQTT"
+ return "NETWORK"
def run_miniterm(config, port):
@@ -92,45 +98,69 @@ def run_miniterm(config, port):
if CONF_LOGGER not in config:
_LOGGER.info("Logger is not enabled. Not starting UART logs.")
return
- baud_rate = config['logger'][CONF_BAUD_RATE]
+ baud_rate = config["logger"][CONF_BAUD_RATE]
if baud_rate == 0:
_LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.")
+ return
_LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
backtrace_state = False
- with serial.Serial(port, baudrate=baud_rate) as ser:
+ ser = serial.Serial()
+ ser.baudrate = baud_rate
+ ser.port = port
+
+ # We can't set to False by default since it leads to toggling and hence
+ # ESP32 resets on some platforms.
+ if config["logger"][CONF_DEASSERT_RTS_DTR]:
+ ser.dtr = False
+ ser.rts = False
+
+ with ser:
while True:
try:
raw = ser.readline()
except serial.SerialException:
_LOGGER.error("Serial port closed!")
return
- line = raw.replace(b'\r', b'').replace(b'\n', b'').decode('utf8', 'backslashreplace')
- time = datetime.now().time().strftime('[%H:%M:%S]')
+ line = (
+ raw.replace(b"\r", b"")
+ .replace(b"\n", b"")
+ .decode("utf8", "backslashreplace")
+ )
+ time = datetime.now().time().strftime("[%H:%M:%S]")
message = time + line
safe_print(message)
backtrace_state = platformio_api.process_stacktrace(
- config, line, backtrace_state=backtrace_state)
+ config, line, backtrace_state=backtrace_state
+ )
def wrap_to_code(name, comp):
coro = coroutine(comp.to_code)
@functools.wraps(comp.to_code)
- @coroutine_with_priority(coro.priority)
- def wrapped(conf):
+ async def wrapped(conf):
cg.add(cg.LineComment(f"{name}:"))
if comp.config_schema is not None:
conf_str = yaml_util.dump(conf)
- conf_str = conf_str.replace('//', '')
+ conf_str = conf_str.replace("//", "")
+ # remove tailing \ to avoid multi-line comment warning
+ conf_str = conf_str.replace("\\\n", "\n")
cg.add(cg.LineComment(indent(conf_str)))
- yield coro(conf)
+ await coro(conf)
+ if hasattr(coro, "priority"):
+ wrapped.priority = coro.priority
return wrapped
def write_cpp(config):
+ generate_cpp_contents(config)
+ return write_cpp_file()
+
+
+def generate_cpp_contents(config):
_LOGGER.info("Generating C++ source...")
for name, component, conf in iter_components(CORE.config):
@@ -140,6 +170,8 @@ def write_cpp(config):
CORE.flush_tasks()
+
+def write_cpp_file():
writer.write_platformio_project()
code_s = indent(CORE.cpp_main_section)
@@ -151,20 +183,60 @@ def compile_program(args, config):
from esphome import platformio_api
_LOGGER.info("Compiling app...")
- return platformio_api.run_compile(config, CORE.verbose)
+ rc = platformio_api.run_compile(config, CORE.verbose)
+ if rc != 0:
+ return rc
+ idedata = platformio_api.get_idedata(config)
+ return 0 if idedata is not None else 1
def upload_using_esptool(config, port):
- path = CORE.firmware_bin
- first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get('upload_speed', 460800)
+ from esphome import platformio_api
+
+ first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
+ "upload_speed", 460800
+ )
def run_esptool(baud_rate):
- cmd = ['esptool.py', '--before', 'default_reset', '--after', 'hard_reset',
- '--baud', str(baud_rate),
- '--chip', 'esp8266', '--port', port, 'write_flash', '0x0', path]
+ idedata = platformio_api.get_idedata(config)
- if os.environ.get('ESPHOME_USE_SUBPROCESS') is None:
+ firmware_offset = "0x10000" if CORE.is_esp32 else "0x0"
+ flash_images = [
+ platformio_api.FlashImage(
+ path=idedata.firmware_bin_path, offset=firmware_offset
+ ),
+ *idedata.extra_flash_images,
+ ]
+
+ mcu = "esp8266"
+ if CORE.is_esp32:
+ from esphome.components.esp32 import get_esp32_variant
+
+ mcu = get_esp32_variant().lower()
+
+ cmd = [
+ "esptool.py",
+ "--before",
+ "default_reset",
+ "--after",
+ "hard_reset",
+ "--baud",
+ str(baud_rate),
+ "--port",
+ port,
+ "--chip",
+ mcu,
+ "write_flash",
+ "-z",
+ "--flash_size",
+ "detect",
+ ]
+ for img in flash_images:
+ cmd += [img.offset, img.path]
+
+ if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
import esptool
+
# pylint: disable=protected-access
return run_external_command(esptool._main, *cmd)
@@ -174,46 +246,48 @@ def upload_using_esptool(config, port):
if rc == 0 or first_baudrate == 115200:
return rc
# Try with 115200 baud rate, with some serial chips the faster baud rates do not work well
- _LOGGER.info("Upload with baud rate %s failed. Trying again with baud rate 115200.",
- first_baudrate)
+ _LOGGER.info(
+ "Upload with baud rate %s failed. Trying again with baud rate 115200.",
+ first_baudrate,
+ )
return run_esptool(115200)
def upload_program(config, args, host):
# if upload is to a serial port use platformio, otherwise assume ota
- if get_port_type(host) == 'SERIAL':
- from esphome import platformio_api
-
- if CORE.is_esp8266:
- return upload_using_esptool(config, host)
- return platformio_api.run_upload(config, CORE.verbose, host)
+ if get_port_type(host) == "SERIAL":
+ return upload_using_esptool(config, host)
from esphome import espota2
if CONF_OTA not in config:
- raise EsphomeError("Cannot upload Over the Air as the config does not include the ota: "
- "component")
+ raise EsphomeError(
+ "Cannot upload Over the Air as the config does not include the ota: "
+ "component"
+ )
ota_conf = config[CONF_OTA]
remote_port = ota_conf[CONF_PORT]
- password = ota_conf[CONF_PASSWORD]
+ password = ota_conf.get(CONF_PASSWORD, "")
return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
def show_logs(config, args, port):
- if 'logger' not in config:
+ if "logger" not in config:
raise EsphomeError("Logger is not configured!")
- if get_port_type(port) == 'SERIAL':
+ if get_port_type(port) == "SERIAL":
run_miniterm(config, port)
return 0
- if get_port_type(port) == 'NETWORK' and 'api' in config:
- from esphome.api.client import run_logs
+ if get_port_type(port) == "NETWORK" and "api" in config:
+ from esphome.components.api.client import run_logs
return run_logs(config, port)
- if get_port_type(port) == 'MQTT' and 'mqtt' in config:
+ if get_port_type(port) == "MQTT" and "mqtt" in config:
from esphome import mqtt
- return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id)
+ return mqtt.show_logs(
+ config, args.topic, args.username, args.password, args.client_id
+ )
raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)")
@@ -221,46 +295,15 @@ def show_logs(config, args, port):
def clean_mqtt(config, args):
from esphome import mqtt
- return mqtt.clear_topic(config, args.topic, args.username, args.password, args.client_id)
-
-
-def setup_log(debug=False, quiet=False):
- if debug:
- log_level = logging.DEBUG
- CORE.verbose = True
- elif quiet:
- log_level = logging.CRITICAL
- else:
- log_level = logging.INFO
- logging.basicConfig(level=log_level)
- fmt = "%(levelname)s %(message)s"
- colorfmt = f"%(log_color)s{fmt}%(reset)s"
- datefmt = '%H:%M:%S'
-
- logging.getLogger('urllib3').setLevel(logging.WARNING)
-
- try:
- from colorlog import ColoredFormatter
- logging.getLogger().handlers[0].setFormatter(ColoredFormatter(
- colorfmt,
- datefmt=datefmt,
- reset=True,
- log_colors={
- 'DEBUG': 'cyan',
- 'INFO': 'green',
- 'WARNING': 'yellow',
- 'ERROR': 'red',
- 'CRITICAL': 'red',
- }
- ))
- except ImportError:
- pass
+ return mqtt.clear_topic(
+ config, args.topic, args.username, args.password, args.client_id
+ )
def command_wizard(args):
from esphome import wizard
- return wizard.wizard(args.configuration[0])
+ return wizard.wizard(args.configuration)
def command_config(args, config):
@@ -274,7 +317,8 @@ def command_config(args, config):
def command_vscode(args):
from esphome import vscode
- CORE.config_path = args.configuration[0]
+ logging.disable(logging.INFO)
+ logging.disable(logging.WARNING)
vscode.read_config(args)
@@ -293,8 +337,13 @@ def command_compile(args, config):
def command_upload(args, config):
- port = choose_upload_log_host(default=args.upload_port, check_default=None,
- show_ota=True, show_mqtt=False, show_api=False)
+ port = choose_upload_log_host(
+ default=args.device,
+ check_default=None,
+ show_ota=True,
+ show_mqtt=False,
+ show_api=False,
+ )
exit_code = upload_program(config, args, port)
if exit_code != 0:
return exit_code
@@ -303,8 +352,13 @@ def command_upload(args, config):
def command_logs(args, config):
- port = choose_upload_log_host(default=args.serial_port, check_default=None,
- show_ota=False, show_mqtt=True, show_api=True)
+ port = choose_upload_log_host(
+ default=args.device,
+ check_default=None,
+ show_ota=False,
+ show_mqtt=True,
+ show_api=True,
+ )
return show_logs(config, args, port)
@@ -316,16 +370,26 @@ def command_run(args, config):
if exit_code != 0:
return exit_code
_LOGGER.info("Successfully compiled program.")
- port = choose_upload_log_host(default=args.upload_port, check_default=None,
- show_ota=True, show_mqtt=False, show_api=True)
+ port = choose_upload_log_host(
+ default=args.device,
+ check_default=None,
+ show_ota=True,
+ show_mqtt=False,
+ show_api=True,
+ )
exit_code = upload_program(config, args, port)
if exit_code != 0:
return exit_code
_LOGGER.info("Successfully uploaded program.")
if args.no_logs:
return 0
- port = choose_upload_log_host(default=args.upload_port, check_default=port,
- show_ota=False, show_mqtt=True, show_api=True)
+ port = choose_upload_log_host(
+ default=args.device,
+ check_default=port,
+ show_ota=False,
+ show_mqtt=True,
+ show_api=True,
+ )
return show_logs(config, args, port)
@@ -364,7 +428,7 @@ def command_update_all(args):
import click
success = {}
- files = list_yaml_files(args.configuration[0])
+ files = list_yaml_files(args.configuration)
twidth = 60
def print_bar(middle_text):
@@ -374,167 +438,371 @@ def command_update_all(args):
click.echo(f"{half_line}{middle_text}{half_line}")
for f in files:
- print("Updating {}".format(color('cyan', f)))
- print('-' * twidth)
+ print(f"Updating {color(Fore.CYAN, f)}")
+ print("-" * twidth)
print()
- rc = run_external_process('esphome', '--dashboard', f, 'run', '--no-logs', '--upload-port',
- 'OTA')
+ rc = run_external_process(
+ "esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
+ )
if rc == 0:
- print_bar("[{}] {}".format(color('bold_green', 'SUCCESS'), f))
+ print_bar(f"[{color(Fore.BOLD_GREEN, 'SUCCESS')}] {f}")
success[f] = True
else:
- print_bar("[{}] {}".format(color('bold_red', 'ERROR'), f))
+ print_bar(f"[{color(Fore.BOLD_RED, 'ERROR')}] {f}")
success[f] = False
print()
print()
print()
- print_bar('[{}]'.format(color('bold_white', 'SUMMARY')))
+ print_bar(f"[{color(Fore.BOLD_WHITE, 'SUMMARY')}]")
failed = 0
for f in files:
if success[f]:
- print(" - {}: {}".format(f, color('green', 'SUCCESS')))
+ print(f" - {f}: {color(Fore.GREEN, 'SUCCESS')}")
else:
- print(" - {}: {}".format(f, color('bold_red', 'FAILED')))
+ print(f" - {f}: {color(Fore.BOLD_RED, 'FAILED')}")
failed += 1
return failed
+def command_idedata(args, config):
+ from esphome import platformio_api
+ import json
+
+ logging.disable(logging.INFO)
+ logging.disable(logging.WARNING)
+
+ idedata = platformio_api.get_idedata(config)
+ if idedata is None:
+ return 1
+
+ print(json.dumps(idedata.raw, indent=2) + "\n")
+ return 0
+
+
PRE_CONFIG_ACTIONS = {
- 'wizard': command_wizard,
- 'version': command_version,
- 'dashboard': command_dashboard,
- 'vscode': command_vscode,
- 'update-all': command_update_all,
+ "wizard": command_wizard,
+ "version": command_version,
+ "dashboard": command_dashboard,
+ "vscode": command_vscode,
+ "update-all": command_update_all,
}
POST_CONFIG_ACTIONS = {
- 'config': command_config,
- 'compile': command_compile,
- 'upload': command_upload,
- 'logs': command_logs,
- 'run': command_run,
- 'clean-mqtt': command_clean_mqtt,
- 'mqtt-fingerprint': command_mqtt_fingerprint,
- 'clean': command_clean,
+ "config": command_config,
+ "compile": command_compile,
+ "upload": command_upload,
+ "logs": command_logs,
+ "run": command_run,
+ "clean-mqtt": command_clean_mqtt,
+ "mqtt-fingerprint": command_mqtt_fingerprint,
+ "clean": command_clean,
+ "idedata": command_idedata,
}
def parse_args(argv):
- parser = argparse.ArgumentParser(description=f'ESPHome v{const.__version__}')
- parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.",
- action='store_true')
- parser.add_argument('-q', '--quiet', help="Disable all esphome logs.",
- action='store_true')
- parser.add_argument('--dashboard', help=argparse.SUPPRESS, action='store_true')
- parser.add_argument('configuration', help='Your YAML configuration file.', nargs='*')
+ options_parser = argparse.ArgumentParser(add_help=False)
+ options_parser.add_argument(
+ "-v", "--verbose", help="Enable verbose ESPHome logs.", action="store_true"
+ )
+ options_parser.add_argument(
+ "-q", "--quiet", help="Disable all ESPHome logs.", action="store_true"
+ )
+ options_parser.add_argument(
+ "--dashboard", help=argparse.SUPPRESS, action="store_true"
+ )
+ options_parser.add_argument(
+ "-s",
+ "--substitution",
+ nargs=2,
+ action="append",
+ help="Add a substitution",
+ metavar=("key", "value"),
+ )
- subparsers = parser.add_subparsers(help='Commands', dest='command')
+ parser = argparse.ArgumentParser(
+ description=f"ESPHome v{const.__version__}", parents=[options_parser]
+ )
+
+ mqtt_options = argparse.ArgumentParser(add_help=False)
+ mqtt_options.add_argument("--topic", help="Manually set the MQTT topic.")
+ mqtt_options.add_argument("--username", help="Manually set the MQTT username.")
+ mqtt_options.add_argument("--password", help="Manually set the MQTT password.")
+ mqtt_options.add_argument("--client-id", help="Manually set the MQTT client id.")
+
+ subparsers = parser.add_subparsers(
+ help="Command to run:", dest="command", metavar="command"
+ )
subparsers.required = True
- subparsers.add_parser('config', help='Validate the configuration and spit it out.')
- parser_compile = subparsers.add_parser('compile',
- help='Read the configuration and compile a program.')
- parser_compile.add_argument('--only-generate',
- help="Only generate source code, do not compile.",
- action='store_true')
+ parser_config = subparsers.add_parser(
+ "config", help="Validate the configuration and spit it out."
+ )
+ parser_config.add_argument(
+ "configuration", help="Your YAML configuration file(s).", nargs="+"
+ )
- parser_upload = subparsers.add_parser('upload', help='Validate the configuration '
- 'and upload the latest binary.')
- parser_upload.add_argument('--upload-port', help="Manually specify the upload port to use. "
- "For example /dev/cu.SLAB_USBtoUART.")
+ parser_compile = subparsers.add_parser(
+ "compile", help="Read the configuration and compile a program."
+ )
+ parser_compile.add_argument(
+ "configuration", help="Your YAML configuration file(s).", nargs="+"
+ )
+ parser_compile.add_argument(
+ "--only-generate",
+ help="Only generate source code, do not compile.",
+ action="store_true",
+ )
- parser_logs = subparsers.add_parser('logs', help='Validate the configuration '
- 'and show all MQTT logs.')
- parser_logs.add_argument('--topic', help='Manually set the topic to subscribe to.')
- parser_logs.add_argument('--username', help='Manually set the username.')
- parser_logs.add_argument('--password', help='Manually set the password.')
- parser_logs.add_argument('--client-id', help='Manually set the client id.')
- parser_logs.add_argument('--serial-port', help="Manually specify a serial port to use"
- "For example /dev/cu.SLAB_USBtoUART.")
+ parser_upload = subparsers.add_parser(
+ "upload", help="Validate the configuration and upload the latest binary."
+ )
+ parser_upload.add_argument(
+ "configuration", help="Your YAML configuration file(s).", nargs="+"
+ )
+ parser_upload.add_argument(
+ "--device",
+ help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
+ )
- parser_run = subparsers.add_parser('run', help='Validate the configuration, create a binary, '
- 'upload it, and start MQTT logs.')
- parser_run.add_argument('--upload-port', help="Manually specify the upload port/ip to use. "
- "For example /dev/cu.SLAB_USBtoUART.")
- parser_run.add_argument('--no-logs', help='Disable starting MQTT logs.',
- action='store_true')
- parser_run.add_argument('--topic', help='Manually set the topic to subscribe to for logs.')
- parser_run.add_argument('--username', help='Manually set the MQTT username for logs.')
- parser_run.add_argument('--password', help='Manually set the MQTT password for logs.')
- parser_run.add_argument('--client-id', help='Manually set the client id for logs.')
+ parser_logs = subparsers.add_parser(
+ "logs",
+ help="Validate the configuration and show all logs.",
+ parents=[mqtt_options],
+ )
+ parser_logs.add_argument(
+ "configuration", help="Your YAML configuration file.", nargs=1
+ )
+ parser_logs.add_argument(
+ "--device",
+ help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
+ )
- parser_clean = subparsers.add_parser('clean-mqtt', help="Helper to clear an MQTT topic from "
- "retain messages.")
- parser_clean.add_argument('--topic', help='Manually set the topic to subscribe to.')
- parser_clean.add_argument('--username', help='Manually set the username.')
- parser_clean.add_argument('--password', help='Manually set the password.')
- parser_clean.add_argument('--client-id', help='Manually set the client id.')
+ parser_run = subparsers.add_parser(
+ "run",
+ help="Validate the configuration, create a binary, upload it, and start logs.",
+ parents=[mqtt_options],
+ )
+ parser_run.add_argument(
+ "configuration", help="Your YAML configuration file(s).", nargs="+"
+ )
+ parser_run.add_argument(
+ "--device",
+ help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
+ )
+ parser_run.add_argument(
+ "--no-logs", help="Disable starting logs.", action="store_true"
+ )
- subparsers.add_parser('wizard', help="A helpful setup wizard that will guide "
- "you through setting up esphome.")
+ parser_clean = subparsers.add_parser(
+ "clean-mqtt",
+ help="Helper to clear retained messages from an MQTT topic.",
+ parents=[mqtt_options],
+ )
+ parser_clean.add_argument(
+ "configuration", help="Your YAML configuration file(s).", nargs="+"
+ )
- subparsers.add_parser('mqtt-fingerprint', help="Get the SSL fingerprint from a MQTT broker.")
+ parser_wizard = subparsers.add_parser(
+ "wizard",
+ help="A helpful setup wizard that will guide you through setting up ESPHome.",
+ )
+ parser_wizard.add_argument("configuration", help="Your YAML configuration file.")
- subparsers.add_parser('version', help="Print the esphome version and exit.")
+ parser_fingerprint = subparsers.add_parser(
+ "mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker."
+ )
+ parser_fingerprint.add_argument(
+ "configuration", help="Your YAML configuration file(s).", nargs="+"
+ )
- subparsers.add_parser('clean', help="Delete all temporary build files.")
+ subparsers.add_parser("version", help="Print the ESPHome version and exit.")
- dashboard = subparsers.add_parser('dashboard',
- help="Create a simple web server for a dashboard.")
- dashboard.add_argument("--port", help="The HTTP port to open connections on. Defaults to 6052.",
- type=int, default=6052)
- dashboard.add_argument("--username", help="The optional username to require "
- "for authentication.",
- type=str, default='')
- dashboard.add_argument("--password", help="The optional password to require "
- "for authentication.",
- type=str, default='')
- dashboard.add_argument("--open-ui", help="Open the dashboard UI in a browser.",
- action='store_true')
- dashboard.add_argument("--hassio",
- help=argparse.SUPPRESS,
- action="store_true")
- dashboard.add_argument("--socket",
- help="Make the dashboard serve under a unix socket", type=str)
+ parser_clean = subparsers.add_parser(
+ "clean", help="Delete all temporary build files."
+ )
+ parser_clean.add_argument(
+ "configuration", help="Your YAML configuration file(s).", nargs="+"
+ )
- vscode = subparsers.add_parser('vscode', help=argparse.SUPPRESS)
- vscode.add_argument('--ace', action='store_true')
+ parser_dashboard = subparsers.add_parser(
+ "dashboard", help="Create a simple web server for a dashboard."
+ )
+ parser_dashboard.add_argument(
+ "configuration", help="Your YAML configuration file directory."
+ )
+ parser_dashboard.add_argument(
+ "--port",
+ help="The HTTP port to open connections on. Defaults to 6052.",
+ type=int,
+ default=6052,
+ )
+ parser_dashboard.add_argument(
+ "--address",
+ help="The address to bind to.",
+ type=str,
+ default="0.0.0.0",
+ )
+ parser_dashboard.add_argument(
+ "--username",
+ help="The optional username to require for authentication.",
+ type=str,
+ default="",
+ )
+ parser_dashboard.add_argument(
+ "--password",
+ help="The optional password to require for authentication.",
+ type=str,
+ default="",
+ )
+ parser_dashboard.add_argument(
+ "--open-ui", help="Open the dashboard UI in a browser.", action="store_true"
+ )
+ parser_dashboard.add_argument(
+ "--hassio", help=argparse.SUPPRESS, action="store_true"
+ )
+ parser_dashboard.add_argument(
+ "--socket", help="Make the dashboard serve under a unix socket", type=str
+ )
- subparsers.add_parser('update-all', help=argparse.SUPPRESS)
+ parser_vscode = subparsers.add_parser("vscode")
+ parser_vscode.add_argument("configuration", help="Your YAML configuration file.")
+ parser_vscode.add_argument("--ace", action="store_true")
- return parser.parse_args(argv[1:])
+ parser_update = subparsers.add_parser("update-all")
+ parser_update.add_argument(
+ "configuration", help="Your YAML configuration file directories.", nargs="+"
+ )
+
+ parser_idedata = subparsers.add_parser("idedata")
+ parser_idedata.add_argument(
+ "configuration", help="Your YAML configuration file(s).", nargs=1
+ )
+
+ # Keep backward compatibility with the old command line format of
+ # esphome .
+ #
+ # Unfortunately this can't be done by adding another configuration argument to the
+ # main config parser, as argparse is greedy when parsing arguments, so in regular
+ # usage it'll eat the command as the configuration argument and error out out
+ # because it can't parse the configuration as a command.
+ #
+ # Instead, if parsing using the current format fails, construct an ad-hoc parser
+ # that doesn't actually process the arguments, but parses them enough to let us
+ # figure out if the old format is used. In that case, swap the command and
+ # configuration in the arguments and retry with the normal parser (and raise
+ # a deprecation warning).
+ arguments = argv[1:]
+
+ # On Python 3.9+ we can simply set exit_on_error=False in the constructor
+ def _raise(x):
+ raise argparse.ArgumentError(None, x)
+
+ # First, try new-style parsing, but don't exit in case of failure
+ try:
+ # duplicate parser so that we can use the original one to raise errors later on
+ current_parser = argparse.ArgumentParser(add_help=False, parents=[parser])
+ current_parser.set_defaults(deprecated_argv_suggestion=None)
+ current_parser.error = _raise
+ return current_parser.parse_args(arguments)
+ except argparse.ArgumentError:
+ pass
+
+ # Second, try compat parsing and rearrange the command-line if it succeeds
+ # Disable argparse's built-in help option and add it manually to prevent this
+ # parser from printing the help messagefor the old format when invoked with -h.
+ compat_parser = argparse.ArgumentParser(parents=[options_parser], add_help=False)
+ compat_parser.add_argument("-h", "--help", action="store_true")
+ compat_parser.add_argument("configuration", nargs="*")
+ compat_parser.add_argument(
+ "command",
+ choices=[
+ "config",
+ "compile",
+ "upload",
+ "logs",
+ "run",
+ "clean-mqtt",
+ "wizard",
+ "mqtt-fingerprint",
+ "version",
+ "clean",
+ "dashboard",
+ "vscode",
+ "update-all",
+ ],
+ )
+
+ try:
+ compat_parser.error = _raise
+ result, unparsed = compat_parser.parse_known_args(argv[1:])
+ last_option = len(arguments) - len(unparsed) - 1 - len(result.configuration)
+ unparsed = [
+ "--device" if arg in ("--upload-port", "--serial-port") else arg
+ for arg in unparsed
+ ]
+ arguments = (
+ arguments[0:last_option]
+ + [result.command]
+ + result.configuration
+ + unparsed
+ )
+ deprecated_argv_suggestion = arguments
+ except argparse.ArgumentError:
+ # old-style parsing failed, don't suggest any argument
+ deprecated_argv_suggestion = None
+
+ # Finally, run the new-style parser again with the possibly swapped arguments,
+ # and let it error out if the command is unparsable.
+ parser.set_defaults(deprecated_argv_suggestion=deprecated_argv_suggestion)
+ return parser.parse_args(arguments)
def run_esphome(argv):
args = parse_args(argv)
CORE.dashboard = args.dashboard
- setup_log(args.verbose, args.quiet)
- if args.command != 'version' and not args.configuration:
- _LOGGER.error("Missing configuration parameter, see esphome --help.")
- return 1
+ setup_log(
+ args.verbose,
+ args.quiet,
+ # Show timestamp for dashboard access logs
+ args.command == "dashboard",
+ )
+ if args.deprecated_argv_suggestion is not None and args.command != "vscode":
+ _LOGGER.warning(
+ "Calling ESPHome with the configuration before the command is deprecated "
+ "and will be removed in the future. "
+ )
+ _LOGGER.warning("Please instead use:")
+ _LOGGER.warning(" esphome %s", " ".join(args.deprecated_argv_suggestion))
- if sys.version_info < (3, 6, 0):
- _LOGGER.error("You're running ESPHome with Python <3.6. ESPHome is no longer compatible "
- "with this Python version. Please reinstall ESPHome with Python 3.6+")
+ if sys.version_info < (3, 7, 0):
+ _LOGGER.error(
+ "You're running ESPHome with Python <3.7. ESPHome is no longer compatible "
+ "with this Python version. Please reinstall ESPHome with Python 3.7+"
+ )
return 1
if args.command in PRE_CONFIG_ACTIONS:
try:
return PRE_CONFIG_ACTIONS[args.command](args)
except EsphomeError as e:
- _LOGGER.error(e)
+ _LOGGER.error(e, exc_info=args.verbose)
return 1
for conf_path in args.configuration:
+ if any(os.path.basename(conf_path) == x for x in SECRETS_FILES):
+ _LOGGER.warning("Skipping secrets file %s", conf_path)
+ continue
+
CORE.config_path = conf_path
CORE.dashboard = args.dashboard
- config = read_config()
+ config = read_config(dict(args.substitution) if args.substitution else {})
if config is None:
- return 1
+ return 2
CORE.config = config
if args.command not in POST_CONFIG_ACTIONS:
@@ -543,7 +811,7 @@ def run_esphome(argv):
try:
rc = POST_CONFIG_ACTIONS[args.command](args, config)
except EsphomeError as e:
- _LOGGER.error(e)
+ _LOGGER.error(e, exc_info=args.verbose)
return 1
if rc != 0:
return rc
diff --git a/esphome/api/api_pb2.py b/esphome/api/api_pb2.py
deleted file mode 100644
index c6c8741f01..0000000000
--- a/esphome/api/api_pb2.py
+++ /dev/null
@@ -1,2485 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by the protocol buffer compiler. DO NOT EDIT!
-# source: api.proto
-
-import sys
-_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
-from google.protobuf.internal import enum_type_wrapper
-from google.protobuf import descriptor as _descriptor
-from google.protobuf import message as _message
-from google.protobuf import reflection as _reflection
-from google.protobuf import symbol_database as _symbol_database
-# @@protoc_insertion_point(imports)
-
-_sym_db = _symbol_database.Default()
-
-
-
-
-DESCRIPTOR = _descriptor.FileDescriptor(
- name='api.proto',
- package='',
- syntax='proto3',
- serialized_options=None,
- serialized_pb=_b('\n\tapi.proto\"#\n\x0cHelloRequest\x12\x13\n\x0b\x63lient_info\x18\x01 \x01(\t\"Z\n\rHelloResponse\x12\x19\n\x11\x61pi_version_major\x18\x01 \x01(\r\x12\x19\n\x11\x61pi_version_minor\x18\x02 \x01(\r\x12\x13\n\x0bserver_info\x18\x03 \x01(\t\"\"\n\x0e\x43onnectRequest\x12\x10\n\x08password\x18\x01 \x01(\t\"+\n\x0f\x43onnectResponse\x12\x18\n\x10invalid_password\x18\x01 \x01(\x08\"\x13\n\x11\x44isconnectRequest\"\x14\n\x12\x44isconnectResponse\"\r\n\x0bPingRequest\"\x0e\n\x0cPingResponse\"\x13\n\x11\x44\x65viceInfoRequest\"\xad\x01\n\x12\x44\x65viceInfoResponse\x12\x15\n\ruses_password\x18\x01 \x01(\x08\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0bmac_address\x18\x03 \x01(\t\x12\x1c\n\x14\x65sphome_core_version\x18\x04 \x01(\t\x12\x18\n\x10\x63ompilation_time\x18\x05 \x01(\t\x12\r\n\x05model\x18\x06 \x01(\t\x12\x16\n\x0ehas_deep_sleep\x18\x07 \x01(\x08\"\x15\n\x13ListEntitiesRequest\"\x9a\x01\n ListEntitiesBinarySensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x14\n\x0c\x64\x65vice_class\x18\x05 \x01(\t\x12\x1f\n\x17is_status_binary_sensor\x18\x06 \x01(\x08\"s\n\x19ListEntitiesCoverResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x15\n\ris_optimistic\x18\x05 \x01(\x08\"\x90\x01\n\x17ListEntitiesFanResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x1c\n\x14supports_oscillation\x18\x05 \x01(\x08\x12\x16\n\x0esupports_speed\x18\x06 \x01(\x08\"\x8a\x02\n\x19ListEntitiesLightResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x1b\n\x13supports_brightness\x18\x05 \x01(\x08\x12\x14\n\x0csupports_rgb\x18\x06 \x01(\x08\x12\x1c\n\x14supports_white_value\x18\x07 \x01(\x08\x12\"\n\x1asupports_color_temperature\x18\x08 \x01(\x08\x12\x12\n\nmin_mireds\x18\t \x01(\x02\x12\x12\n\nmax_mireds\x18\n \x01(\x02\x12\x0f\n\x07\x65\x66\x66\x65\x63ts\x18\x0b \x03(\t\"\xa3\x01\n\x1aListEntitiesSensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t\x12\x1b\n\x13unit_of_measurement\x18\x06 \x01(\t\x12\x19\n\x11\x61\x63\x63uracy_decimals\x18\x07 \x01(\x05\"\x7f\n\x1aListEntitiesSwitchResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t\x12\x12\n\noptimistic\x18\x06 \x01(\x08\"o\n\x1eListEntitiesTextSensorResponse\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\x07\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tunique_id\x18\x04 \x01(\t\x12\x0c\n\x04icon\x18\x05 \x01(\t\"\x1a\n\x18ListEntitiesDoneResponse\"\x18\n\x16SubscribeStatesRequest\"7\n\x19\x42inarySensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\"t\n\x12\x43overStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12-\n\x05state\x18\x02 \x01(\x0e\x32\x1e.CoverStateResponse.CoverState\"\"\n\nCoverState\x12\x08\n\x04OPEN\x10\x00\x12\n\n\x06\x43LOSED\x10\x01\"]\n\x10\x46\x61nStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\x12\x13\n\x0boscillating\x18\x03 \x01(\x08\x12\x18\n\x05speed\x18\x04 \x01(\x0e\x32\t.FanSpeed\"\xa8\x01\n\x12LightStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\x12\x12\n\nbrightness\x18\x03 \x01(\x02\x12\x0b\n\x03red\x18\x04 \x01(\x02\x12\r\n\x05green\x18\x05 \x01(\x02\x12\x0c\n\x04\x62lue\x18\x06 \x01(\x02\x12\r\n\x05white\x18\x07 \x01(\x02\x12\x19\n\x11\x63olor_temperature\x18\x08 \x01(\x02\x12\x0e\n\x06\x65\x66\x66\x65\x63t\x18\t \x01(\t\"1\n\x13SensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x02\"1\n\x13SwitchStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\"5\n\x17TextSensorStateResponse\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\t\"\x98\x01\n\x13\x43overCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\x32\n\x07\x63ommand\x18\x03 \x01(\x0e\x32!.CoverCommandRequest.CoverCommand\"-\n\x0c\x43overCommand\x12\x08\n\x04OPEN\x10\x00\x12\t\n\x05\x43LOSE\x10\x01\x12\x08\n\x04STOP\x10\x02\"\x9d\x01\n\x11\x46\x61nCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\r\n\x05state\x18\x03 \x01(\x08\x12\x11\n\thas_speed\x18\x04 \x01(\x08\x12\x18\n\x05speed\x18\x05 \x01(\x0e\x32\t.FanSpeed\x12\x17\n\x0fhas_oscillating\x18\x06 \x01(\x08\x12\x13\n\x0boscillating\x18\x07 \x01(\x08\"\x95\x03\n\x13LightCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\x11\n\thas_state\x18\x02 \x01(\x08\x12\r\n\x05state\x18\x03 \x01(\x08\x12\x16\n\x0ehas_brightness\x18\x04 \x01(\x08\x12\x12\n\nbrightness\x18\x05 \x01(\x02\x12\x0f\n\x07has_rgb\x18\x06 \x01(\x08\x12\x0b\n\x03red\x18\x07 \x01(\x02\x12\r\n\x05green\x18\x08 \x01(\x02\x12\x0c\n\x04\x62lue\x18\t \x01(\x02\x12\x11\n\thas_white\x18\n \x01(\x08\x12\r\n\x05white\x18\x0b \x01(\x02\x12\x1d\n\x15has_color_temperature\x18\x0c \x01(\x08\x12\x19\n\x11\x63olor_temperature\x18\r \x01(\x02\x12\x1d\n\x15has_transition_length\x18\x0e \x01(\x08\x12\x19\n\x11transition_length\x18\x0f \x01(\r\x12\x18\n\x10has_flash_length\x18\x10 \x01(\x08\x12\x14\n\x0c\x66lash_length\x18\x11 \x01(\r\x12\x12\n\nhas_effect\x18\x12 \x01(\x08\x12\x0e\n\x06\x65\x66\x66\x65\x63t\x18\x13 \x01(\t\"2\n\x14SwitchCommandRequest\x12\x0b\n\x03key\x18\x01 \x01(\x07\x12\r\n\x05state\x18\x02 \x01(\x08\"E\n\x14SubscribeLogsRequest\x12\x18\n\x05level\x18\x01 \x01(\x0e\x32\t.LogLevel\x12\x13\n\x0b\x64ump_config\x18\x02 \x01(\x08\"d\n\x15SubscribeLogsResponse\x12\x18\n\x05level\x18\x01 \x01(\x0e\x32\t.LogLevel\x12\x0b\n\x03tag\x18\x02 \x01(\t\x12\x0f\n\x07message\x18\x03 \x01(\t\x12\x13\n\x0bsend_failed\x18\x04 \x01(\x08\"\x1e\n\x1cSubscribeServiceCallsRequest\"\xdf\x02\n\x13ServiceCallResponse\x12\x0f\n\x07service\x18\x01 \x01(\t\x12,\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32\x1e.ServiceCallResponse.DataEntry\x12=\n\rdata_template\x18\x03 \x03(\x0b\x32&.ServiceCallResponse.DataTemplateEntry\x12\x36\n\tvariables\x18\x04 \x03(\x0b\x32#.ServiceCallResponse.VariablesEntry\x1a+\n\tDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x33\n\x11\x44\x61taTemplateEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x30\n\x0eVariablesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"%\n#SubscribeHomeAssistantStatesRequest\"8\n#SubscribeHomeAssistantStateResponse\x12\x11\n\tentity_id\x18\x01 \x01(\t\">\n\x1aHomeAssistantStateResponse\x12\x11\n\tentity_id\x18\x01 \x01(\t\x12\r\n\x05state\x18\x02 \x01(\t\"\x10\n\x0eGetTimeRequest\"(\n\x0fGetTimeResponse\x12\x15\n\repoch_seconds\x18\x01 \x01(\x07*)\n\x08\x46\x61nSpeed\x12\x07\n\x03LOW\x10\x00\x12\n\n\x06MEDIUM\x10\x01\x12\x08\n\x04HIGH\x10\x02*]\n\x08LogLevel\x12\x08\n\x04NONE\x10\x00\x12\t\n\x05\x45RROR\x10\x01\x12\x08\n\x04WARN\x10\x02\x12\x08\n\x04INFO\x10\x03\x12\t\n\x05\x44\x45\x42UG\x10\x04\x12\x0b\n\x07VERBOSE\x10\x05\x12\x10\n\x0cVERY_VERBOSE\x10\x06\x62\x06proto3')
-)
-
-_FANSPEED = _descriptor.EnumDescriptor(
- name='FanSpeed',
- full_name='FanSpeed',
- filename=None,
- file=DESCRIPTOR,
- values=[
- _descriptor.EnumValueDescriptor(
- name='LOW', index=0, number=0,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='MEDIUM', index=1, number=1,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='HIGH', index=2, number=2,
- serialized_options=None,
- type=None),
- ],
- containing_type=None,
- serialized_options=None,
- serialized_start=3822,
- serialized_end=3863,
-)
-_sym_db.RegisterEnumDescriptor(_FANSPEED)
-
-FanSpeed = enum_type_wrapper.EnumTypeWrapper(_FANSPEED)
-_LOGLEVEL = _descriptor.EnumDescriptor(
- name='LogLevel',
- full_name='LogLevel',
- filename=None,
- file=DESCRIPTOR,
- values=[
- _descriptor.EnumValueDescriptor(
- name='NONE', index=0, number=0,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='ERROR', index=1, number=1,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='WARN', index=2, number=2,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='INFO', index=3, number=3,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='DEBUG', index=4, number=4,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='VERBOSE', index=5, number=5,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='VERY_VERBOSE', index=6, number=6,
- serialized_options=None,
- type=None),
- ],
- containing_type=None,
- serialized_options=None,
- serialized_start=3865,
- serialized_end=3958,
-)
-_sym_db.RegisterEnumDescriptor(_LOGLEVEL)
-
-LogLevel = enum_type_wrapper.EnumTypeWrapper(_LOGLEVEL)
-LOW = 0
-MEDIUM = 1
-HIGH = 2
-NONE = 0
-ERROR = 1
-WARN = 2
-INFO = 3
-DEBUG = 4
-VERBOSE = 5
-VERY_VERBOSE = 6
-
-
-_COVERSTATERESPONSE_COVERSTATE = _descriptor.EnumDescriptor(
- name='CoverState',
- full_name='CoverStateResponse.CoverState',
- filename=None,
- file=DESCRIPTOR,
- values=[
- _descriptor.EnumValueDescriptor(
- name='OPEN', index=0, number=0,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='CLOSED', index=1, number=1,
- serialized_options=None,
- type=None),
- ],
- containing_type=None,
- serialized_options=None,
- serialized_start=1808,
- serialized_end=1842,
-)
-_sym_db.RegisterEnumDescriptor(_COVERSTATERESPONSE_COVERSTATE)
-
-_COVERCOMMANDREQUEST_COVERCOMMAND = _descriptor.EnumDescriptor(
- name='CoverCommand',
- full_name='CoverCommandRequest.CoverCommand',
- filename=None,
- file=DESCRIPTOR,
- values=[
- _descriptor.EnumValueDescriptor(
- name='OPEN', index=0, number=0,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='CLOSE', index=1, number=1,
- serialized_options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='STOP', index=2, number=2,
- serialized_options=None,
- type=None),
- ],
- containing_type=None,
- serialized_options=None,
- serialized_start=2375,
- serialized_end=2420,
-)
-_sym_db.RegisterEnumDescriptor(_COVERCOMMANDREQUEST_COVERCOMMAND)
-
-
-_HELLOREQUEST = _descriptor.Descriptor(
- name='HelloRequest',
- full_name='HelloRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='client_info', full_name='HelloRequest.client_info', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=13,
- serialized_end=48,
-)
-
-
-_HELLORESPONSE = _descriptor.Descriptor(
- name='HelloResponse',
- full_name='HelloResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='api_version_major', full_name='HelloResponse.api_version_major', index=0,
- number=1, type=13, cpp_type=3, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='api_version_minor', full_name='HelloResponse.api_version_minor', index=1,
- number=2, type=13, cpp_type=3, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='server_info', full_name='HelloResponse.server_info', index=2,
- number=3, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=50,
- serialized_end=140,
-)
-
-
-_CONNECTREQUEST = _descriptor.Descriptor(
- name='ConnectRequest',
- full_name='ConnectRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='password', full_name='ConnectRequest.password', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=142,
- serialized_end=176,
-)
-
-
-_CONNECTRESPONSE = _descriptor.Descriptor(
- name='ConnectResponse',
- full_name='ConnectResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='invalid_password', full_name='ConnectResponse.invalid_password', index=0,
- number=1, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=178,
- serialized_end=221,
-)
-
-
-_DISCONNECTREQUEST = _descriptor.Descriptor(
- name='DisconnectRequest',
- full_name='DisconnectRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=223,
- serialized_end=242,
-)
-
-
-_DISCONNECTRESPONSE = _descriptor.Descriptor(
- name='DisconnectResponse',
- full_name='DisconnectResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=244,
- serialized_end=264,
-)
-
-
-_PINGREQUEST = _descriptor.Descriptor(
- name='PingRequest',
- full_name='PingRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=266,
- serialized_end=279,
-)
-
-
-_PINGRESPONSE = _descriptor.Descriptor(
- name='PingResponse',
- full_name='PingResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=281,
- serialized_end=295,
-)
-
-
-_DEVICEINFOREQUEST = _descriptor.Descriptor(
- name='DeviceInfoRequest',
- full_name='DeviceInfoRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=297,
- serialized_end=316,
-)
-
-
-_DEVICEINFORESPONSE = _descriptor.Descriptor(
- name='DeviceInfoResponse',
- full_name='DeviceInfoResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='uses_password', full_name='DeviceInfoResponse.uses_password', index=0,
- number=1, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='name', full_name='DeviceInfoResponse.name', index=1,
- number=2, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='mac_address', full_name='DeviceInfoResponse.mac_address', index=2,
- number=3, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='esphome_core_version', full_name='DeviceInfoResponse.esphome_core_version', index=3,
- number=4, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='compilation_time', full_name='DeviceInfoResponse.compilation_time', index=4,
- number=5, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='model', full_name='DeviceInfoResponse.model', index=5,
- number=6, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='has_deep_sleep', full_name='DeviceInfoResponse.has_deep_sleep', index=6,
- number=7, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=319,
- serialized_end=492,
-)
-
-
-_LISTENTITIESREQUEST = _descriptor.Descriptor(
- name='ListEntitiesRequest',
- full_name='ListEntitiesRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=494,
- serialized_end=515,
-)
-
-
-_LISTENTITIESBINARYSENSORRESPONSE = _descriptor.Descriptor(
- name='ListEntitiesBinarySensorResponse',
- full_name='ListEntitiesBinarySensorResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='object_id', full_name='ListEntitiesBinarySensorResponse.object_id', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='key', full_name='ListEntitiesBinarySensorResponse.key', index=1,
- number=2, type=7, cpp_type=3, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='name', full_name='ListEntitiesBinarySensorResponse.name', index=2,
- number=3, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='unique_id', full_name='ListEntitiesBinarySensorResponse.unique_id', index=3,
- number=4, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='device_class', full_name='ListEntitiesBinarySensorResponse.device_class', index=4,
- number=5, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='is_status_binary_sensor', full_name='ListEntitiesBinarySensorResponse.is_status_binary_sensor', index=5,
- number=6, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=518,
- serialized_end=672,
-)
-
-
-_LISTENTITIESCOVERRESPONSE = _descriptor.Descriptor(
- name='ListEntitiesCoverResponse',
- full_name='ListEntitiesCoverResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='object_id', full_name='ListEntitiesCoverResponse.object_id', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='key', full_name='ListEntitiesCoverResponse.key', index=1,
- number=2, type=7, cpp_type=3, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='name', full_name='ListEntitiesCoverResponse.name', index=2,
- number=3, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='unique_id', full_name='ListEntitiesCoverResponse.unique_id', index=3,
- number=4, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='is_optimistic', full_name='ListEntitiesCoverResponse.is_optimistic', index=4,
- number=5, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=674,
- serialized_end=789,
-)
-
-
-_LISTENTITIESFANRESPONSE = _descriptor.Descriptor(
- name='ListEntitiesFanResponse',
- full_name='ListEntitiesFanResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='object_id', full_name='ListEntitiesFanResponse.object_id', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='key', full_name='ListEntitiesFanResponse.key', index=1,
- number=2, type=7, cpp_type=3, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='name', full_name='ListEntitiesFanResponse.name', index=2,
- number=3, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='unique_id', full_name='ListEntitiesFanResponse.unique_id', index=3,
- number=4, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='supports_oscillation', full_name='ListEntitiesFanResponse.supports_oscillation', index=4,
- number=5, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='supports_speed', full_name='ListEntitiesFanResponse.supports_speed', index=5,
- number=6, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=792,
- serialized_end=936,
-)
-
-
-_LISTENTITIESLIGHTRESPONSE = _descriptor.Descriptor(
- name='ListEntitiesLightResponse',
- full_name='ListEntitiesLightResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='object_id', full_name='ListEntitiesLightResponse.object_id', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='key', full_name='ListEntitiesLightResponse.key', index=1,
- number=2, type=7, cpp_type=3, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='name', full_name='ListEntitiesLightResponse.name', index=2,
- number=3, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='unique_id', full_name='ListEntitiesLightResponse.unique_id', index=3,
- number=4, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='supports_brightness', full_name='ListEntitiesLightResponse.supports_brightness', index=4,
- number=5, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='supports_rgb', full_name='ListEntitiesLightResponse.supports_rgb', index=5,
- number=6, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='supports_white_value', full_name='ListEntitiesLightResponse.supports_white_value', index=6,
- number=7, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='supports_color_temperature', full_name='ListEntitiesLightResponse.supports_color_temperature', index=7,
- number=8, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='min_mireds', full_name='ListEntitiesLightResponse.min_mireds', index=8,
- number=9, type=2, cpp_type=6, label=1,
- has_default_value=False, default_value=float(0),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='max_mireds', full_name='ListEntitiesLightResponse.max_mireds', index=9,
- number=10, type=2, cpp_type=6, label=1,
- has_default_value=False, default_value=float(0),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='effects', full_name='ListEntitiesLightResponse.effects', index=10,
- number=11, type=9, cpp_type=9, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=939,
- serialized_end=1205,
-)
-
-
-_LISTENTITIESSENSORRESPONSE = _descriptor.Descriptor(
- name='ListEntitiesSensorResponse',
- full_name='ListEntitiesSensorResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='object_id', full_name='ListEntitiesSensorResponse.object_id', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='key', full_name='ListEntitiesSensorResponse.key', index=1,
- number=2, type=7, cpp_type=3, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='name', full_name='ListEntitiesSensorResponse.name', index=2,
- number=3, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='unique_id', full_name='ListEntitiesSensorResponse.unique_id', index=3,
- number=4, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='icon', full_name='ListEntitiesSensorResponse.icon', index=4,
- number=5, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='unit_of_measurement', full_name='ListEntitiesSensorResponse.unit_of_measurement', index=5,
- number=6, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='accuracy_decimals', full_name='ListEntitiesSensorResponse.accuracy_decimals', index=6,
- number=7, type=5, cpp_type=1, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=1208,
- serialized_end=1371,
-)
-
-
-_LISTENTITIESSWITCHRESPONSE = _descriptor.Descriptor(
- name='ListEntitiesSwitchResponse',
- full_name='ListEntitiesSwitchResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='object_id', full_name='ListEntitiesSwitchResponse.object_id', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='key', full_name='ListEntitiesSwitchResponse.key', index=1,
- number=2, type=7, cpp_type=3, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='name', full_name='ListEntitiesSwitchResponse.name', index=2,
- number=3, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='unique_id', full_name='ListEntitiesSwitchResponse.unique_id', index=3,
- number=4, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='icon', full_name='ListEntitiesSwitchResponse.icon', index=4,
- number=5, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='optimistic', full_name='ListEntitiesSwitchResponse.optimistic', index=5,
- number=6, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=1373,
- serialized_end=1500,
-)
-
-
-_LISTENTITIESTEXTSENSORRESPONSE = _descriptor.Descriptor(
- name='ListEntitiesTextSensorResponse',
- full_name='ListEntitiesTextSensorResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='object_id', full_name='ListEntitiesTextSensorResponse.object_id', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='key', full_name='ListEntitiesTextSensorResponse.key', index=1,
- number=2, type=7, cpp_type=3, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='name', full_name='ListEntitiesTextSensorResponse.name', index=2,
- number=3, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='unique_id', full_name='ListEntitiesTextSensorResponse.unique_id', index=3,
- number=4, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='icon', full_name='ListEntitiesTextSensorResponse.icon', index=4,
- number=5, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=1502,
- serialized_end=1613,
-)
-
-
-_LISTENTITIESDONERESPONSE = _descriptor.Descriptor(
- name='ListEntitiesDoneResponse',
- full_name='ListEntitiesDoneResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=1615,
- serialized_end=1641,
-)
-
-
-_SUBSCRIBESTATESREQUEST = _descriptor.Descriptor(
- name='SubscribeStatesRequest',
- full_name='SubscribeStatesRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=1643,
- serialized_end=1667,
-)
-
-
-_BINARYSENSORSTATERESPONSE = _descriptor.Descriptor(
- name='BinarySensorStateResponse',
- full_name='BinarySensorStateResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='key', full_name='BinarySensorStateResponse.key', index=0,
- number=1, type=7, cpp_type=3, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='state', full_name='BinarySensorStateResponse.state', index=1,
- number=2, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=1669,
- serialized_end=1724,
-)
-
-
-_COVERSTATERESPONSE = _descriptor.Descriptor(
- name='CoverStateResponse',
- full_name='CoverStateResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='key', full_name='CoverStateResponse.key', index=0,
- number=1, type=7, cpp_type=3, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='state', full_name='CoverStateResponse.state', index=1,
- number=2, type=14, cpp_type=8, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- _COVERSTATERESPONSE_COVERSTATE,
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=1726,
- serialized_end=1842,
-)
-
-
-_FANSTATERESPONSE = _descriptor.Descriptor(
- name='FanStateResponse',
- full_name='FanStateResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='key', full_name='FanStateResponse.key', index=0,
- number=1, type=7, cpp_type=3, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='state', full_name='FanStateResponse.state', index=1,
- number=2, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='oscillating', full_name='FanStateResponse.oscillating', index=2,
- number=3, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='speed', full_name='FanStateResponse.speed', index=3,
- number=4, type=14, cpp_type=8, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=1844,
- serialized_end=1937,
-)
-
-
-_LIGHTSTATERESPONSE = _descriptor.Descriptor(
- name='LightStateResponse',
- full_name='LightStateResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='key', full_name='LightStateResponse.key', index=0,
- number=1, type=7, cpp_type=3, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='state', full_name='LightStateResponse.state', index=1,
- number=2, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='brightness', full_name='LightStateResponse.brightness', index=2,
- number=3, type=2, cpp_type=6, label=1,
- has_default_value=False, default_value=float(0),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='red', full_name='LightStateResponse.red', index=3,
- number=4, type=2, cpp_type=6, label=1,
- has_default_value=False, default_value=float(0),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='green', full_name='LightStateResponse.green', index=4,
- number=5, type=2, cpp_type=6, label=1,
- has_default_value=False, default_value=float(0),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='blue', full_name='LightStateResponse.blue', index=5,
- number=6, type=2, cpp_type=6, label=1,
- has_default_value=False, default_value=float(0),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='white', full_name='LightStateResponse.white', index=6,
- number=7, type=2, cpp_type=6, label=1,
- has_default_value=False, default_value=float(0),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='color_temperature', full_name='LightStateResponse.color_temperature', index=7,
- number=8, type=2, cpp_type=6, label=1,
- has_default_value=False, default_value=float(0),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='effect', full_name='LightStateResponse.effect', index=8,
- number=9, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=1940,
- serialized_end=2108,
-)
-
-
-_SENSORSTATERESPONSE = _descriptor.Descriptor(
- name='SensorStateResponse',
- full_name='SensorStateResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='key', full_name='SensorStateResponse.key', index=0,
- number=1, type=7, cpp_type=3, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='state', full_name='SensorStateResponse.state', index=1,
- number=2, type=2, cpp_type=6, label=1,
- has_default_value=False, default_value=float(0),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=2110,
- serialized_end=2159,
-)
-
-
-_SWITCHSTATERESPONSE = _descriptor.Descriptor(
- name='SwitchStateResponse',
- full_name='SwitchStateResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='key', full_name='SwitchStateResponse.key', index=0,
- number=1, type=7, cpp_type=3, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='state', full_name='SwitchStateResponse.state', index=1,
- number=2, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=2161,
- serialized_end=2210,
-)
-
-
-_TEXTSENSORSTATERESPONSE = _descriptor.Descriptor(
- name='TextSensorStateResponse',
- full_name='TextSensorStateResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='key', full_name='TextSensorStateResponse.key', index=0,
- number=1, type=7, cpp_type=3, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='state', full_name='TextSensorStateResponse.state', index=1,
- number=2, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=2212,
- serialized_end=2265,
-)
-
-
-_COVERCOMMANDREQUEST = _descriptor.Descriptor(
- name='CoverCommandRequest',
- full_name='CoverCommandRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='key', full_name='CoverCommandRequest.key', index=0,
- number=1, type=7, cpp_type=3, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='has_state', full_name='CoverCommandRequest.has_state', index=1,
- number=2, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='command', full_name='CoverCommandRequest.command', index=2,
- number=3, type=14, cpp_type=8, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- _COVERCOMMANDREQUEST_COVERCOMMAND,
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=2268,
- serialized_end=2420,
-)
-
-
-_FANCOMMANDREQUEST = _descriptor.Descriptor(
- name='FanCommandRequest',
- full_name='FanCommandRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='key', full_name='FanCommandRequest.key', index=0,
- number=1, type=7, cpp_type=3, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='has_state', full_name='FanCommandRequest.has_state', index=1,
- number=2, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='state', full_name='FanCommandRequest.state', index=2,
- number=3, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='has_speed', full_name='FanCommandRequest.has_speed', index=3,
- number=4, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='speed', full_name='FanCommandRequest.speed', index=4,
- number=5, type=14, cpp_type=8, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='has_oscillating', full_name='FanCommandRequest.has_oscillating', index=5,
- number=6, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='oscillating', full_name='FanCommandRequest.oscillating', index=6,
- number=7, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=2423,
- serialized_end=2580,
-)
-
-
-_LIGHTCOMMANDREQUEST = _descriptor.Descriptor(
- name='LightCommandRequest',
- full_name='LightCommandRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='key', full_name='LightCommandRequest.key', index=0,
- number=1, type=7, cpp_type=3, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='has_state', full_name='LightCommandRequest.has_state', index=1,
- number=2, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='state', full_name='LightCommandRequest.state', index=2,
- number=3, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='has_brightness', full_name='LightCommandRequest.has_brightness', index=3,
- number=4, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='brightness', full_name='LightCommandRequest.brightness', index=4,
- number=5, type=2, cpp_type=6, label=1,
- has_default_value=False, default_value=float(0),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='has_rgb', full_name='LightCommandRequest.has_rgb', index=5,
- number=6, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='red', full_name='LightCommandRequest.red', index=6,
- number=7, type=2, cpp_type=6, label=1,
- has_default_value=False, default_value=float(0),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='green', full_name='LightCommandRequest.green', index=7,
- number=8, type=2, cpp_type=6, label=1,
- has_default_value=False, default_value=float(0),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='blue', full_name='LightCommandRequest.blue', index=8,
- number=9, type=2, cpp_type=6, label=1,
- has_default_value=False, default_value=float(0),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='has_white', full_name='LightCommandRequest.has_white', index=9,
- number=10, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='white', full_name='LightCommandRequest.white', index=10,
- number=11, type=2, cpp_type=6, label=1,
- has_default_value=False, default_value=float(0),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='has_color_temperature', full_name='LightCommandRequest.has_color_temperature', index=11,
- number=12, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='color_temperature', full_name='LightCommandRequest.color_temperature', index=12,
- number=13, type=2, cpp_type=6, label=1,
- has_default_value=False, default_value=float(0),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='has_transition_length', full_name='LightCommandRequest.has_transition_length', index=13,
- number=14, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='transition_length', full_name='LightCommandRequest.transition_length', index=14,
- number=15, type=13, cpp_type=3, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='has_flash_length', full_name='LightCommandRequest.has_flash_length', index=15,
- number=16, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='flash_length', full_name='LightCommandRequest.flash_length', index=16,
- number=17, type=13, cpp_type=3, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='has_effect', full_name='LightCommandRequest.has_effect', index=17,
- number=18, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='effect', full_name='LightCommandRequest.effect', index=18,
- number=19, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=2583,
- serialized_end=2988,
-)
-
-
-_SWITCHCOMMANDREQUEST = _descriptor.Descriptor(
- name='SwitchCommandRequest',
- full_name='SwitchCommandRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='key', full_name='SwitchCommandRequest.key', index=0,
- number=1, type=7, cpp_type=3, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='state', full_name='SwitchCommandRequest.state', index=1,
- number=2, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=2990,
- serialized_end=3040,
-)
-
-
-_SUBSCRIBELOGSREQUEST = _descriptor.Descriptor(
- name='SubscribeLogsRequest',
- full_name='SubscribeLogsRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='level', full_name='SubscribeLogsRequest.level', index=0,
- number=1, type=14, cpp_type=8, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='dump_config', full_name='SubscribeLogsRequest.dump_config', index=1,
- number=2, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=3042,
- serialized_end=3111,
-)
-
-
-_SUBSCRIBELOGSRESPONSE = _descriptor.Descriptor(
- name='SubscribeLogsResponse',
- full_name='SubscribeLogsResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='level', full_name='SubscribeLogsResponse.level', index=0,
- number=1, type=14, cpp_type=8, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='tag', full_name='SubscribeLogsResponse.tag', index=1,
- number=2, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='message', full_name='SubscribeLogsResponse.message', index=2,
- number=3, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='send_failed', full_name='SubscribeLogsResponse.send_failed', index=3,
- number=4, type=8, cpp_type=7, label=1,
- has_default_value=False, default_value=False,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=3113,
- serialized_end=3213,
-)
-
-
-_SUBSCRIBESERVICECALLSREQUEST = _descriptor.Descriptor(
- name='SubscribeServiceCallsRequest',
- full_name='SubscribeServiceCallsRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=3215,
- serialized_end=3245,
-)
-
-
-_SERVICECALLRESPONSE_DATAENTRY = _descriptor.Descriptor(
- name='DataEntry',
- full_name='ServiceCallResponse.DataEntry',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='key', full_name='ServiceCallResponse.DataEntry.key', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='value', full_name='ServiceCallResponse.DataEntry.value', index=1,
- number=2, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=_b('8\001'),
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=3453,
- serialized_end=3496,
-)
-
-_SERVICECALLRESPONSE_DATATEMPLATEENTRY = _descriptor.Descriptor(
- name='DataTemplateEntry',
- full_name='ServiceCallResponse.DataTemplateEntry',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='key', full_name='ServiceCallResponse.DataTemplateEntry.key', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='value', full_name='ServiceCallResponse.DataTemplateEntry.value', index=1,
- number=2, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=_b('8\001'),
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=3498,
- serialized_end=3549,
-)
-
-_SERVICECALLRESPONSE_VARIABLESENTRY = _descriptor.Descriptor(
- name='VariablesEntry',
- full_name='ServiceCallResponse.VariablesEntry',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='key', full_name='ServiceCallResponse.VariablesEntry.key', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='value', full_name='ServiceCallResponse.VariablesEntry.value', index=1,
- number=2, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=_b('8\001'),
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=3551,
- serialized_end=3599,
-)
-
-_SERVICECALLRESPONSE = _descriptor.Descriptor(
- name='ServiceCallResponse',
- full_name='ServiceCallResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='service', full_name='ServiceCallResponse.service', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='data', full_name='ServiceCallResponse.data', index=1,
- number=2, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='data_template', full_name='ServiceCallResponse.data_template', index=2,
- number=3, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='variables', full_name='ServiceCallResponse.variables', index=3,
- number=4, type=11, cpp_type=10, label=3,
- has_default_value=False, default_value=[],
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[_SERVICECALLRESPONSE_DATAENTRY, _SERVICECALLRESPONSE_DATATEMPLATEENTRY, _SERVICECALLRESPONSE_VARIABLESENTRY, ],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=3248,
- serialized_end=3599,
-)
-
-
-_SUBSCRIBEHOMEASSISTANTSTATESREQUEST = _descriptor.Descriptor(
- name='SubscribeHomeAssistantStatesRequest',
- full_name='SubscribeHomeAssistantStatesRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=3601,
- serialized_end=3638,
-)
-
-
-_SUBSCRIBEHOMEASSISTANTSTATERESPONSE = _descriptor.Descriptor(
- name='SubscribeHomeAssistantStateResponse',
- full_name='SubscribeHomeAssistantStateResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='entity_id', full_name='SubscribeHomeAssistantStateResponse.entity_id', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=3640,
- serialized_end=3696,
-)
-
-
-_HOMEASSISTANTSTATERESPONSE = _descriptor.Descriptor(
- name='HomeAssistantStateResponse',
- full_name='HomeAssistantStateResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='entity_id', full_name='HomeAssistantStateResponse.entity_id', index=0,
- number=1, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- _descriptor.FieldDescriptor(
- name='state', full_name='HomeAssistantStateResponse.state', index=1,
- number=2, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=_b("").decode('utf-8'),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=3698,
- serialized_end=3760,
-)
-
-
-_GETTIMEREQUEST = _descriptor.Descriptor(
- name='GetTimeRequest',
- full_name='GetTimeRequest',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=3762,
- serialized_end=3778,
-)
-
-
-_GETTIMERESPONSE = _descriptor.Descriptor(
- name='GetTimeResponse',
- full_name='GetTimeResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='epoch_seconds', full_name='GetTimeResponse.epoch_seconds', index=0,
- number=1, type=7, cpp_type=3, label=1,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- serialized_options=None, file=DESCRIPTOR),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- serialized_options=None,
- is_extendable=False,
- syntax='proto3',
- extension_ranges=[],
- oneofs=[
- ],
- serialized_start=3780,
- serialized_end=3820,
-)
-
-_COVERSTATERESPONSE.fields_by_name['state'].enum_type = _COVERSTATERESPONSE_COVERSTATE
-_COVERSTATERESPONSE_COVERSTATE.containing_type = _COVERSTATERESPONSE
-_FANSTATERESPONSE.fields_by_name['speed'].enum_type = _FANSPEED
-_COVERCOMMANDREQUEST.fields_by_name['command'].enum_type = _COVERCOMMANDREQUEST_COVERCOMMAND
-_COVERCOMMANDREQUEST_COVERCOMMAND.containing_type = _COVERCOMMANDREQUEST
-_FANCOMMANDREQUEST.fields_by_name['speed'].enum_type = _FANSPEED
-_SUBSCRIBELOGSREQUEST.fields_by_name['level'].enum_type = _LOGLEVEL
-_SUBSCRIBELOGSRESPONSE.fields_by_name['level'].enum_type = _LOGLEVEL
-_SERVICECALLRESPONSE_DATAENTRY.containing_type = _SERVICECALLRESPONSE
-_SERVICECALLRESPONSE_DATATEMPLATEENTRY.containing_type = _SERVICECALLRESPONSE
-_SERVICECALLRESPONSE_VARIABLESENTRY.containing_type = _SERVICECALLRESPONSE
-_SERVICECALLRESPONSE.fields_by_name['data'].message_type = _SERVICECALLRESPONSE_DATAENTRY
-_SERVICECALLRESPONSE.fields_by_name['data_template'].message_type = _SERVICECALLRESPONSE_DATATEMPLATEENTRY
-_SERVICECALLRESPONSE.fields_by_name['variables'].message_type = _SERVICECALLRESPONSE_VARIABLESENTRY
-DESCRIPTOR.message_types_by_name['HelloRequest'] = _HELLOREQUEST
-DESCRIPTOR.message_types_by_name['HelloResponse'] = _HELLORESPONSE
-DESCRIPTOR.message_types_by_name['ConnectRequest'] = _CONNECTREQUEST
-DESCRIPTOR.message_types_by_name['ConnectResponse'] = _CONNECTRESPONSE
-DESCRIPTOR.message_types_by_name['DisconnectRequest'] = _DISCONNECTREQUEST
-DESCRIPTOR.message_types_by_name['DisconnectResponse'] = _DISCONNECTRESPONSE
-DESCRIPTOR.message_types_by_name['PingRequest'] = _PINGREQUEST
-DESCRIPTOR.message_types_by_name['PingResponse'] = _PINGRESPONSE
-DESCRIPTOR.message_types_by_name['DeviceInfoRequest'] = _DEVICEINFOREQUEST
-DESCRIPTOR.message_types_by_name['DeviceInfoResponse'] = _DEVICEINFORESPONSE
-DESCRIPTOR.message_types_by_name['ListEntitiesRequest'] = _LISTENTITIESREQUEST
-DESCRIPTOR.message_types_by_name['ListEntitiesBinarySensorResponse'] = _LISTENTITIESBINARYSENSORRESPONSE
-DESCRIPTOR.message_types_by_name['ListEntitiesCoverResponse'] = _LISTENTITIESCOVERRESPONSE
-DESCRIPTOR.message_types_by_name['ListEntitiesFanResponse'] = _LISTENTITIESFANRESPONSE
-DESCRIPTOR.message_types_by_name['ListEntitiesLightResponse'] = _LISTENTITIESLIGHTRESPONSE
-DESCRIPTOR.message_types_by_name['ListEntitiesSensorResponse'] = _LISTENTITIESSENSORRESPONSE
-DESCRIPTOR.message_types_by_name['ListEntitiesSwitchResponse'] = _LISTENTITIESSWITCHRESPONSE
-DESCRIPTOR.message_types_by_name['ListEntitiesTextSensorResponse'] = _LISTENTITIESTEXTSENSORRESPONSE
-DESCRIPTOR.message_types_by_name['ListEntitiesDoneResponse'] = _LISTENTITIESDONERESPONSE
-DESCRIPTOR.message_types_by_name['SubscribeStatesRequest'] = _SUBSCRIBESTATESREQUEST
-DESCRIPTOR.message_types_by_name['BinarySensorStateResponse'] = _BINARYSENSORSTATERESPONSE
-DESCRIPTOR.message_types_by_name['CoverStateResponse'] = _COVERSTATERESPONSE
-DESCRIPTOR.message_types_by_name['FanStateResponse'] = _FANSTATERESPONSE
-DESCRIPTOR.message_types_by_name['LightStateResponse'] = _LIGHTSTATERESPONSE
-DESCRIPTOR.message_types_by_name['SensorStateResponse'] = _SENSORSTATERESPONSE
-DESCRIPTOR.message_types_by_name['SwitchStateResponse'] = _SWITCHSTATERESPONSE
-DESCRIPTOR.message_types_by_name['TextSensorStateResponse'] = _TEXTSENSORSTATERESPONSE
-DESCRIPTOR.message_types_by_name['CoverCommandRequest'] = _COVERCOMMANDREQUEST
-DESCRIPTOR.message_types_by_name['FanCommandRequest'] = _FANCOMMANDREQUEST
-DESCRIPTOR.message_types_by_name['LightCommandRequest'] = _LIGHTCOMMANDREQUEST
-DESCRIPTOR.message_types_by_name['SwitchCommandRequest'] = _SWITCHCOMMANDREQUEST
-DESCRIPTOR.message_types_by_name['SubscribeLogsRequest'] = _SUBSCRIBELOGSREQUEST
-DESCRIPTOR.message_types_by_name['SubscribeLogsResponse'] = _SUBSCRIBELOGSRESPONSE
-DESCRIPTOR.message_types_by_name['SubscribeServiceCallsRequest'] = _SUBSCRIBESERVICECALLSREQUEST
-DESCRIPTOR.message_types_by_name['ServiceCallResponse'] = _SERVICECALLRESPONSE
-DESCRIPTOR.message_types_by_name['SubscribeHomeAssistantStatesRequest'] = _SUBSCRIBEHOMEASSISTANTSTATESREQUEST
-DESCRIPTOR.message_types_by_name['SubscribeHomeAssistantStateResponse'] = _SUBSCRIBEHOMEASSISTANTSTATERESPONSE
-DESCRIPTOR.message_types_by_name['HomeAssistantStateResponse'] = _HOMEASSISTANTSTATERESPONSE
-DESCRIPTOR.message_types_by_name['GetTimeRequest'] = _GETTIMEREQUEST
-DESCRIPTOR.message_types_by_name['GetTimeResponse'] = _GETTIMERESPONSE
-DESCRIPTOR.enum_types_by_name['FanSpeed'] = _FANSPEED
-DESCRIPTOR.enum_types_by_name['LogLevel'] = _LOGLEVEL
-_sym_db.RegisterFileDescriptor(DESCRIPTOR)
-
-HelloRequest = _reflection.GeneratedProtocolMessageType('HelloRequest', (_message.Message,), dict(
- DESCRIPTOR = _HELLOREQUEST,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:HelloRequest)
- ))
-_sym_db.RegisterMessage(HelloRequest)
-
-HelloResponse = _reflection.GeneratedProtocolMessageType('HelloResponse', (_message.Message,), dict(
- DESCRIPTOR = _HELLORESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:HelloResponse)
- ))
-_sym_db.RegisterMessage(HelloResponse)
-
-ConnectRequest = _reflection.GeneratedProtocolMessageType('ConnectRequest', (_message.Message,), dict(
- DESCRIPTOR = _CONNECTREQUEST,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:ConnectRequest)
- ))
-_sym_db.RegisterMessage(ConnectRequest)
-
-ConnectResponse = _reflection.GeneratedProtocolMessageType('ConnectResponse', (_message.Message,), dict(
- DESCRIPTOR = _CONNECTRESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:ConnectResponse)
- ))
-_sym_db.RegisterMessage(ConnectResponse)
-
-DisconnectRequest = _reflection.GeneratedProtocolMessageType('DisconnectRequest', (_message.Message,), dict(
- DESCRIPTOR = _DISCONNECTREQUEST,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:DisconnectRequest)
- ))
-_sym_db.RegisterMessage(DisconnectRequest)
-
-DisconnectResponse = _reflection.GeneratedProtocolMessageType('DisconnectResponse', (_message.Message,), dict(
- DESCRIPTOR = _DISCONNECTRESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:DisconnectResponse)
- ))
-_sym_db.RegisterMessage(DisconnectResponse)
-
-PingRequest = _reflection.GeneratedProtocolMessageType('PingRequest', (_message.Message,), dict(
- DESCRIPTOR = _PINGREQUEST,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:PingRequest)
- ))
-_sym_db.RegisterMessage(PingRequest)
-
-PingResponse = _reflection.GeneratedProtocolMessageType('PingResponse', (_message.Message,), dict(
- DESCRIPTOR = _PINGRESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:PingResponse)
- ))
-_sym_db.RegisterMessage(PingResponse)
-
-DeviceInfoRequest = _reflection.GeneratedProtocolMessageType('DeviceInfoRequest', (_message.Message,), dict(
- DESCRIPTOR = _DEVICEINFOREQUEST,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:DeviceInfoRequest)
- ))
-_sym_db.RegisterMessage(DeviceInfoRequest)
-
-DeviceInfoResponse = _reflection.GeneratedProtocolMessageType('DeviceInfoResponse', (_message.Message,), dict(
- DESCRIPTOR = _DEVICEINFORESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:DeviceInfoResponse)
- ))
-_sym_db.RegisterMessage(DeviceInfoResponse)
-
-ListEntitiesRequest = _reflection.GeneratedProtocolMessageType('ListEntitiesRequest', (_message.Message,), dict(
- DESCRIPTOR = _LISTENTITIESREQUEST,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:ListEntitiesRequest)
- ))
-_sym_db.RegisterMessage(ListEntitiesRequest)
-
-ListEntitiesBinarySensorResponse = _reflection.GeneratedProtocolMessageType('ListEntitiesBinarySensorResponse', (_message.Message,), dict(
- DESCRIPTOR = _LISTENTITIESBINARYSENSORRESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:ListEntitiesBinarySensorResponse)
- ))
-_sym_db.RegisterMessage(ListEntitiesBinarySensorResponse)
-
-ListEntitiesCoverResponse = _reflection.GeneratedProtocolMessageType('ListEntitiesCoverResponse', (_message.Message,), dict(
- DESCRIPTOR = _LISTENTITIESCOVERRESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:ListEntitiesCoverResponse)
- ))
-_sym_db.RegisterMessage(ListEntitiesCoverResponse)
-
-ListEntitiesFanResponse = _reflection.GeneratedProtocolMessageType('ListEntitiesFanResponse', (_message.Message,), dict(
- DESCRIPTOR = _LISTENTITIESFANRESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:ListEntitiesFanResponse)
- ))
-_sym_db.RegisterMessage(ListEntitiesFanResponse)
-
-ListEntitiesLightResponse = _reflection.GeneratedProtocolMessageType('ListEntitiesLightResponse', (_message.Message,), dict(
- DESCRIPTOR = _LISTENTITIESLIGHTRESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:ListEntitiesLightResponse)
- ))
-_sym_db.RegisterMessage(ListEntitiesLightResponse)
-
-ListEntitiesSensorResponse = _reflection.GeneratedProtocolMessageType('ListEntitiesSensorResponse', (_message.Message,), dict(
- DESCRIPTOR = _LISTENTITIESSENSORRESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:ListEntitiesSensorResponse)
- ))
-_sym_db.RegisterMessage(ListEntitiesSensorResponse)
-
-ListEntitiesSwitchResponse = _reflection.GeneratedProtocolMessageType('ListEntitiesSwitchResponse', (_message.Message,), dict(
- DESCRIPTOR = _LISTENTITIESSWITCHRESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:ListEntitiesSwitchResponse)
- ))
-_sym_db.RegisterMessage(ListEntitiesSwitchResponse)
-
-ListEntitiesTextSensorResponse = _reflection.GeneratedProtocolMessageType('ListEntitiesTextSensorResponse', (_message.Message,), dict(
- DESCRIPTOR = _LISTENTITIESTEXTSENSORRESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:ListEntitiesTextSensorResponse)
- ))
-_sym_db.RegisterMessage(ListEntitiesTextSensorResponse)
-
-ListEntitiesDoneResponse = _reflection.GeneratedProtocolMessageType('ListEntitiesDoneResponse', (_message.Message,), dict(
- DESCRIPTOR = _LISTENTITIESDONERESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:ListEntitiesDoneResponse)
- ))
-_sym_db.RegisterMessage(ListEntitiesDoneResponse)
-
-SubscribeStatesRequest = _reflection.GeneratedProtocolMessageType('SubscribeStatesRequest', (_message.Message,), dict(
- DESCRIPTOR = _SUBSCRIBESTATESREQUEST,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:SubscribeStatesRequest)
- ))
-_sym_db.RegisterMessage(SubscribeStatesRequest)
-
-BinarySensorStateResponse = _reflection.GeneratedProtocolMessageType('BinarySensorStateResponse', (_message.Message,), dict(
- DESCRIPTOR = _BINARYSENSORSTATERESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:BinarySensorStateResponse)
- ))
-_sym_db.RegisterMessage(BinarySensorStateResponse)
-
-CoverStateResponse = _reflection.GeneratedProtocolMessageType('CoverStateResponse', (_message.Message,), dict(
- DESCRIPTOR = _COVERSTATERESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:CoverStateResponse)
- ))
-_sym_db.RegisterMessage(CoverStateResponse)
-
-FanStateResponse = _reflection.GeneratedProtocolMessageType('FanStateResponse', (_message.Message,), dict(
- DESCRIPTOR = _FANSTATERESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:FanStateResponse)
- ))
-_sym_db.RegisterMessage(FanStateResponse)
-
-LightStateResponse = _reflection.GeneratedProtocolMessageType('LightStateResponse', (_message.Message,), dict(
- DESCRIPTOR = _LIGHTSTATERESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:LightStateResponse)
- ))
-_sym_db.RegisterMessage(LightStateResponse)
-
-SensorStateResponse = _reflection.GeneratedProtocolMessageType('SensorStateResponse', (_message.Message,), dict(
- DESCRIPTOR = _SENSORSTATERESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:SensorStateResponse)
- ))
-_sym_db.RegisterMessage(SensorStateResponse)
-
-SwitchStateResponse = _reflection.GeneratedProtocolMessageType('SwitchStateResponse', (_message.Message,), dict(
- DESCRIPTOR = _SWITCHSTATERESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:SwitchStateResponse)
- ))
-_sym_db.RegisterMessage(SwitchStateResponse)
-
-TextSensorStateResponse = _reflection.GeneratedProtocolMessageType('TextSensorStateResponse', (_message.Message,), dict(
- DESCRIPTOR = _TEXTSENSORSTATERESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:TextSensorStateResponse)
- ))
-_sym_db.RegisterMessage(TextSensorStateResponse)
-
-CoverCommandRequest = _reflection.GeneratedProtocolMessageType('CoverCommandRequest', (_message.Message,), dict(
- DESCRIPTOR = _COVERCOMMANDREQUEST,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:CoverCommandRequest)
- ))
-_sym_db.RegisterMessage(CoverCommandRequest)
-
-FanCommandRequest = _reflection.GeneratedProtocolMessageType('FanCommandRequest', (_message.Message,), dict(
- DESCRIPTOR = _FANCOMMANDREQUEST,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:FanCommandRequest)
- ))
-_sym_db.RegisterMessage(FanCommandRequest)
-
-LightCommandRequest = _reflection.GeneratedProtocolMessageType('LightCommandRequest', (_message.Message,), dict(
- DESCRIPTOR = _LIGHTCOMMANDREQUEST,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:LightCommandRequest)
- ))
-_sym_db.RegisterMessage(LightCommandRequest)
-
-SwitchCommandRequest = _reflection.GeneratedProtocolMessageType('SwitchCommandRequest', (_message.Message,), dict(
- DESCRIPTOR = _SWITCHCOMMANDREQUEST,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:SwitchCommandRequest)
- ))
-_sym_db.RegisterMessage(SwitchCommandRequest)
-
-SubscribeLogsRequest = _reflection.GeneratedProtocolMessageType('SubscribeLogsRequest', (_message.Message,), dict(
- DESCRIPTOR = _SUBSCRIBELOGSREQUEST,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:SubscribeLogsRequest)
- ))
-_sym_db.RegisterMessage(SubscribeLogsRequest)
-
-SubscribeLogsResponse = _reflection.GeneratedProtocolMessageType('SubscribeLogsResponse', (_message.Message,), dict(
- DESCRIPTOR = _SUBSCRIBELOGSRESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:SubscribeLogsResponse)
- ))
-_sym_db.RegisterMessage(SubscribeLogsResponse)
-
-SubscribeServiceCallsRequest = _reflection.GeneratedProtocolMessageType('SubscribeServiceCallsRequest', (_message.Message,), dict(
- DESCRIPTOR = _SUBSCRIBESERVICECALLSREQUEST,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:SubscribeServiceCallsRequest)
- ))
-_sym_db.RegisterMessage(SubscribeServiceCallsRequest)
-
-ServiceCallResponse = _reflection.GeneratedProtocolMessageType('ServiceCallResponse', (_message.Message,), dict(
-
- DataEntry = _reflection.GeneratedProtocolMessageType('DataEntry', (_message.Message,), dict(
- DESCRIPTOR = _SERVICECALLRESPONSE_DATAENTRY,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:ServiceCallResponse.DataEntry)
- ))
- ,
-
- DataTemplateEntry = _reflection.GeneratedProtocolMessageType('DataTemplateEntry', (_message.Message,), dict(
- DESCRIPTOR = _SERVICECALLRESPONSE_DATATEMPLATEENTRY,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:ServiceCallResponse.DataTemplateEntry)
- ))
- ,
-
- VariablesEntry = _reflection.GeneratedProtocolMessageType('VariablesEntry', (_message.Message,), dict(
- DESCRIPTOR = _SERVICECALLRESPONSE_VARIABLESENTRY,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:ServiceCallResponse.VariablesEntry)
- ))
- ,
- DESCRIPTOR = _SERVICECALLRESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:ServiceCallResponse)
- ))
-_sym_db.RegisterMessage(ServiceCallResponse)
-_sym_db.RegisterMessage(ServiceCallResponse.DataEntry)
-_sym_db.RegisterMessage(ServiceCallResponse.DataTemplateEntry)
-_sym_db.RegisterMessage(ServiceCallResponse.VariablesEntry)
-
-SubscribeHomeAssistantStatesRequest = _reflection.GeneratedProtocolMessageType('SubscribeHomeAssistantStatesRequest', (_message.Message,), dict(
- DESCRIPTOR = _SUBSCRIBEHOMEASSISTANTSTATESREQUEST,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:SubscribeHomeAssistantStatesRequest)
- ))
-_sym_db.RegisterMessage(SubscribeHomeAssistantStatesRequest)
-
-SubscribeHomeAssistantStateResponse = _reflection.GeneratedProtocolMessageType('SubscribeHomeAssistantStateResponse', (_message.Message,), dict(
- DESCRIPTOR = _SUBSCRIBEHOMEASSISTANTSTATERESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:SubscribeHomeAssistantStateResponse)
- ))
-_sym_db.RegisterMessage(SubscribeHomeAssistantStateResponse)
-
-HomeAssistantStateResponse = _reflection.GeneratedProtocolMessageType('HomeAssistantStateResponse', (_message.Message,), dict(
- DESCRIPTOR = _HOMEASSISTANTSTATERESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:HomeAssistantStateResponse)
- ))
-_sym_db.RegisterMessage(HomeAssistantStateResponse)
-
-GetTimeRequest = _reflection.GeneratedProtocolMessageType('GetTimeRequest', (_message.Message,), dict(
- DESCRIPTOR = _GETTIMEREQUEST,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:GetTimeRequest)
- ))
-_sym_db.RegisterMessage(GetTimeRequest)
-
-GetTimeResponse = _reflection.GeneratedProtocolMessageType('GetTimeResponse', (_message.Message,), dict(
- DESCRIPTOR = _GETTIMERESPONSE,
- __module__ = 'api_pb2'
- # @@protoc_insertion_point(class_scope:GetTimeResponse)
- ))
-_sym_db.RegisterMessage(GetTimeResponse)
-
-
-_SERVICECALLRESPONSE_DATAENTRY._options = None
-_SERVICECALLRESPONSE_DATATEMPLATEENTRY._options = None
-_SERVICECALLRESPONSE_VARIABLESENTRY._options = None
-# @@protoc_insertion_point(module_scope)
diff --git a/esphome/api/client.py b/esphome/api/client.py
deleted file mode 100644
index fcea90e3b4..0000000000
--- a/esphome/api/client.py
+++ /dev/null
@@ -1,490 +0,0 @@
-from datetime import datetime
-import functools
-import logging
-import socket
-import threading
-import time
-
-# pylint: disable=unused-import
-from typing import Optional # noqa
-from google.protobuf import message # noqa
-
-from esphome import const
-import esphome.api.api_pb2 as pb
-from esphome.const import CONF_PASSWORD, CONF_PORT
-from esphome.core import EsphomeError
-from esphome.helpers import resolve_ip_address, indent, color
-from esphome.util import safe_print
-
-_LOGGER = logging.getLogger(__name__)
-
-
-class APIConnectionError(EsphomeError):
- pass
-
-
-MESSAGE_TYPE_TO_PROTO = {
- 1: pb.HelloRequest,
- 2: pb.HelloResponse,
- 3: pb.ConnectRequest,
- 4: pb.ConnectResponse,
- 5: pb.DisconnectRequest,
- 6: pb.DisconnectResponse,
- 7: pb.PingRequest,
- 8: pb.PingResponse,
- 9: pb.DeviceInfoRequest,
- 10: pb.DeviceInfoResponse,
- 11: pb.ListEntitiesRequest,
- 12: pb.ListEntitiesBinarySensorResponse,
- 13: pb.ListEntitiesCoverResponse,
- 14: pb.ListEntitiesFanResponse,
- 15: pb.ListEntitiesLightResponse,
- 16: pb.ListEntitiesSensorResponse,
- 17: pb.ListEntitiesSwitchResponse,
- 18: pb.ListEntitiesTextSensorResponse,
- 19: pb.ListEntitiesDoneResponse,
- 20: pb.SubscribeStatesRequest,
- 21: pb.BinarySensorStateResponse,
- 22: pb.CoverStateResponse,
- 23: pb.FanStateResponse,
- 24: pb.LightStateResponse,
- 25: pb.SensorStateResponse,
- 26: pb.SwitchStateResponse,
- 27: pb.TextSensorStateResponse,
- 28: pb.SubscribeLogsRequest,
- 29: pb.SubscribeLogsResponse,
- 30: pb.CoverCommandRequest,
- 31: pb.FanCommandRequest,
- 32: pb.LightCommandRequest,
- 33: pb.SwitchCommandRequest,
- 34: pb.SubscribeServiceCallsRequest,
- 35: pb.ServiceCallResponse,
- 36: pb.GetTimeRequest,
- 37: pb.GetTimeResponse,
-}
-
-
-def _varuint_to_bytes(value):
- if value <= 0x7F:
- return bytes([value])
-
- ret = bytes()
- while value:
- temp = value & 0x7F
- value >>= 7
- if value:
- ret += bytes([temp | 0x80])
- else:
- ret += bytes([temp])
-
- return ret
-
-
-def _bytes_to_varuint(value):
- result = 0
- bitpos = 0
- for val in value:
- result |= (val & 0x7F) << bitpos
- bitpos += 7
- if (val & 0x80) == 0:
- return result
- return None
-
-
-# pylint: disable=too-many-instance-attributes,not-callable
-class APIClient(threading.Thread):
- def __init__(self, address, port, password):
- threading.Thread.__init__(self)
- self._address = address # type: str
- self._port = port # type: int
- self._password = password # type: Optional[str]
- self._socket = None # type: Optional[socket.socket]
- self._socket_open_event = threading.Event()
- self._socket_write_lock = threading.Lock()
- self._connected = False
- self._authenticated = False
- self._message_handlers = []
- self._keepalive = 5
- self._ping_timer = None
-
- self.on_disconnect = None
- self.on_connect = None
- self.on_login = None
- self.auto_reconnect = False
- self._running_event = threading.Event()
- self._stop_event = threading.Event()
-
- @property
- def stopped(self):
- return self._stop_event.is_set()
-
- def _refresh_ping(self):
- if self._ping_timer is not None:
- self._ping_timer.cancel()
- self._ping_timer = None
-
- def func():
- self._ping_timer = None
-
- if self._connected:
- try:
- self.ping()
- except APIConnectionError as err:
- self._fatal_error(err)
- else:
- self._refresh_ping()
-
- self._ping_timer = threading.Timer(self._keepalive, func)
- self._ping_timer.start()
-
- def _cancel_ping(self):
- if self._ping_timer is not None:
- self._ping_timer.cancel()
- self._ping_timer = None
-
- def _close_socket(self):
- self._cancel_ping()
- if self._socket is not None:
- self._socket.close()
- self._socket = None
- self._socket_open_event.clear()
- self._connected = False
- self._authenticated = False
- self._message_handlers = []
-
- def stop(self, force=False):
- if self.stopped:
- raise ValueError
-
- if self._connected and not force:
- try:
- self.disconnect()
- except APIConnectionError:
- pass
- self._close_socket()
-
- self._stop_event.set()
- if not force:
- self.join()
-
- def connect(self):
- if not self._running_event.wait(0.1):
- raise APIConnectionError("You need to call start() first!")
-
- if self._connected:
- self.disconnect(on_disconnect=False)
-
- try:
- ip = resolve_ip_address(self._address)
- except EsphomeError as err:
- _LOGGER.warning("Error resolving IP address of %s. Is it connected to WiFi?",
- self._address)
- _LOGGER.warning("(If this error persists, please set a static IP address: "
- "https://esphome.io/components/wifi.html#manual-ips)")
- raise APIConnectionError(err)
-
- _LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip)
- self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self._socket.settimeout(10.0)
- self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
- try:
- self._socket.connect((ip, self._port))
- except OSError as err:
- err = APIConnectionError(f"Error connecting to {ip}: {err}")
- self._fatal_error(err)
- raise err
- self._socket.settimeout(0.1)
-
- self._socket_open_event.set()
-
- hello = pb.HelloRequest()
- hello.client_info = f'ESPHome v{const.__version__}'
- try:
- resp = self._send_message_await_response(hello, pb.HelloResponse)
- except APIConnectionError as err:
- self._fatal_error(err)
- raise err
- _LOGGER.debug("Successfully connected to %s ('%s' API=%s.%s)", self._address,
- resp.server_info, resp.api_version_major, resp.api_version_minor)
- self._connected = True
- self._refresh_ping()
- if self.on_connect is not None:
- self.on_connect()
-
- def _check_connected(self):
- if not self._connected:
- err = APIConnectionError("Must be connected!")
- self._fatal_error(err)
- raise err
-
- def login(self):
- self._check_connected()
- if self._authenticated:
- raise APIConnectionError("Already logged in!")
-
- connect = pb.ConnectRequest()
- if self._password is not None:
- connect.password = self._password
- resp = self._send_message_await_response(connect, pb.ConnectResponse)
- if resp.invalid_password:
- raise APIConnectionError("Invalid password!")
-
- self._authenticated = True
- if self.on_login is not None:
- self.on_login()
-
- def _fatal_error(self, err):
- was_connected = self._connected
-
- self._close_socket()
-
- if was_connected and self.on_disconnect is not None:
- self.on_disconnect(err)
-
- def _write(self, data): # type: (bytes) -> None
- if self._socket is None:
- raise APIConnectionError("Socket closed")
-
- # _LOGGER.debug("Write: %s", format_bytes(data))
- with self._socket_write_lock:
- try:
- self._socket.sendall(data)
- except OSError as err:
- err = APIConnectionError(f"Error while writing data: {err}")
- self._fatal_error(err)
- raise err
-
- def _send_message(self, msg):
- # type: (message.Message) -> None
- for message_type, klass in MESSAGE_TYPE_TO_PROTO.items():
- if isinstance(msg, klass):
- break
- else:
- raise ValueError
-
- encoded = msg.SerializeToString()
- _LOGGER.debug("Sending %s:\n%s", type(msg), indent(str(msg)))
- req = bytes([0])
- req += _varuint_to_bytes(len(encoded))
- req += _varuint_to_bytes(message_type)
- req += encoded
- self._write(req)
-
- def _send_message_await_response_complex(self, send_msg, do_append, do_stop, timeout=5):
- event = threading.Event()
- responses = []
-
- def on_message(resp):
- if do_append(resp):
- responses.append(resp)
- if do_stop(resp):
- event.set()
-
- self._message_handlers.append(on_message)
- self._send_message(send_msg)
- ret = event.wait(timeout)
- try:
- self._message_handlers.remove(on_message)
- except ValueError:
- pass
- if not ret:
- raise APIConnectionError("Timeout while waiting for message response!")
- return responses
-
- def _send_message_await_response(self, send_msg, response_type, timeout=5):
- def is_response(msg):
- return isinstance(msg, response_type)
-
- return self._send_message_await_response_complex(send_msg, is_response, is_response,
- timeout)[0]
-
- def device_info(self):
- self._check_connected()
- return self._send_message_await_response(pb.DeviceInfoRequest(), pb.DeviceInfoResponse)
-
- def ping(self):
- self._check_connected()
- return self._send_message_await_response(pb.PingRequest(), pb.PingResponse)
-
- def disconnect(self, on_disconnect=True):
- self._check_connected()
-
- try:
- self._send_message_await_response(pb.DisconnectRequest(), pb.DisconnectResponse)
- except APIConnectionError:
- pass
- self._close_socket()
-
- if self.on_disconnect is not None and on_disconnect:
- self.on_disconnect(None)
-
- def _check_authenticated(self):
- if not self._authenticated:
- raise APIConnectionError("Must login first!")
-
- def subscribe_logs(self, on_log, log_level=7, dump_config=False):
- self._check_authenticated()
-
- def on_msg(msg):
- if isinstance(msg, pb.SubscribeLogsResponse):
- on_log(msg)
-
- self._message_handlers.append(on_msg)
- req = pb.SubscribeLogsRequest(dump_config=dump_config)
- req.level = log_level
- self._send_message(req)
-
- def _recv(self, amount):
- ret = bytes()
- if amount == 0:
- return ret
-
- while len(ret) < amount:
- if self.stopped:
- raise APIConnectionError("Stopped!")
- if not self._socket_open_event.is_set():
- raise APIConnectionError("No socket!")
- try:
- val = self._socket.recv(amount - len(ret))
- except AttributeError:
- raise APIConnectionError("Socket was closed")
- except socket.timeout:
- continue
- except OSError as err:
- raise APIConnectionError(f"Error while receiving data: {err}")
- ret += val
- return ret
-
- def _recv_varint(self):
- raw = bytes()
- while not raw or raw[-1] & 0x80:
- raw += self._recv(1)
- return _bytes_to_varuint(raw)
-
- def _run_once(self):
- if not self._socket_open_event.wait(0.1):
- return
-
- # Preamble
- if self._recv(1)[0] != 0x00:
- raise APIConnectionError("Invalid preamble")
-
- length = self._recv_varint()
- msg_type = self._recv_varint()
-
- raw_msg = self._recv(length)
- if msg_type not in MESSAGE_TYPE_TO_PROTO:
- _LOGGER.debug("Skipping message type %s", msg_type)
- return
-
- msg = MESSAGE_TYPE_TO_PROTO[msg_type]()
- msg.ParseFromString(raw_msg)
- _LOGGER.debug("Got message: %s:\n%s", type(msg), indent(str(msg)))
- for msg_handler in self._message_handlers[:]:
- msg_handler(msg)
- self._handle_internal_messages(msg)
-
- def run(self):
- self._running_event.set()
- while not self.stopped:
- try:
- self._run_once()
- except APIConnectionError as err:
- if self.stopped:
- break
- if self._connected:
- _LOGGER.error("Error while reading incoming messages: %s", err)
- self._fatal_error(err)
- self._running_event.clear()
-
- def _handle_internal_messages(self, msg):
- if isinstance(msg, pb.DisconnectRequest):
- self._send_message(pb.DisconnectResponse())
- if self._socket is not None:
- self._socket.close()
- self._socket = None
- self._connected = False
- if self.on_disconnect is not None:
- self.on_disconnect(None)
- elif isinstance(msg, pb.PingRequest):
- self._send_message(pb.PingResponse())
- elif isinstance(msg, pb.GetTimeRequest):
- resp = pb.GetTimeResponse()
- resp.epoch_seconds = int(time.time())
- self._send_message(resp)
-
-
-def run_logs(config, address):
- conf = config['api']
- port = conf[CONF_PORT]
- password = conf[CONF_PASSWORD]
- _LOGGER.info("Starting log output from %s using esphome API", address)
-
- cli = APIClient(address, port, password)
- stopping = False
- retry_timer = []
-
- has_connects = []
-
- def try_connect(err, tries=0):
- if stopping:
- return
-
- if err:
- _LOGGER.warning("Disconnected from API: %s", err)
-
- while retry_timer:
- retry_timer.pop(0).cancel()
-
- error = None
- try:
- cli.connect()
- cli.login()
- except APIConnectionError as err2: # noqa
- error = err2
-
- if error is None:
- _LOGGER.info("Successfully connected to %s", address)
- return
-
- wait_time = int(min(1.5**min(tries, 100), 30))
- if not has_connects:
- _LOGGER.warning("Initial connection failed. The ESP might not be connected "
- "to WiFi yet (%s). Re-Trying in %s seconds",
- error, wait_time)
- else:
- _LOGGER.warning("Couldn't connect to API (%s). Trying to reconnect in %s seconds",
- error, wait_time)
- timer = threading.Timer(wait_time, functools.partial(try_connect, None, tries + 1))
- timer.start()
- retry_timer.append(timer)
-
- def on_log(msg):
- time_ = datetime.now().time().strftime('[%H:%M:%S]')
- text = msg.message
- if msg.send_failed:
- text = color('white', '(Message skipped because it was too big to fit in '
- 'TCP buffer - This is only cosmetic)')
- safe_print(time_ + text)
-
- def on_login():
- try:
- cli.subscribe_logs(on_log, dump_config=not has_connects)
- has_connects.append(True)
- except APIConnectionError:
- cli.disconnect()
-
- cli.on_disconnect = try_connect
- cli.on_login = on_login
- cli.start()
-
- try:
- try_connect(None)
- while True:
- time.sleep(1)
- except KeyboardInterrupt:
- stopping = True
- cli.stop(True)
- while retry_timer:
- retry_timer.pop(0).cancel()
- return 0
diff --git a/esphome/automation.py b/esphome/automation.py
index 5df884e7c2..fab998527f 100644
--- a/esphome/automation.py
+++ b/esphome/automation.py
@@ -1,8 +1,18 @@
import esphome.codegen as cg
import esphome.config_validation as cv
-from esphome.const import CONF_AUTOMATION_ID, CONF_CONDITION, CONF_ELSE, CONF_ID, CONF_THEN, \
- CONF_TRIGGER_ID, CONF_TYPE_ID, CONF_TIME
-from esphome.core import coroutine
+from esphome.const import (
+ CONF_AUTOMATION_ID,
+ CONF_CONDITION,
+ CONF_COUNT,
+ CONF_ELSE,
+ CONF_ID,
+ CONF_THEN,
+ CONF_TIMEOUT,
+ CONF_TRIGGER_ID,
+ CONF_TYPE_ID,
+ CONF_TIME,
+)
+from esphome.jsonschema import jschema_extractor
from esphome.util import Registry
@@ -13,7 +23,12 @@ def maybe_simple_id(*validators):
def maybe_conf(conf, *validators):
validator = cv.All(*validators)
+ @jschema_extractor("maybe")
def validate(value):
+ # pylint: disable=comparison-with-callable
+ if value == jschema_extractor:
+ return validator
+
if isinstance(value, dict):
return validator(value)
with cv.remove_prepend_path([conf]):
@@ -30,36 +45,35 @@ def register_condition(name, condition_type, schema):
return CONDITION_REGISTRY.register(name, condition_type, schema)
-Action = cg.esphome_ns.class_('Action')
-Trigger = cg.esphome_ns.class_('Trigger')
+Action = cg.esphome_ns.class_("Action")
+Trigger = cg.esphome_ns.class_("Trigger")
ACTION_REGISTRY = Registry()
-Condition = cg.esphome_ns.class_('Condition')
+Condition = cg.esphome_ns.class_("Condition")
CONDITION_REGISTRY = Registry()
-validate_action = cv.validate_registry_entry('action', ACTION_REGISTRY)
-validate_action_list = cv.validate_registry('action', ACTION_REGISTRY)
-validate_condition = cv.validate_registry_entry('condition', CONDITION_REGISTRY)
-validate_condition_list = cv.validate_registry('condition', CONDITION_REGISTRY)
+validate_action = cv.validate_registry_entry("action", ACTION_REGISTRY)
+validate_action_list = cv.validate_registry("action", ACTION_REGISTRY)
+validate_condition = cv.validate_registry_entry("condition", CONDITION_REGISTRY)
+validate_condition_list = cv.validate_registry("condition", CONDITION_REGISTRY)
def validate_potentially_and_condition(value):
if isinstance(value, list):
- with cv.remove_prepend_path(['and']):
- return validate_condition({
- 'and': value
- })
+ with cv.remove_prepend_path(["and"]):
+ return validate_condition({"and": value})
return validate_condition(value)
-DelayAction = cg.esphome_ns.class_('DelayAction', Action, cg.Component)
-LambdaAction = cg.esphome_ns.class_('LambdaAction', Action)
-IfAction = cg.esphome_ns.class_('IfAction', Action)
-WhileAction = cg.esphome_ns.class_('WhileAction', Action)
-WaitUntilAction = cg.esphome_ns.class_('WaitUntilAction', Action, cg.Component)
-UpdateComponentAction = cg.esphome_ns.class_('UpdateComponentAction', Action)
-Automation = cg.esphome_ns.class_('Automation')
+DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
+LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
+IfAction = cg.esphome_ns.class_("IfAction", Action)
+WhileAction = cg.esphome_ns.class_("WhileAction", Action)
+RepeatAction = cg.esphome_ns.class_("RepeatAction", Action)
+WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component)
+UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action)
+Automation = cg.esphome_ns.class_("Automation")
-LambdaCondition = cg.esphome_ns.class_('LambdaCondition', Condition)
-ForCondition = cg.esphome_ns.class_('ForCondition', Condition, cg.Component)
+LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition)
+ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component)
def validate_automation(extra_schema=None, extra_validators=None, single=False):
@@ -83,9 +97,10 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
try:
return cv.Schema([schema])(value)
except cv.Invalid as err2:
- if 'extra keys not allowed' in str(err2) and len(err2.path) == 2:
+ if "extra keys not allowed" in str(err2) and len(err2.path) == 2:
+ # pylint: disable=raise-missing-from
raise err
- if 'Unable to find action' in str(err):
+ if "Unable to find action" in str(err):
raise err2
raise cv.MultipleInvalid([err, err2])
elif isinstance(value, dict):
@@ -96,7 +111,13 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
# This should only happen with invalid configs, but let's have a nice error message.
return [schema(value)]
+ @jschema_extractor("automation")
def validator(value):
+ # hack to get the schema
+ # pylint: disable=comparison-with-callable
+ if value == jschema_extractor:
+ return schema
+
value = validator_(value)
if extra_validators is not None:
value = cv.Schema([extra_validators])(value)
@@ -109,162 +130,223 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
return validator
-AUTOMATION_SCHEMA = cv.Schema({
- cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger),
- cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_id(Automation),
- cv.Required(CONF_THEN): validate_action_list,
-})
+AUTOMATION_SCHEMA = cv.Schema(
+ {
+ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger),
+ cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_id(Automation),
+ cv.Required(CONF_THEN): validate_action_list,
+ }
+)
-AndCondition = cg.esphome_ns.class_('AndCondition', Condition)
-OrCondition = cg.esphome_ns.class_('OrCondition', Condition)
-NotCondition = cg.esphome_ns.class_('NotCondition', Condition)
+AndCondition = cg.esphome_ns.class_("AndCondition", Condition)
+OrCondition = cg.esphome_ns.class_("OrCondition", Condition)
+NotCondition = cg.esphome_ns.class_("NotCondition", Condition)
-@register_condition('and', AndCondition, validate_condition_list)
-def and_condition_to_code(config, condition_id, template_arg, args):
- conditions = yield build_condition_list(config, template_arg, args)
- yield cg.new_Pvariable(condition_id, template_arg, conditions)
+@register_condition("and", AndCondition, validate_condition_list)
+async def and_condition_to_code(config, condition_id, template_arg, args):
+ conditions = await build_condition_list(config, template_arg, args)
+ return cg.new_Pvariable(condition_id, template_arg, conditions)
-@register_condition('or', OrCondition, validate_condition_list)
-def or_condition_to_code(config, condition_id, template_arg, args):
- conditions = yield build_condition_list(config, template_arg, args)
- yield cg.new_Pvariable(condition_id, template_arg, conditions)
+@register_condition("or", OrCondition, validate_condition_list)
+async def or_condition_to_code(config, condition_id, template_arg, args):
+ conditions = await build_condition_list(config, template_arg, args)
+ return cg.new_Pvariable(condition_id, template_arg, conditions)
-@register_condition('not', NotCondition, validate_potentially_and_condition)
-def not_condition_to_code(config, condition_id, template_arg, args):
- condition = yield build_condition(config, template_arg, args)
- yield cg.new_Pvariable(condition_id, template_arg, condition)
+@register_condition("not", NotCondition, validate_potentially_and_condition)
+async def not_condition_to_code(config, condition_id, template_arg, args):
+ condition = await build_condition(config, template_arg, args)
+ return cg.new_Pvariable(condition_id, template_arg, condition)
-@register_condition('lambda', LambdaCondition, cv.lambda_)
-def lambda_condition_to_code(config, condition_id, template_arg, args):
- lambda_ = yield cg.process_lambda(config, args, return_type=bool)
- yield cg.new_Pvariable(condition_id, template_arg, lambda_)
+@register_condition("lambda", LambdaCondition, cv.returning_lambda)
+async def lambda_condition_to_code(config, condition_id, template_arg, args):
+ lambda_ = await cg.process_lambda(config, args, return_type=bool)
+ return cg.new_Pvariable(condition_id, template_arg, lambda_)
-@register_condition('for', ForCondition, cv.Schema({
- cv.Required(CONF_TIME): cv.templatable(cv.positive_time_period_milliseconds),
- cv.Required(CONF_CONDITION): validate_potentially_and_condition,
-}).extend(cv.COMPONENT_SCHEMA))
-def for_condition_to_code(config, condition_id, template_arg, args):
- condition = yield build_condition(config[CONF_CONDITION], cg.TemplateArguments(), [])
+@register_condition(
+ "for",
+ ForCondition,
+ cv.Schema(
+ {
+ cv.Required(CONF_TIME): cv.templatable(
+ cv.positive_time_period_milliseconds
+ ),
+ cv.Required(CONF_CONDITION): validate_potentially_and_condition,
+ }
+ ).extend(cv.COMPONENT_SCHEMA),
+)
+async def for_condition_to_code(config, condition_id, template_arg, args):
+ condition = await build_condition(
+ config[CONF_CONDITION], cg.TemplateArguments(), []
+ )
var = cg.new_Pvariable(condition_id, template_arg, condition)
- yield cg.register_component(var, config)
- templ = yield cg.templatable(config[CONF_TIME], args, cg.uint32)
+ await cg.register_component(var, config)
+ templ = await cg.templatable(config[CONF_TIME], args, cg.uint32)
cg.add(var.set_time(templ))
- yield var
+ return var
-@register_action('delay', DelayAction, cv.templatable(cv.positive_time_period_milliseconds))
-def delay_action_to_code(config, action_id, template_arg, args):
+@register_action(
+ "delay", DelayAction, cv.templatable(cv.positive_time_period_milliseconds)
+)
+async def delay_action_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
- yield cg.register_component(var, {})
- template_ = yield cg.templatable(config, args, cg.uint32)
+ await cg.register_component(var, {})
+ template_ = await cg.templatable(config, args, cg.uint32)
cg.add(var.set_delay(template_))
- yield var
+ return var
-@register_action('if', IfAction, cv.All({
- cv.Required(CONF_CONDITION): validate_potentially_and_condition,
- cv.Optional(CONF_THEN): validate_action_list,
- cv.Optional(CONF_ELSE): validate_action_list,
-}, cv.has_at_least_one_key(CONF_THEN, CONF_ELSE)))
-def if_action_to_code(config, action_id, template_arg, args):
- conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
+@register_action(
+ "if",
+ IfAction,
+ cv.All(
+ {
+ cv.Required(CONF_CONDITION): validate_potentially_and_condition,
+ cv.Optional(CONF_THEN): validate_action_list,
+ cv.Optional(CONF_ELSE): validate_action_list,
+ },
+ cv.has_at_least_one_key(CONF_THEN, CONF_ELSE),
+ ),
+)
+async def if_action_to_code(config, action_id, template_arg, args):
+ conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
var = cg.new_Pvariable(action_id, template_arg, conditions)
if CONF_THEN in config:
- actions = yield build_action_list(config[CONF_THEN], template_arg, args)
+ actions = await build_action_list(config[CONF_THEN], template_arg, args)
cg.add(var.add_then(actions))
if CONF_ELSE in config:
- actions = yield build_action_list(config[CONF_ELSE], template_arg, args)
+ actions = await build_action_list(config[CONF_ELSE], template_arg, args)
cg.add(var.add_else(actions))
- yield var
+ return var
-@register_action('while', WhileAction, cv.Schema({
- cv.Required(CONF_CONDITION): validate_potentially_and_condition,
- cv.Required(CONF_THEN): validate_action_list,
-}))
-def while_action_to_code(config, action_id, template_arg, args):
- conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
+@register_action(
+ "while",
+ WhileAction,
+ cv.Schema(
+ {
+ cv.Required(CONF_CONDITION): validate_potentially_and_condition,
+ cv.Required(CONF_THEN): validate_action_list,
+ }
+ ),
+)
+async def while_action_to_code(config, action_id, template_arg, args):
+ conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
var = cg.new_Pvariable(action_id, template_arg, conditions)
- actions = yield build_action_list(config[CONF_THEN], template_arg, args)
+ actions = await build_action_list(config[CONF_THEN], template_arg, args)
cg.add(var.add_then(actions))
- yield var
+ return var
+
+
+@register_action(
+ "repeat",
+ RepeatAction,
+ cv.Schema(
+ {
+ cv.Required(CONF_COUNT): cv.templatable(cv.positive_not_null_int),
+ cv.Required(CONF_THEN): validate_action_list,
+ }
+ ),
+)
+async def repeat_action_to_code(config, action_id, template_arg, args):
+ var = cg.new_Pvariable(action_id, template_arg)
+ count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32)
+ cg.add(var.set_count(count_template))
+ actions = await build_action_list(config[CONF_THEN], template_arg, args)
+ cg.add(var.add_then(actions))
+ return var
def validate_wait_until(value):
- schema = cv.Schema({
- cv.Required(CONF_CONDITION): validate_potentially_and_condition,
- })
+ schema = cv.Schema(
+ {
+ cv.Required(CONF_CONDITION): validate_potentially_and_condition,
+ cv.Optional(CONF_TIMEOUT): cv.templatable(
+ cv.positive_time_period_milliseconds
+ ),
+ }
+ )
if isinstance(value, dict) and CONF_CONDITION in value:
return schema(value)
return validate_wait_until({CONF_CONDITION: value})
-@register_action('wait_until', WaitUntilAction, validate_wait_until)
-def wait_until_action_to_code(config, action_id, template_arg, args):
- conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
+@register_action("wait_until", WaitUntilAction, validate_wait_until)
+async def wait_until_action_to_code(config, action_id, template_arg, args):
+ conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
var = cg.new_Pvariable(action_id, template_arg, conditions)
- yield cg.register_component(var, {})
- yield var
+ if CONF_TIMEOUT in config:
+ template_ = await cg.templatable(config[CONF_TIMEOUT], args, cg.uint32)
+ cg.add(var.set_timeout_value(template_))
+ await cg.register_component(var, {})
+ return var
-@register_action('lambda', LambdaAction, cv.lambda_)
-def lambda_action_to_code(config, action_id, template_arg, args):
- lambda_ = yield cg.process_lambda(config, args, return_type=cg.void)
- yield cg.new_Pvariable(action_id, template_arg, lambda_)
+@register_action("lambda", LambdaAction, cv.lambda_)
+async def lambda_action_to_code(config, action_id, template_arg, args):
+ lambda_ = await cg.process_lambda(config, args, return_type=cg.void)
+ return cg.new_Pvariable(action_id, template_arg, lambda_)
-@register_action('component.update', UpdateComponentAction, maybe_simple_id({
- cv.Required(CONF_ID): cv.use_id(cg.PollingComponent),
-}))
-def component_update_action_to_code(config, action_id, template_arg, args):
- comp = yield cg.get_variable(config[CONF_ID])
- yield cg.new_Pvariable(action_id, template_arg, comp)
+@register_action(
+ "component.update",
+ UpdateComponentAction,
+ maybe_simple_id(
+ {
+ cv.Required(CONF_ID): cv.use_id(cg.PollingComponent),
+ }
+ ),
+)
+async def component_update_action_to_code(config, action_id, template_arg, args):
+ comp = await cg.get_variable(config[CONF_ID])
+ return cg.new_Pvariable(action_id, template_arg, comp)
-@coroutine
-def build_action(full_config, template_arg, args):
- registry_entry, config = cg.extract_registry_entry_config(ACTION_REGISTRY, full_config)
+async def build_action(full_config, template_arg, args):
+ registry_entry, config = cg.extract_registry_entry_config(
+ ACTION_REGISTRY, full_config
+ )
action_id = full_config[CONF_TYPE_ID]
builder = registry_entry.coroutine_fun
- yield builder(config, action_id, template_arg, args)
+ ret = await builder(config, action_id, template_arg, args)
+ return ret
-@coroutine
-def build_action_list(config, templ, arg_type):
+async def build_action_list(config, templ, arg_type):
actions = []
for conf in config:
- action = yield build_action(conf, templ, arg_type)
+ action = await build_action(conf, templ, arg_type)
actions.append(action)
- yield actions
+ return actions
-@coroutine
-def build_condition(full_config, template_arg, args):
- registry_entry, config = cg.extract_registry_entry_config(CONDITION_REGISTRY, full_config)
+async def build_condition(full_config, template_arg, args):
+ registry_entry, config = cg.extract_registry_entry_config(
+ CONDITION_REGISTRY, full_config
+ )
action_id = full_config[CONF_TYPE_ID]
builder = registry_entry.coroutine_fun
- yield builder(config, action_id, template_arg, args)
+ ret = await builder(config, action_id, template_arg, args)
+ return ret
-@coroutine
-def build_condition_list(config, templ, args):
+async def build_condition_list(config, templ, args):
conditions = []
for conf in config:
- condition = yield build_condition(conf, templ, args)
+ condition = await build_condition(conf, templ, args)
conditions.append(condition)
- yield conditions
+ return conditions
-@coroutine
-def build_automation(trigger, args, config):
+async def build_automation(trigger, args, config):
arg_types = [arg[0] for arg in args]
templ = cg.TemplateArguments(*arg_types)
obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ, trigger)
- actions = yield build_action_list(config[CONF_THEN], templ, args)
+ actions = await build_action_list(config[CONF_THEN], templ, args)
cg.add(obj.add_actions(actions))
- yield obj
+ return obj
diff --git a/esphome/codegen.py b/esphome/codegen.py
index c30b43f952..5e1e934e58 100644
--- a/esphome/codegen.py
+++ b/esphome/codegen.py
@@ -9,18 +9,77 @@
# pylint: disable=unused-import
from esphome.cpp_generator import ( # noqa
- Expression, RawExpression, RawStatement, TemplateArguments,
- StructInitializer, ArrayInitializer, safe_exp, Statement, LineComment,
- progmem_array, statement, variable, Pvariable, new_Pvariable,
- add, add_global, add_library, add_build_flag, add_define,
- get_variable, get_variable_with_full_id, process_lambda, is_template, templatable, MockObj,
- MockObjClass)
+ Expression,
+ RawExpression,
+ RawStatement,
+ TemplateArguments,
+ StructInitializer,
+ ArrayInitializer,
+ safe_exp,
+ Statement,
+ LineComment,
+ progmem_array,
+ static_const_array,
+ statement,
+ variable,
+ new_variable,
+ Pvariable,
+ new_Pvariable,
+ add,
+ add_global,
+ add_library,
+ add_build_flag,
+ add_define,
+ add_platformio_option,
+ get_variable,
+ get_variable_with_full_id,
+ process_lambda,
+ is_template,
+ templatable,
+ MockObj,
+ MockObjClass,
+)
from esphome.cpp_helpers import ( # noqa
- gpio_pin_expression, register_component, build_registry_entry,
- build_registry_list, extract_registry_entry_config, register_parented)
+ gpio_pin_expression,
+ register_component,
+ build_registry_entry,
+ build_registry_list,
+ extract_registry_entry_config,
+ register_parented,
+)
from esphome.cpp_types import ( # noqa
- global_ns, void, nullptr, float_, double, bool_, int_, std_ns, std_string,
- std_vector, uint8, uint16, uint32, int32, const_char_ptr, NAN,
- esphome_ns, App, Nameable, Component, ComponentPtr,
- PollingComponent, Application, optional, arduino_json_ns, JsonObject,
- JsonObjectRef, JsonObjectConstRef, Controller, GPIOPin)
+ global_ns,
+ void,
+ nullptr,
+ float_,
+ double,
+ bool_,
+ int_,
+ std_ns,
+ std_string,
+ std_vector,
+ uint8,
+ uint16,
+ uint32,
+ uint64,
+ int32,
+ const_char_ptr,
+ NAN,
+ esphome_ns,
+ App,
+ EntityBase,
+ Component,
+ ComponentPtr,
+ PollingComponent,
+ Application,
+ optional,
+ arduino_json_ns,
+ JsonObject,
+ JsonObjectRef,
+ JsonObjectConstRef,
+ Controller,
+ GPIOPin,
+ InternalGPIOPin,
+ gpio_Flags,
+ EntityCategory,
+)
diff --git a/esphome/components/a4988/a4988.cpp b/esphome/components/a4988/a4988.cpp
index 99b677a9ab..429fa25648 100644
--- a/esphome/components/a4988/a4988.cpp
+++ b/esphome/components/a4988/a4988.cpp
@@ -4,13 +4,14 @@
namespace esphome {
namespace a4988 {
-static const char *TAG = "a4988.stepper";
+static const char *const 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->sleep_pin_state_ = false;
}
this->step_pin_->setup();
this->step_pin_->digital_write(false);
@@ -27,7 +28,12 @@ void A4988::dump_config() {
void A4988::loop() {
bool at_target = this->has_reached_target();
if (this->sleep_pin_ != nullptr) {
+ bool sleep_rising_edge = !sleep_pin_state_ & !at_target;
this->sleep_pin_->digital_write(!at_target);
+ this->sleep_pin_state_ = !at_target;
+ if (sleep_rising_edge) {
+ delayMicroseconds(1000);
+ }
}
if (at_target) {
this->high_freq_.stop();
diff --git a/esphome/components/a4988/a4988.h b/esphome/components/a4988/a4988.h
index 10fb5e0015..0fe7891110 100644
--- a/esphome/components/a4988/a4988.h
+++ b/esphome/components/a4988/a4988.h
@@ -1,7 +1,7 @@
#pragma once
#include "esphome/core/component.h"
-#include "esphome/core/esphal.h"
+#include "esphome/core/hal.h"
#include "esphome/components/stepper/stepper.h"
namespace esphome {
@@ -21,6 +21,7 @@ class A4988 : public stepper::Stepper, public Component {
GPIOPin *step_pin_;
GPIOPin *dir_pin_;
GPIOPin *sleep_pin_{nullptr};
+ bool sleep_pin_state_;
HighFrequencyLoopRequester high_freq_;
};
diff --git a/esphome/components/a4988/stepper.py b/esphome/components/a4988/stepper.py
index 29696dbd5e..7f53856c7b 100644
--- a/esphome/components/a4988/stepper.py
+++ b/esphome/components/a4988/stepper.py
@@ -5,27 +5,29 @@ 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)
+a4988_ns = cg.esphome_ns.namespace("a4988")
+A4988 = a4988_ns.class_("A4988", stepper.Stepper, cg.Component)
-CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend({
- cv.Required(CONF_ID): cv.declare_id(A4988),
- cv.Required(CONF_STEP_PIN): pins.gpio_output_pin_schema,
- cv.Required(CONF_DIR_PIN): pins.gpio_output_pin_schema,
- cv.Optional(CONF_SLEEP_PIN): pins.gpio_output_pin_schema,
-}).extend(cv.COMPONENT_SCHEMA)
+CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend(
+ {
+ cv.Required(CONF_ID): cv.declare_id(A4988),
+ cv.Required(CONF_STEP_PIN): pins.gpio_output_pin_schema,
+ cv.Required(CONF_DIR_PIN): pins.gpio_output_pin_schema,
+ cv.Optional(CONF_SLEEP_PIN): pins.gpio_output_pin_schema,
+ }
+).extend(cv.COMPONENT_SCHEMA)
-def to_code(config):
+async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
- yield cg.register_component(var, config)
- yield stepper.register_stepper(var, config)
+ await cg.register_component(var, config)
+ await stepper.register_stepper(var, config)
- step_pin = yield cg.gpio_pin_expression(config[CONF_STEP_PIN])
+ step_pin = await cg.gpio_pin_expression(config[CONF_STEP_PIN])
cg.add(var.set_step_pin(step_pin))
- dir_pin = yield cg.gpio_pin_expression(config[CONF_DIR_PIN])
+ dir_pin = await cg.gpio_pin_expression(config[CONF_DIR_PIN])
cg.add(var.set_dir_pin(dir_pin))
if CONF_SLEEP_PIN in config:
- sleep_pin = yield cg.gpio_pin_expression(config[CONF_SLEEP_PIN])
+ sleep_pin = await cg.gpio_pin_expression(config[CONF_SLEEP_PIN])
cg.add(var.set_sleep_pin(sleep_pin))
diff --git a/esphome/components/ac_dimmer/ac_dimmer.cpp b/esphome/components/ac_dimmer/ac_dimmer.cpp
index a60cc9e29a..a7f1e6f3a9 100644
--- a/esphome/components/ac_dimmer/ac_dimmer.cpp
+++ b/esphome/components/ac_dimmer/ac_dimmer.cpp
@@ -1,28 +1,37 @@
+#ifdef USE_ARDUINO
+
#include "ac_dimmer.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
+#include
-#ifdef ARDUINO_ARCH_ESP8266
+#ifdef USE_ESP8266
#include
#endif
+#ifdef USE_ESP32_FRAMEWORK_ARDUINO
+#include
+#endif
namespace esphome {
namespace ac_dimmer {
-static const char *TAG = "ac_dimmer";
+static const char *const TAG = "ac_dimmer";
// Global array to store dimmer objects
-static AcDimmerDataStore *all_dimmers[32];
+static AcDimmerDataStore *all_dimmers[32]; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
/// Time in microseconds the gate should be held high
/// 10µs should be long enough for most triacs
/// For reference: BT136 datasheet says 2µs nominal (page 7)
-static uint32_t GATE_ENABLE_TIME = 10;
+/// However other factors like gate driver propagation time
+/// are also considered and a really low value is not important
+/// See also: https://github.com/esphome/issues/issues/1632
+static const uint32_t GATE_ENABLE_TIME = 50;
/// Function called from timer interrupt
/// Input is current time in microseconds (micros())
/// Returns when next "event" is expected in µs, or 0 if no such event known.
-uint32_t ICACHE_RAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
+uint32_t IRAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
// If no ZC signal received yet.
if (this->crossed_zero_at == 0)
return 0;
@@ -34,13 +43,13 @@ uint32_t ICACHE_RAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
if (this->enable_time_us != 0 && time_since_zc >= this->enable_time_us) {
this->enable_time_us = 0;
- this->gate_pin->digital_write(true);
+ this->gate_pin.digital_write(true);
// Prevent too short pulses
- this->disable_time_us = max(this->disable_time_us, time_since_zc + GATE_ENABLE_TIME);
+ this->disable_time_us = std::max(this->disable_time_us, time_since_zc + GATE_ENABLE_TIME);
}
if (this->disable_time_us != 0 && time_since_zc >= this->disable_time_us) {
this->disable_time_us = 0;
- this->gate_pin->digital_write(false);
+ this->gate_pin.digital_write(false);
}
if (time_since_zc < this->enable_time_us)
@@ -60,7 +69,7 @@ uint32_t ICACHE_RAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
}
/// Run timer interrupt code and return in how many µs the next event is expected
-uint32_t ICACHE_RAM_ATTR HOT timer_interrupt() {
+uint32_t IRAM_ATTR HOT timer_interrupt() {
// run at least with 1kHz
uint32_t min_dt_us = 1000;
uint32_t now = micros();
@@ -77,7 +86,7 @@ uint32_t ICACHE_RAM_ATTR HOT timer_interrupt() {
}
/// GPIO interrupt routine, called when ZC pin triggers
-void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
+void IRAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
uint32_t prev_crossed = this->crossed_zero_at;
// 50Hz mains frequency should give a half cycle of 10ms a 60Hz will give 8.33ms
@@ -94,7 +103,7 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
if (this->value == 65535) {
// fully on, enable output immediately
- this->gate_pin->digital_write(true);
+ this->gate_pin.digital_write(true);
} else if (this->init_cycle) {
// send a full cycle
this->init_cycle = false;
@@ -102,30 +111,30 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
this->disable_time_us = cycle_time_us;
} else if (this->value == 0) {
// fully off, disable output immediately
- this->gate_pin->digital_write(false);
+ this->gate_pin.digital_write(false);
} else {
if (this->method == DIM_METHOD_TRAILING) {
this->enable_time_us = 1; // cannot be 0
- this->disable_time_us = max((uint32_t) 10, this->value * this->cycle_time_us / 65535);
+ this->disable_time_us = std::max((uint32_t) 10, this->value * this->cycle_time_us / 65535);
} else {
// calculate time until enable in µs: (1.0-value)*cycle_time, but with integer arithmetic
// also take into account min_power
auto min_us = this->cycle_time_us * this->min_power / 1000;
- this->enable_time_us = max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535);
+ this->enable_time_us = std::max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535);
if (this->method == DIM_METHOD_LEADING_PULSE) {
// Minimum pulse time should be enough for the triac to trigger when it is close to the ZC zone
// this is for brightness near 99%
- this->disable_time_us = max(this->enable_time_us + GATE_ENABLE_TIME, (uint32_t) cycle_time_us / 10);
+ this->disable_time_us = std::max(this->enable_time_us + GATE_ENABLE_TIME, (uint32_t) cycle_time_us / 10);
} else {
- this->gate_pin->digital_write(false);
+ this->gate_pin.digital_write(false);
this->disable_time_us = this->cycle_time_us;
}
}
}
}
-void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) {
- // Attaching pin interrupts on the same pin will override the previous interupt
+void IRAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) {
+ // Attaching pin interrupts on the same pin will override the previous interrupt
// However, the user expects that multiple dimmers sharing the same ZC pin will work.
// We solve this in a bit of a hacky way: On each pin interrupt, we check all dimmers
// if any of them are using the same ZC pin, and also trigger the interrupt for *them*.
@@ -138,11 +147,11 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store
}
}
-#ifdef ARDUINO_ARCH_ESP32
+#ifdef USE_ESP32
// ESP32 implementation, uses basically the same code but needs to wrap
// timer_interrupt() function to auto-reschedule
-static hw_timer_t *dimmer_timer = nullptr;
-void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); }
+static hw_timer_t *dimmer_timer = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
+void IRAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); }
#endif
void AcDimmer::setup() {
@@ -171,15 +180,16 @@ void AcDimmer::setup() {
if (setup_zero_cross_pin) {
this->zero_cross_pin_->setup();
this->store_.zero_cross_pin = this->zero_cross_pin_->to_isr();
- this->zero_cross_pin_->attach_interrupt(&AcDimmerDataStore::s_gpio_intr, &this->store_, FALLING);
+ this->zero_cross_pin_->attach_interrupt(&AcDimmerDataStore::s_gpio_intr, &this->store_,
+ gpio::INTERRUPT_FALLING_EDGE);
}
-#ifdef ARDUINO_ARCH_ESP8266
+#ifdef USE_ESP8266
// Uses ESP8266 waveform (soft PWM) class
// PWM and AcDimmer can even run at the same time this way
setTimer1Callback(&timer_interrupt);
#endif
-#ifdef ARDUINO_ARCH_ESP32
+#ifdef USE_ESP32
// 80 Divider -> 1 count=1µs
dimmer_timer = timerBegin(0, 80, true);
timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr, true);
@@ -215,3 +225,5 @@ void AcDimmer::dump_config() {
} // namespace ac_dimmer
} // namespace esphome
+
+#endif // USE_ARDUINO
diff --git a/esphome/components/ac_dimmer/ac_dimmer.h b/esphome/components/ac_dimmer/ac_dimmer.h
index 00da061cfd..fd1bbc28db 100644
--- a/esphome/components/ac_dimmer/ac_dimmer.h
+++ b/esphome/components/ac_dimmer/ac_dimmer.h
@@ -1,7 +1,9 @@
#pragma once
+#ifdef USE_ARDUINO
+
#include "esphome/core/component.h"
-#include "esphome/core/esphal.h"
+#include "esphome/core/hal.h"
#include "esphome/components/output/float_output.h"
namespace esphome {
@@ -11,11 +13,11 @@ enum DimMethod { DIM_METHOD_LEADING_PULSE = 0, DIM_METHOD_LEADING, DIM_METHOD_TR
struct AcDimmerDataStore {
/// Zero-cross pin
- ISRInternalGPIOPin *zero_cross_pin;
+ ISRInternalGPIOPin zero_cross_pin;
/// Zero-cross pin number - used to share ZC pin across multiple dimmers
uint8_t zero_cross_pin_number;
/// Output pin to write to
- ISRInternalGPIOPin *gate_pin;
+ ISRInternalGPIOPin gate_pin;
/// Value of the dimmer - 0 to 65535.
uint16_t value;
/// Minimum power for activation
@@ -37,7 +39,7 @@ struct AcDimmerDataStore {
void gpio_intr();
static void s_gpio_intr(AcDimmerDataStore *store);
-#ifdef ARDUINO_ARCH_ESP32
+#ifdef USE_ESP32
static void s_timer_intr();
#endif
};
@@ -47,16 +49,16 @@ class AcDimmer : public output::FloatOutput, public Component {
void setup() override;
void dump_config() override;
- void set_gate_pin(GPIOPin *gate_pin) { gate_pin_ = gate_pin; }
- void set_zero_cross_pin(GPIOPin *zero_cross_pin) { zero_cross_pin_ = zero_cross_pin; }
+ void set_gate_pin(InternalGPIOPin *gate_pin) { gate_pin_ = gate_pin; }
+ void set_zero_cross_pin(InternalGPIOPin *zero_cross_pin) { zero_cross_pin_ = zero_cross_pin; }
void set_init_with_half_cycle(bool init_with_half_cycle) { init_with_half_cycle_ = init_with_half_cycle; }
void set_method(DimMethod method) { method_ = method; }
protected:
void write_state(float state) override;
- GPIOPin *gate_pin_;
- GPIOPin *zero_cross_pin_;
+ InternalGPIOPin *gate_pin_;
+ InternalGPIOPin *zero_cross_pin_;
AcDimmerDataStore store_;
bool init_with_half_cycle_;
DimMethod method_;
@@ -64,3 +66,5 @@ class AcDimmer : public output::FloatOutput, public Component {
} // namespace ac_dimmer
} // namespace esphome
+
+#endif // USE_ARDUINO
diff --git a/esphome/components/ac_dimmer/output.py b/esphome/components/ac_dimmer/output.py
index 16f04ac984..c39fc382b6 100644
--- a/esphome/components/ac_dimmer/output.py
+++ b/esphome/components/ac_dimmer/output.py
@@ -4,40 +4,49 @@ from esphome import pins
from esphome.components import output
from esphome.const import CONF_ID, CONF_MIN_POWER, CONF_METHOD
-ac_dimmer_ns = cg.esphome_ns.namespace('ac_dimmer')
-AcDimmer = ac_dimmer_ns.class_('AcDimmer', output.FloatOutput, cg.Component)
+CODEOWNERS = ["@glmnet"]
-DimMethod = ac_dimmer_ns.enum('DimMethod')
+ac_dimmer_ns = cg.esphome_ns.namespace("ac_dimmer")
+AcDimmer = ac_dimmer_ns.class_("AcDimmer", output.FloatOutput, cg.Component)
+
+DimMethod = ac_dimmer_ns.enum("DimMethod")
DIM_METHODS = {
- 'LEADING_PULSE': DimMethod.DIM_METHOD_LEADING_PULSE,
- 'LEADING': DimMethod.DIM_METHOD_LEADING,
- 'TRAILING': DimMethod.DIM_METHOD_TRAILING,
+ "LEADING_PULSE": DimMethod.DIM_METHOD_LEADING_PULSE,
+ "LEADING": DimMethod.DIM_METHOD_LEADING,
+ "TRAILING": DimMethod.DIM_METHOD_TRAILING,
}
-CONF_GATE_PIN = 'gate_pin'
-CONF_ZERO_CROSS_PIN = 'zero_cross_pin'
-CONF_INIT_WITH_HALF_CYCLE = 'init_with_half_cycle'
-CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend({
- cv.Required(CONF_ID): cv.declare_id(AcDimmer),
- cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema,
- cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema,
- cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean,
- cv.Optional(CONF_METHOD, default='leading pulse'): cv.enum(DIM_METHODS, upper=True, space='_'),
-}).extend(cv.COMPONENT_SCHEMA)
+CONF_GATE_PIN = "gate_pin"
+CONF_ZERO_CROSS_PIN = "zero_cross_pin"
+CONF_INIT_WITH_HALF_CYCLE = "init_with_half_cycle"
+CONFIG_SCHEMA = cv.All(
+ output.FLOAT_OUTPUT_SCHEMA.extend(
+ {
+ cv.Required(CONF_ID): cv.declare_id(AcDimmer),
+ cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema,
+ cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema,
+ cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean,
+ cv.Optional(CONF_METHOD, default="leading pulse"): cv.enum(
+ DIM_METHODS, upper=True, space="_"
+ ),
+ }
+ ).extend(cv.COMPONENT_SCHEMA),
+ cv.only_with_arduino,
+)
-def to_code(config):
+async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
- yield cg.register_component(var, config)
+ await cg.register_component(var, config)
# override default min power to 10%
if CONF_MIN_POWER not in config:
config[CONF_MIN_POWER] = 0.1
- yield output.register_output(var, config)
+ await output.register_output(var, config)
- pin = yield cg.gpio_pin_expression(config[CONF_GATE_PIN])
+ pin = await cg.gpio_pin_expression(config[CONF_GATE_PIN])
cg.add(var.set_gate_pin(pin))
- pin = yield cg.gpio_pin_expression(config[CONF_ZERO_CROSS_PIN])
+ pin = await cg.gpio_pin_expression(config[CONF_ZERO_CROSS_PIN])
cg.add(var.set_zero_cross_pin(pin))
cg.add(var.set_init_with_half_cycle(config[CONF_INIT_WITH_HALF_CYCLE]))
cg.add(var.set_method(config[CONF_METHOD]))
diff --git a/esphome/components/adalight/__init__.py b/esphome/components/adalight/__init__.py
new file mode 100644
index 0000000000..919ffecbea
--- /dev/null
+++ b/esphome/components/adalight/__init__.py
@@ -0,0 +1,27 @@
+import esphome.codegen as cg
+import esphome.config_validation as cv
+from esphome.components import uart
+from esphome.components.light.types import AddressableLightEffect
+from esphome.components.light.effects import register_addressable_effect
+from esphome.const import CONF_NAME, CONF_UART_ID
+
+DEPENDENCIES = ["uart"]
+
+adalight_ns = cg.esphome_ns.namespace("adalight")
+AdalightLightEffect = adalight_ns.class_(
+ "AdalightLightEffect", uart.UARTDevice, AddressableLightEffect
+)
+
+CONFIG_SCHEMA = cv.Schema({})
+
+
+@register_addressable_effect(
+ "adalight",
+ AdalightLightEffect,
+ "Adalight",
+ {cv.GenerateID(CONF_UART_ID): cv.use_id(uart.UARTComponent)},
+)
+async def adalight_light_effect_to_code(config, effect_id):
+ effect = cg.new_Pvariable(effect_id, config[CONF_NAME])
+ await uart.register_uart_device(effect, config)
+ return effect
diff --git a/esphome/components/adalight/adalight_light_effect.cpp b/esphome/components/adalight/adalight_light_effect.cpp
new file mode 100644
index 0000000000..35e98d7360
--- /dev/null
+++ b/esphome/components/adalight/adalight_light_effect.cpp
@@ -0,0 +1,142 @@
+#include "adalight_light_effect.h"
+#include "esphome/core/log.h"
+
+namespace esphome {
+namespace adalight {
+
+static const char *const TAG = "adalight_light_effect";
+
+static const uint32_t ADALIGHT_ACK_INTERVAL = 1000;
+static const uint32_t ADALIGHT_RECEIVE_TIMEOUT = 1000;
+
+AdalightLightEffect::AdalightLightEffect(const std::string &name) : AddressableLightEffect(name) {}
+
+void AdalightLightEffect::start() {
+ AddressableLightEffect::start();
+
+ last_ack_ = 0;
+ last_byte_ = 0;
+ last_reset_ = 0;
+}
+
+void AdalightLightEffect::stop() {
+ frame_.resize(0);
+
+ AddressableLightEffect::stop();
+}
+
+unsigned int AdalightLightEffect::get_frame_size_(int led_count) const {
+ // 3 bytes: Ada
+ // 2 bytes: LED count
+ // 1 byte: checksum
+ // 3 bytes per LED
+ return 3 + 2 + 1 + led_count * 3;
+}
+
+void AdalightLightEffect::reset_frame_(light::AddressableLight &it) {
+ int buffer_capacity = get_frame_size_(it.size());
+
+ frame_.clear();
+ frame_.reserve(buffer_capacity);
+}
+
+void AdalightLightEffect::blank_all_leds_(light::AddressableLight &it) {
+ for (int led = it.size(); led-- > 0;) {
+ it[led].set(Color::BLACK);
+ }
+ it.schedule_show();
+}
+
+void AdalightLightEffect::apply(light::AddressableLight &it, const Color ¤t_color) {
+ const uint32_t now = millis();
+
+ if (now - this->last_ack_ >= ADALIGHT_ACK_INTERVAL) {
+ ESP_LOGV(TAG, "Sending ACK");
+ this->write_str("Ada\n");
+ this->last_ack_ = now;
+ }
+
+ if (!this->last_reset_) {
+ ESP_LOGW(TAG, "Frame: Reset.");
+ reset_frame_(it);
+ blank_all_leds_(it);
+ this->last_reset_ = now;
+ }
+
+ if (!this->frame_.empty() && now - this->last_byte_ >= ADALIGHT_RECEIVE_TIMEOUT) {
+ ESP_LOGW(TAG, "Frame: Receive timeout (size=%zu).", this->frame_.size());
+ reset_frame_(it);
+ blank_all_leds_(it);
+ }
+
+ if (this->available() > 0) {
+ ESP_LOGV(TAG, "Frame: Available (size=%d).", this->available());
+ }
+
+ while (this->available() != 0) {
+ uint8_t data;
+ if (!this->read_byte(&data))
+ break;
+ this->frame_.push_back(data);
+ this->last_byte_ = now;
+
+ switch (this->parse_frame_(it)) {
+ case INVALID:
+ ESP_LOGD(TAG, "Frame: Invalid (size=%zu, first=%d).", this->frame_.size(), this->frame_[0]);
+ reset_frame_(it);
+ break;
+
+ case PARTIAL:
+ break;
+
+ case CONSUMED:
+ ESP_LOGV(TAG, "Frame: Consumed (size=%zu).", this->frame_.size());
+ reset_frame_(it);
+ break;
+ }
+ }
+}
+
+AdalightLightEffect::Frame AdalightLightEffect::parse_frame_(light::AddressableLight &it) {
+ if (frame_.empty())
+ return INVALID;
+
+ // Check header: `Ada`
+ if (frame_[0] != 'A')
+ return INVALID;
+ if (frame_.size() > 1 && frame_[1] != 'd')
+ return INVALID;
+ if (frame_.size() > 2 && frame_[2] != 'a')
+ return INVALID;
+
+ // 3 bytes: Count Hi, Count Lo, Checksum
+ if (frame_.size() < 6)
+ return PARTIAL;
+
+ // Check checksum
+ uint16_t checksum = frame_[3] ^ frame_[4] ^ 0x55;
+ if (checksum != frame_[5])
+ return INVALID;
+
+ // Check if we received the full frame
+ uint16_t led_count = (frame_[3] << 8) + frame_[4] + 1;
+ auto buffer_size = get_frame_size_(led_count);
+ if (frame_.size() < buffer_size)
+ return PARTIAL;
+
+ // Apply lights
+ auto accepted_led_count = std::min(led_count, it.size());
+ uint8_t *led_data = &frame_[6];
+
+ for (int led = 0; led < accepted_led_count; led++, led_data += 3) {
+ auto white = std::min(std::min(led_data[0], led_data[1]), led_data[2]);
+
+ it[led].set(Color(led_data[0], led_data[1], led_data[2], white));
+ }
+
+ it.schedule_show();
+ return CONSUMED;
+}
+
+} // namespace adalight
+} // namespace esphome
diff --git a/esphome/components/adalight/adalight_light_effect.h b/esphome/components/adalight/adalight_light_effect.h
new file mode 100644
index 0000000000..b757191864
--- /dev/null
+++ b/esphome/components/adalight/adalight_light_effect.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include "esphome/core/component.h"
+#include "esphome/components/light/addressable_light_effect.h"
+#include "esphome/components/uart/uart.h"
+
+#include
+
+namespace esphome {
+namespace adalight {
+
+class AdalightLightEffect : public light::AddressableLightEffect, public uart::UARTDevice {
+ public:
+ AdalightLightEffect(const std::string &name);
+
+ public:
+ void start() override;
+ void stop() override;
+ void apply(light::AddressableLight &it, const Color ¤t_color) override;
+
+ protected:
+ enum Frame {
+ INVALID,
+ PARTIAL,
+ CONSUMED,
+ };
+
+ unsigned int get_frame_size_(int led_count) const;
+ void reset_frame_(light::AddressableLight &it);
+ void blank_all_leds_(light::AddressableLight &it);
+ Frame parse_frame_(light::AddressableLight &it);
+
+ protected:
+ uint32_t last_ack_{0};
+ uint32_t last_byte_{0};
+ uint32_t last_reset_{0};
+ std::vector frame_;
+};
+
+} // namespace adalight
+} // namespace esphome
diff --git a/esphome/components/adc/__init__.py b/esphome/components/adc/__init__.py
index e69de29bb2..f70ffa9520 100644
--- a/esphome/components/adc/__init__.py
+++ b/esphome/components/adc/__init__.py
@@ -0,0 +1 @@
+CODEOWNERS = ["@esphome/core"]
diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp
index 2c448d0392..0a439f8b8d 100644
--- a/esphome/components/adc/adc_sensor.cpp
+++ b/esphome/components/adc/adc_sensor.cpp
@@ -1,90 +1,168 @@
#include "adc_sensor.h"
#include "esphome/core/log.h"
+#include "esphome/core/helpers.h"
+#ifdef USE_ESP8266
#ifdef USE_ADC_SENSOR_VCC
+#include
ADC_MODE(ADC_VCC)
+#else
+#include
+#endif
#endif
namespace esphome {
namespace adc {
-static const char *TAG = "adc";
-
-#ifdef ARDUINO_ARCH_ESP32
-void ADCSensor::set_attenuation(adc_attenuation_t attenuation) { this->attenuation_ = attenuation; }
-#endif
+static const char *const TAG = "adc";
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_);
+#ifndef USE_ADC_SENSOR_VCC
+ pin_->setup();
#endif
+
+#ifdef USE_ESP32
+ adc1_config_width(ADC_WIDTH_BIT_12);
+ if (!autorange_) {
+ adc1_config_channel_atten(channel_, attenuation_);
+ }
+
+ // load characteristics for each attenuation
+ for (int i = 0; i < (int) ADC_ATTEN_MAX; i++) {
+ auto cal_value = esp_adc_cal_characterize(ADC_UNIT_1, (adc_atten_t) i, ADC_WIDTH_BIT_12,
+ 1100, // default vref
+ &cal_characteristics_[i]);
+ switch (cal_value) {
+ case ESP_ADC_CAL_VAL_EFUSE_VREF:
+ ESP_LOGV(TAG, "Using eFuse Vref for calibration");
+ break;
+ case ESP_ADC_CAL_VAL_EFUSE_TP:
+ ESP_LOGV(TAG, "Using two-point eFuse Vref for calibration");
+ break;
+ case ESP_ADC_CAL_VAL_DEFAULT_VREF:
+ default:
+ break;
+ }
+ }
+
+ // adc_gpio_init doesn't exist on ESP32-C3 or ESP32-H2
+#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32H2)
+ adc_gpio_init(ADC_UNIT_1, (adc_channel_t) channel_);
+#endif
+#endif // USE_ESP32
}
+
void ADCSensor::dump_config() {
LOG_SENSOR("", "ADC Sensor", this);
-#ifdef ARDUINO_ARCH_ESP8266
+#ifdef USE_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;
- }
+ LOG_PIN(" Pin: ", pin_);
#endif
+#endif // USE_ESP8266
+
+#ifdef USE_ESP32
+ LOG_PIN(" Pin: ", pin_);
+ if (autorange_)
+ ESP_LOGCONFIG(TAG, " Attenuation: auto");
+ else
+ switch (this->attenuation_) {
+ case ADC_ATTEN_DB_0:
+ ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
+ break;
+ case ADC_ATTEN_DB_2_5:
+ ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)");
+ break;
+ case ADC_ATTEN_DB_6:
+ ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)");
+ break;
+ case ADC_ATTEN_DB_11:
+ ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)");
+ break;
+ default: // This is to satisfy the unused ADC_ATTEN_MAX
+ break;
+ }
+#endif // USE_ESP32
LOG_UPDATE_INTERVAL(this);
}
+
float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
void ADCSensor::update() {
float value_v = this->sample();
- ESP_LOGD(TAG, "'%s': Got voltage=%.2fV", this->get_name().c_str(), value_v);
+ ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
this->publish_state(value_v);
}
+
+#ifdef USE_ESP8266
float ADCSensor::sample() {
-#ifdef ARDUINO_ARCH_ESP32
- float value_v = analogRead(this->pin_) / 4095.0f;
- switch (this->attenuation_) {
- case ADC_0db:
- value_v *= 1.1;
- break;
- case ADC_2_5db:
- value_v *= 1.5;
- break;
- case ADC_6db:
- value_v *= 2.2;
- break;
- case ADC_11db:
- value_v *= 3.9;
- break;
+#ifdef USE_ADC_SENSOR_VCC
+ int raw = ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance)
+#else
+ int raw = analogRead(this->pin_->get_pin()); // NOLINT
+#endif
+ if (output_raw_) {
+ return raw;
}
- return value_v;
+ return raw / 1024.0f;
+}
#endif
-#ifdef ARDUINO_ARCH_ESP8266
-#ifdef USE_ADC_SENSOR_VCC
- return ESP.getVcc() / 1024.0f;
-#else
- return analogRead(this->pin_) / 1024.0f;
-#endif
-#endif
+#ifdef USE_ESP32
+float ADCSensor::sample() {
+ if (!autorange_) {
+ int raw = adc1_get_raw(channel_);
+ if (raw == -1) {
+ return NAN;
+ }
+ if (output_raw_) {
+ return raw;
+ }
+ uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int) attenuation_]);
+ return mv / 1000.0f;
+ }
+
+ int raw11, raw6 = 4095, raw2 = 4095, raw0 = 4095;
+ adc1_config_channel_atten(channel_, ADC_ATTEN_DB_11);
+ raw11 = adc1_get_raw(channel_);
+ if (raw11 < 4095) {
+ adc1_config_channel_atten(channel_, ADC_ATTEN_DB_6);
+ raw6 = adc1_get_raw(channel_);
+ if (raw6 < 4095) {
+ adc1_config_channel_atten(channel_, ADC_ATTEN_DB_2_5);
+ raw2 = adc1_get_raw(channel_);
+ if (raw2 < 4095) {
+ adc1_config_channel_atten(channel_, ADC_ATTEN_DB_0);
+ raw0 = adc1_get_raw(channel_);
+ }
+ }
+ }
+
+ if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw11 == -1) {
+ return NAN;
+ }
+
+ uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int) ADC_ATTEN_DB_11]);
+ uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int) ADC_ATTEN_DB_6]);
+ uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int) ADC_ATTEN_DB_2_5]);
+ uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int) ADC_ATTEN_DB_0]);
+
+ // Contribution of each value, in range 0-2048
+ uint32_t c11 = std::min(raw11, 2048);
+ uint32_t c6 = 2048 - std::abs(raw6 - 2048);
+ uint32_t c2 = 2048 - std::abs(raw2 - 2048);
+ uint32_t c0 = std::min(4095 - raw0, 2048);
+ // max theoretical csum value is 2048*4 = 8192
+ uint32_t csum = c11 + c6 + c2 + c0;
+
+ // each mv is max 3900; so max value is 3900*2048*4, fits in unsigned
+ uint32_t mv_scaled = (mv11 * c11) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0);
+ return mv_scaled / (float) (csum * 1000U);
}
-#ifdef ARDUINO_ARCH_ESP8266
+#endif // USE_ESP32
+
+#ifdef USE_ESP8266
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
#endif
diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h
index 3a08ff6be4..12272a1577 100644
--- a/esphome/components/adc/adc_sensor.h
+++ b/esphome/components/adc/adc_sensor.h
@@ -1,19 +1,26 @@
#pragma once
#include "esphome/core/component.h"
-#include "esphome/core/esphal.h"
+#include "esphome/core/hal.h"
#include "esphome/core/defines.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/voltage_sampler/voltage_sampler.h"
+#ifdef USE_ESP32
+#include "driver/adc.h"
+#include
+#endif
+
namespace esphome {
namespace adc {
class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
public:
-#ifdef ARDUINO_ARCH_ESP32
+#ifdef USE_ESP32
/// Set the attenuation for this pin. Only available on the ESP32.
- void set_attenuation(adc_attenuation_t attenuation);
+ void set_attenuation(adc_atten_t attenuation) { attenuation_ = attenuation; }
+ void set_channel(adc1_channel_t channel) { channel_ = channel; }
+ void set_autorange(bool autorange) { autorange_ = autorange; }
#endif
/// Update adc values.
@@ -23,18 +30,23 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
void dump_config() override;
/// `HARDWARE_LATE` setup priority.
float get_setup_priority() const override;
- void set_pin(uint8_t pin) { this->pin_ = pin; }
+ void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
+ void set_output_raw(bool output_raw) { output_raw_ = output_raw; }
float sample() override;
-#ifdef ARDUINO_ARCH_ESP8266
+#ifdef USE_ESP8266
std::string unique_id() override;
#endif
protected:
- uint8_t pin_;
+ InternalGPIOPin *pin_;
+ bool output_raw_{false};
-#ifdef ARDUINO_ARCH_ESP32
- adc_attenuation_t attenuation_{ADC_0db};
+#ifdef USE_ESP32
+ adc_atten_t attenuation_{ADC_ATTEN_DB_0};
+ adc1_channel_t channel_{};
+ bool autorange_{false};
+ esp_adc_cal_characteristics_t cal_characteristics_[(int) ADC_ATTEN_MAX] = {};
#endif
};
diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py
index 6a274f04af..c812e67a68 100644
--- a/esphome/components/adc/sensor.py
+++ b/esphome/components/adc/sensor.py
@@ -2,47 +2,179 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import sensor, voltage_sampler
-from esphome.const import CONF_ATTENUATION, CONF_ID, CONF_PIN, ICON_FLASH, UNIT_VOLT
+from esphome.const import (
+ CONF_ATTENUATION,
+ CONF_RAW,
+ CONF_ID,
+ CONF_INPUT,
+ CONF_NUMBER,
+ CONF_PIN,
+ DEVICE_CLASS_VOLTAGE,
+ STATE_CLASS_MEASUREMENT,
+ UNIT_VOLT,
+)
+from esphome.core import CORE
+from esphome.components.esp32 import get_esp32_variant
+from esphome.components.esp32.const import (
+ VARIANT_ESP32,
+ VARIANT_ESP32C3,
+ VARIANT_ESP32H2,
+ VARIANT_ESP32S2,
+ VARIANT_ESP32S3,
+)
-AUTO_LOAD = ['voltage_sampler']
+AUTO_LOAD = ["voltage_sampler"]
ATTENUATION_MODES = {
- '0db': cg.global_ns.ADC_0db,
- '2.5db': cg.global_ns.ADC_2_5db,
- '6db': cg.global_ns.ADC_6db,
- '11db': cg.global_ns.ADC_11db,
+ "0db": cg.global_ns.ADC_ATTEN_DB_0,
+ "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5,
+ "6db": cg.global_ns.ADC_ATTEN_DB_6,
+ "11db": cg.global_ns.ADC_ATTEN_DB_11,
+ "auto": "auto",
+}
+
+adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
+
+# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
+# pin to adc1 channel mapping
+ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
+ VARIANT_ESP32: {
+ 36: adc1_channel_t.ADC1_CHANNEL_0,
+ 37: adc1_channel_t.ADC1_CHANNEL_1,
+ 38: adc1_channel_t.ADC1_CHANNEL_2,
+ 39: adc1_channel_t.ADC1_CHANNEL_3,
+ 32: adc1_channel_t.ADC1_CHANNEL_4,
+ 33: adc1_channel_t.ADC1_CHANNEL_5,
+ 34: adc1_channel_t.ADC1_CHANNEL_6,
+ 35: adc1_channel_t.ADC1_CHANNEL_7,
+ },
+ VARIANT_ESP32S2: {
+ 1: adc1_channel_t.ADC1_CHANNEL_0,
+ 2: adc1_channel_t.ADC1_CHANNEL_1,
+ 3: adc1_channel_t.ADC1_CHANNEL_2,
+ 4: adc1_channel_t.ADC1_CHANNEL_3,
+ 5: adc1_channel_t.ADC1_CHANNEL_4,
+ 6: adc1_channel_t.ADC1_CHANNEL_5,
+ 7: adc1_channel_t.ADC1_CHANNEL_6,
+ 8: adc1_channel_t.ADC1_CHANNEL_7,
+ 9: adc1_channel_t.ADC1_CHANNEL_8,
+ 10: adc1_channel_t.ADC1_CHANNEL_9,
+ },
+ VARIANT_ESP32S3: {
+ 1: adc1_channel_t.ADC1_CHANNEL_0,
+ 2: adc1_channel_t.ADC1_CHANNEL_1,
+ 3: adc1_channel_t.ADC1_CHANNEL_2,
+ 4: adc1_channel_t.ADC1_CHANNEL_3,
+ 5: adc1_channel_t.ADC1_CHANNEL_4,
+ 6: adc1_channel_t.ADC1_CHANNEL_5,
+ 7: adc1_channel_t.ADC1_CHANNEL_6,
+ 8: adc1_channel_t.ADC1_CHANNEL_7,
+ 9: adc1_channel_t.ADC1_CHANNEL_8,
+ 10: adc1_channel_t.ADC1_CHANNEL_9,
+ },
+ VARIANT_ESP32C3: {
+ 0: adc1_channel_t.ADC1_CHANNEL_0,
+ 1: adc1_channel_t.ADC1_CHANNEL_1,
+ 2: adc1_channel_t.ADC1_CHANNEL_2,
+ 3: adc1_channel_t.ADC1_CHANNEL_3,
+ 4: adc1_channel_t.ADC1_CHANNEL_4,
+ },
+ VARIANT_ESP32H2: {
+ 0: adc1_channel_t.ADC1_CHANNEL_0,
+ 1: adc1_channel_t.ADC1_CHANNEL_1,
+ 2: adc1_channel_t.ADC1_CHANNEL_2,
+ 3: adc1_channel_t.ADC1_CHANNEL_3,
+ 4: adc1_channel_t.ADC1_CHANNEL_4,
+ },
}
def validate_adc_pin(value):
- vcc = str(value).upper()
- if vcc == 'VCC':
- return cv.only_on_esp8266(vcc)
- return pins.analog_pin(value)
+ if str(value).upper() == "VCC":
+ return cv.only_on_esp8266("VCC")
+
+ if CORE.is_esp32:
+ value = pins.internal_gpio_input_pin_number(value)
+ variant = get_esp32_variant()
+ if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL:
+ raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported")
+
+ if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]:
+ raise cv.Invalid(f"{variant} doesn't support ADC on this pin")
+ return pins.internal_gpio_input_pin_schema(value)
+
+ if CORE.is_esp8266:
+ from esphome.components.esp8266.gpio import CONF_ANALOG
+
+ value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})(
+ value
+ )
+
+ if value != 17: # A0
+ raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.")
+ return pins.gpio_pin_schema(
+ {CONF_ANALOG: True, CONF_INPUT: True}, internal=True
+ )(value)
+
+ raise NotImplementedError
-adc_ns = cg.esphome_ns.namespace('adc')
-ADCSensor = adc_ns.class_('ADCSensor', sensor.Sensor, cg.PollingComponent,
- voltage_sampler.VoltageSampler)
-
-CONFIG_SCHEMA = sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2).extend({
- cv.GenerateID(): cv.declare_id(ADCSensor),
- cv.Required(CONF_PIN): validate_adc_pin,
- cv.SplitDefault(CONF_ATTENUATION, esp32='0db'):
- cv.All(cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)),
-}).extend(cv.polling_component_schema('60s'))
+def validate_config(config):
+ if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto":
+ raise cv.Invalid("Automatic attenuation cannot be used when raw output is set.")
+ return config
-def to_code(config):
+adc_ns = cg.esphome_ns.namespace("adc")
+ADCSensor = adc_ns.class_(
+ "ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
+)
+
+CONFIG_SCHEMA = cv.All(
+ sensor.sensor_schema(
+ unit_of_measurement=UNIT_VOLT,
+ accuracy_decimals=2,
+ device_class=DEVICE_CLASS_VOLTAGE,
+ state_class=STATE_CLASS_MEASUREMENT,
+ )
+ .extend(
+ {
+ cv.GenerateID(): cv.declare_id(ADCSensor),
+ cv.Required(CONF_PIN): validate_adc_pin,
+ cv.Optional(CONF_RAW, default=False): cv.boolean,
+ cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All(
+ cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)
+ ),
+ }
+ )
+ .extend(cv.polling_component_schema("60s")),
+ validate_config,
+)
+
+
+async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
- yield cg.register_component(var, config)
- yield sensor.register_sensor(var, config)
+ await cg.register_component(var, config)
+ await sensor.register_sensor(var, config)
- if config[CONF_PIN] == 'VCC':
- cg.add_define('USE_ADC_SENSOR_VCC')
+ if config[CONF_PIN] == "VCC":
+ cg.add_define("USE_ADC_SENSOR_VCC")
else:
- cg.add(var.set_pin(config[CONF_PIN]))
+ pin = await cg.gpio_pin_expression(config[CONF_PIN])
+ cg.add(var.set_pin(pin))
+
+ if CONF_RAW in config:
+ cg.add(var.set_output_raw(config[CONF_RAW]))
if CONF_ATTENUATION in config:
- cg.add(var.set_attenuation(config[CONF_ATTENUATION]))
+ if config[CONF_ATTENUATION] == "auto":
+ cg.add(var.set_autorange(cg.global_ns.true))
+ else:
+ cg.add(var.set_attenuation(config[CONF_ATTENUATION]))
+
+ if CORE.is_esp32:
+ variant = get_esp32_variant()
+ pin_num = config[CONF_PIN][CONF_NUMBER]
+ chan = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num]
+ cg.add(var.set_channel(chan))
diff --git a/esphome/api/__init__.py b/esphome/components/addressable_light/__init__.py
similarity index 100%
rename from esphome/api/__init__.py
rename to esphome/components/addressable_light/__init__.py
diff --git a/esphome/components/addressable_light/addressable_light_display.cpp b/esphome/components/addressable_light/addressable_light_display.cpp
new file mode 100644
index 0000000000..16fab15b17
--- /dev/null
+++ b/esphome/components/addressable_light/addressable_light_display.cpp
@@ -0,0 +1,67 @@
+#include "addressable_light_display.h"
+#include "esphome/core/log.h"
+
+namespace esphome {
+namespace addressable_light {
+
+static const char *const TAG = "addressable_light.display";
+
+int AddressableLightDisplay::get_width_internal() { return this->width_; }
+int AddressableLightDisplay::get_height_internal() { return this->height_; }
+
+void AddressableLightDisplay::setup() {
+ this->addressable_light_buffer_.resize(this->width_ * this->height_, {0, 0, 0, 0});
+}
+
+void AddressableLightDisplay::update() {
+ if (!this->enabled_)
+ return;
+
+ this->do_update_();
+ this->display();
+}
+
+void AddressableLightDisplay::display() {
+ bool dirty = false;
+ uint8_t old_r, old_g, old_b, old_w;
+ Color *c;
+
+ for (uint32_t offset = 0; offset < this->addressable_light_buffer_.size(); offset++) {
+ c = &(this->addressable_light_buffer_[offset]);
+
+ light::ESPColorView pixel = (*this->light_)[offset];
+
+ // Track the original values for the pixel view. If it has changed updating, then
+ // we trigger a redraw. Avoiding redraws == avoiding flicker!
+ old_r = pixel.get_red();
+ old_g = pixel.get_green();
+ old_b = pixel.get_blue();
+ old_w = pixel.get_white();
+
+ pixel.set_rgbw(c->r, c->g, c->b, c->w);
+
+ // If the actual value of the pixel changed, then schedule a redraw.
+ if (pixel.get_red() != old_r || pixel.get_green() != old_g || pixel.get_blue() != old_b ||
+ pixel.get_white() != old_w) {
+ dirty = true;
+ }
+ }
+
+ if (dirty) {
+ this->light_->schedule_show();
+ }
+}
+
+void HOT AddressableLightDisplay::draw_absolute_pixel_internal(int x, int y, Color color) {
+ if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0)
+ return;
+
+ if (this->pixel_mapper_f_.has_value()) {
+ // Params are passed by reference, so they may be modified in call.
+ this->addressable_light_buffer_[(*this->pixel_mapper_f_)(x, y)] = color;
+ } else {
+ this->addressable_light_buffer_[y * this->get_width_internal() + x] = color;
+ }
+}
+} // namespace addressable_light
+} // namespace esphome
diff --git a/esphome/components/addressable_light/addressable_light_display.h b/esphome/components/addressable_light/addressable_light_display.h
new file mode 100644
index 0000000000..163faf27b0
--- /dev/null
+++ b/esphome/components/addressable_light/addressable_light_display.h
@@ -0,0 +1,59 @@
+#pragma once
+
+#include "esphome/core/component.h"
+#include "esphome/core/color.h"
+#include "esphome/components/display/display_buffer.h"
+#include "esphome/components/light/addressable_light.h"
+
+namespace esphome {
+namespace addressable_light {
+
+class AddressableLightDisplay : public display::DisplayBuffer, public PollingComponent {
+ public:
+ light::AddressableLight *get_light() const { return this->light_; }
+
+ void set_width(int32_t width) { width_ = width; }
+ void set_height(int32_t height) { height_ = height; }
+ void set_light(light::LightState *state) {
+ light_state_ = state;
+ light_ = static_cast(state->get_output());
+ }
+ void set_enabled(bool enabled) {
+ if (light_state_) {
+ if (enabled_ && !enabled) { // enabled -> disabled
+ // - Tell the parent light to refresh, effectively wiping the display. Also
+ // restores the previous effect (if any).
+ light_state_->make_call().set_effect(this->last_effect_).perform();
+
+ } else if (!enabled_ && enabled) { // disabled -> enabled
+ // - Save the current effect.
+ this->last_effect_ = light_state_->get_effect_name();
+ // - Disable any current effect.
+ light_state_->make_call().set_effect(0).perform();
+ }
+ }
+ enabled_ = enabled;
+ }
+ bool get_enabled() { return enabled_; }
+
+ void set_pixel_mapper(std::function &&pixel_mapper_f) { this->pixel_mapper_f_ = pixel_mapper_f; }
+ void setup() override;
+ void display();
+
+ protected:
+ int get_width_internal() override;
+ int get_height_internal() override;
+ void draw_absolute_pixel_internal(int x, int y, Color color) override;
+ void update() override;
+
+ light::LightState *light_state_;
+ light::AddressableLight *light_;
+ bool enabled_{true};
+ int32_t width_;
+ int32_t height_;
+ std::vector addressable_light_buffer_;
+ optional last_effect_;
+ optional> pixel_mapper_f_;
+};
+} // namespace addressable_light
+} // namespace esphome
diff --git a/esphome/components/addressable_light/display.py b/esphome/components/addressable_light/display.py
new file mode 100644
index 0000000000..0684bf8dfc
--- /dev/null
+++ b/esphome/components/addressable_light/display.py
@@ -0,0 +1,63 @@
+import esphome.codegen as cg
+import esphome.config_validation as cv
+from esphome.components import display, light
+from esphome.const import (
+ CONF_ID,
+ CONF_LAMBDA,
+ CONF_PAGES,
+ CONF_ADDRESSABLE_LIGHT_ID,
+ CONF_HEIGHT,
+ CONF_WIDTH,
+ CONF_UPDATE_INTERVAL,
+ CONF_PIXEL_MAPPER,
+)
+
+CODEOWNERS = ["@justfalter"]
+
+addressable_light_ns = cg.esphome_ns.namespace("addressable_light")
+AddressableLightDisplay = addressable_light_ns.class_(
+ "AddressableLightDisplay", display.DisplayBuffer, cg.PollingComponent
+)
+
+CONFIG_SCHEMA = cv.All(
+ display.FULL_DISPLAY_SCHEMA.extend(
+ {
+ cv.GenerateID(): cv.declare_id(AddressableLightDisplay),
+ cv.Required(CONF_ADDRESSABLE_LIGHT_ID): cv.use_id(
+ light.AddressableLightState
+ ),
+ cv.Required(CONF_WIDTH): cv.positive_int,
+ cv.Required(CONF_HEIGHT): cv.positive_int,
+ cv.Optional(
+ CONF_UPDATE_INTERVAL, default="16ms"
+ ): cv.positive_time_period_milliseconds,
+ cv.Optional(CONF_PIXEL_MAPPER): cv.returning_lambda,
+ }
+ ),
+ cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA),
+)
+
+
+async def to_code(config):
+ var = cg.new_Pvariable(config[CONF_ID])
+ wrapped_light = await cg.get_variable(config[CONF_ADDRESSABLE_LIGHT_ID])
+ cg.add(var.set_width(config[CONF_WIDTH]))
+ cg.add(var.set_height(config[CONF_HEIGHT]))
+ cg.add(var.set_light(wrapped_light))
+
+ await cg.register_component(var, config)
+ await display.register_display(var, config)
+
+ if CONF_PIXEL_MAPPER in config:
+ pixel_mapper_template_ = await cg.process_lambda(
+ config[CONF_PIXEL_MAPPER],
+ [(int, "x"), (int, "y")],
+ return_type=cg.int_,
+ )
+ cg.add(var.set_pixel_mapper(pixel_mapper_template_))
+
+ if CONF_LAMBDA in config:
+ lambda_ = await cg.process_lambda(
+ config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void
+ )
+ cg.add(var.set_writer(lambda_))
diff --git a/esphome/components/ade7953/ade7953.cpp b/esphome/components/ade7953/ade7953.cpp
index 9316d9cad0..2c61fc6a44 100644
--- a/esphome/components/ade7953/ade7953.cpp
+++ b/esphome/components/ade7953/ade7953.cpp
@@ -4,10 +4,11 @@
namespace esphome {
namespace ade7953 {
-static const char *TAG = "ade7953";
+static const char *const TAG = "ade7953";
void ADE7953::dump_config() {
ESP_LOGCONFIG(TAG, "ADE7953:");
+ LOG_PIN(" IRQ Pin: ", irq_pin_);
LOG_I2C_DEVICE(this);
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Voltage Sensor", this->voltage_sensor_);
@@ -17,27 +18,28 @@ void ADE7953::dump_config() {
LOG_SENSOR(" ", "Active Power B Sensor", this->active_power_b_sensor_);
}
-#define ADE_PUBLISH_(name, factor) \
- if (name) { \
- float value = *name / factor; \
+#define ADE_PUBLISH_(name, val, factor) \
+ if (err == i2c::ERROR_OK && this->name##_sensor_) { \
+ float value = (val) / (factor); \
this->name##_sensor_->publish_state(value); \
}
-#define ADE_PUBLISH(name, factor) ADE_PUBLISH_(name, factor)
+#define ADE_PUBLISH(name, val, factor) ADE_PUBLISH_(name, val, factor)
void ADE7953::update() {
if (!this->is_setup_)
return;
- auto active_power_a = this->ade_read_(0x0312);
- ADE_PUBLISH(active_power_a, 154.0f);
- auto active_power_b = this->ade_read_(0x0313);
- ADE_PUBLISH(active_power_b, 154.0f);
- auto current_a = this->ade_read_(0x031A);
- ADE_PUBLISH(current_a, 100000.0f);
- auto current_b = this->ade_read_(0x031B);
- ADE_PUBLISH(current_b, 100000.0f);
- auto voltage = this->ade_read_(0x031C);
- ADE_PUBLISH(voltage, 26000.0f);
+ uint32_t val;
+ i2c::ErrorCode err = ade_read_32_(0x0312, &val);
+ ADE_PUBLISH(active_power_a, (int32_t) val, 154.0f);
+ err = ade_read_32_(0x0313, &val);
+ ADE_PUBLISH(active_power_b, (int32_t) val, 154.0f);
+ err = ade_read_32_(0x031A, &val);
+ ADE_PUBLISH(current_a, (uint32_t) val, 100000.0f);
+ err = ade_read_32_(0x031B, &val);
+ ADE_PUBLISH(current_b, (uint32_t) val, 100000.0f);
+ err = ade_read_32_(0x031C, &val);
+ ADE_PUBLISH(voltage, (uint32_t) val, 26000.0f);
// auto apparent_power_a = this->ade_read_(0x0310);
// auto apparent_power_b = this->ade_read_(0x0311);
diff --git a/esphome/components/ade7953/ade7953.h b/esphome/components/ade7953/ade7953.h
index 7591bc1684..bb160cd8eb 100644
--- a/esphome/components/ade7953/ade7953.h
+++ b/esphome/components/ade7953/ade7953.h
@@ -1,6 +1,7 @@
#pragma once
#include "esphome/core/component.h"
+#include "esphome/core/hal.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
@@ -9,6 +10,7 @@ namespace ade7953 {
class ADE7953 : public i2c::I2CDevice, public PollingComponent {
public:
+ void set_irq_pin(InternalGPIOPin *irq_pin) { irq_pin_ = irq_pin; }
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
void set_current_a_sensor(sensor::Sensor *current_a_sensor) { current_a_sensor_ = current_a_sensor; }
void set_current_b_sensor(sensor::Sensor *current_b_sensor) { current_b_sensor_ = current_b_sensor; }
@@ -20,10 +22,13 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent {
}
void setup() override {
+ if (this->irq_pin_ != nullptr) {
+ this->irq_pin_->setup();
+ }
this->set_timeout(100, [this]() {
- this->ade_write_(0x0010, 0x04);
- this->ade_write_(0x00FE, 0xAD);
- this->ade_write_(0x0120, 0x0030);
+ this->ade_write_8_(0x0010, 0x04);
+ this->ade_write_8_(0x00FE, 0xAD);
+ this->ade_write_16_(0x0120, 0x0030);
this->is_setup_ = true;
});
}
@@ -33,28 +38,51 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent {
void update() override;
protected:
- template bool ade_write_(uint16_t reg, T value) {
+ i2c::ErrorCode ade_write_8_(uint16_t reg, uint8_t value) {
std::vector data;
data.push_back(reg >> 8);
data.push_back(reg >> 0);
- for (int i = sizeof(T) - 1; i >= 0; i--)
- data.push_back(value >> (i * 8));
- return this->write_bytes_raw(data);
+ data.push_back(value);
+ return write(data.data(), data.size());
}
- template optional ade_read_(uint16_t reg) {
- uint8_t hi = reg >> 8;
- uint8_t lo = reg >> 0;
- if (!this->write_bytes_raw({hi, lo}))
- return {};
- auto ret = this->read_bytes_raw();
- if (!ret.has_value())
- return {};
- T result = 0;
- for (int i = 0, j = sizeof(T) - 1; i < sizeof(T); i++, j--)
- result |= T((*ret)[i]) << (j * 8);
- return result;
+ i2c::ErrorCode ade_write_16_(uint16_t reg, uint16_t value) {
+ std::vector data;
+ data.push_back(reg >> 8);
+ data.push_back(reg >> 0);
+ data.push_back(value >> 8);
+ data.push_back(value >> 0);
+ return write(data.data(), data.size());
+ }
+ i2c::ErrorCode ade_write_32_(uint16_t reg, uint32_t value) {
+ std::vector data;
+ data.push_back(reg >> 8);
+ data.push_back(reg >> 0);
+ data.push_back(value >> 24);
+ data.push_back(value >> 16);
+ data.push_back(value >> 8);
+ data.push_back(value >> 0);
+ return write(data.data(), data.size());
+ }
+ i2c::ErrorCode ade_read_32_(uint16_t reg, uint32_t *value) {
+ uint8_t reg_data[2];
+ reg_data[0] = reg >> 8;
+ reg_data[1] = reg >> 0;
+ i2c::ErrorCode err = write(reg_data, 2);
+ if (err != i2c::ERROR_OK)
+ return err;
+ uint8_t recv[4];
+ err = read(recv, 4);
+ if (err != i2c::ERROR_OK)
+ return err;
+ *value = 0;
+ *value |= ((uint32_t) recv[0]) << 24;
+ *value |= ((uint32_t) recv[1]) << 16;
+ *value |= ((uint32_t) recv[2]) << 8;
+ *value |= ((uint32_t) recv[3]);
+ return i2c::ERROR_OK;
}
+ InternalGPIOPin *irq_pin_ = nullptr;
bool is_setup_{false};
sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *current_a_sensor_{nullptr};
diff --git a/esphome/components/ade7953/sensor.py b/esphome/components/ade7953/sensor.py
index b048b1ed71..d02f466091 100644
--- a/esphome/components/ade7953/sensor.py
+++ b/esphome/components/ade7953/sensor.py
@@ -1,39 +1,90 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, i2c
-from esphome.const import CONF_ID, CONF_VOLTAGE, \
- UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT
+from esphome import pins
+from esphome.const import (
+ CONF_ID,
+ CONF_VOLTAGE,
+ DEVICE_CLASS_CURRENT,
+ DEVICE_CLASS_POWER,
+ DEVICE_CLASS_VOLTAGE,
+ STATE_CLASS_MEASUREMENT,
+ UNIT_VOLT,
+ UNIT_AMPERE,
+ UNIT_WATT,
+)
-DEPENDENCIES = ['i2c']
+DEPENDENCIES = ["i2c"]
-ace7953_ns = cg.esphome_ns.namespace('ade7953')
-ADE7953 = ace7953_ns.class_('ADE7953', cg.PollingComponent, i2c.I2CDevice)
+ade7953_ns = cg.esphome_ns.namespace("ade7953")
+ADE7953 = ade7953_ns.class_("ADE7953", cg.PollingComponent, i2c.I2CDevice)
-CONF_CURRENT_A = 'current_a'
-CONF_CURRENT_B = 'current_b'
-CONF_ACTIVE_POWER_A = 'active_power_a'
-CONF_ACTIVE_POWER_B = 'active_power_b'
+CONF_IRQ_PIN = "irq_pin"
+CONF_CURRENT_A = "current_a"
+CONF_CURRENT_B = "current_b"
+CONF_ACTIVE_POWER_A = "active_power_a"
+CONF_ACTIVE_POWER_B = "active_power_b"
-CONFIG_SCHEMA = cv.Schema({
- cv.GenerateID(): cv.declare_id(ADE7953),
-
- cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1),
- cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
- cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
- cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1),
- cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1),
-}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x38))
+CONFIG_SCHEMA = (
+ cv.Schema(
+ {
+ cv.GenerateID(): cv.declare_id(ADE7953),
+ cv.Optional(CONF_IRQ_PIN): pins.internal_gpio_input_pin_schema,
+ cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
+ unit_of_measurement=UNIT_VOLT,
+ accuracy_decimals=1,
+ device_class=DEVICE_CLASS_VOLTAGE,
+ state_class=STATE_CLASS_MEASUREMENT,
+ ),
+ cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(
+ unit_of_measurement=UNIT_AMPERE,
+ accuracy_decimals=2,
+ device_class=DEVICE_CLASS_CURRENT,
+ state_class=STATE_CLASS_MEASUREMENT,
+ ),
+ cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(
+ unit_of_measurement=UNIT_AMPERE,
+ accuracy_decimals=2,
+ device_class=DEVICE_CLASS_CURRENT,
+ state_class=STATE_CLASS_MEASUREMENT,
+ ),
+ cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(
+ unit_of_measurement=UNIT_WATT,
+ accuracy_decimals=1,
+ device_class=DEVICE_CLASS_POWER,
+ state_class=STATE_CLASS_MEASUREMENT,
+ ),
+ cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema(
+ unit_of_measurement=UNIT_WATT,
+ accuracy_decimals=1,
+ device_class=DEVICE_CLASS_POWER,
+ state_class=STATE_CLASS_MEASUREMENT,
+ ),
+ }
+ )
+ .extend(cv.polling_component_schema("60s"))
+ .extend(i2c.i2c_device_schema(0x38))
+)
-def to_code(config):
+async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
- yield cg.register_component(var, config)
- yield i2c.register_i2c_device(var, config)
+ await cg.register_component(var, config)
+ await i2c.register_i2c_device(var, config)
- for key in [CONF_VOLTAGE, CONF_CURRENT_A, CONF_CURRENT_B, CONF_ACTIVE_POWER_A,
- CONF_ACTIVE_POWER_B]:
+ if CONF_IRQ_PIN in config:
+ irq_pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN])
+ cg.add(var.set_irq_pin(irq_pin))
+
+ for key in [
+ CONF_VOLTAGE,
+ CONF_CURRENT_A,
+ CONF_CURRENT_B,
+ CONF_ACTIVE_POWER_A,
+ CONF_ACTIVE_POWER_B,
+ ]:
if key not in config:
continue
conf = config[key]
- sens = yield sensor.new_sensor(conf)
- cg.add(getattr(var, f'set_{key}_sensor')(sens))
+ sens = await sensor.new_sensor(conf)
+ cg.add(getattr(var, f"set_{key}_sensor")(sens))
diff --git a/esphome/components/ads1115/__init__.py b/esphome/components/ads1115/__init__.py
index a41a521ba7..e8861a2f67 100644
--- a/esphome/components/ads1115/__init__.py
+++ b/esphome/components/ads1115/__init__.py
@@ -3,23 +3,29 @@ import esphome.config_validation as cv
from esphome.components import i2c
from esphome.const import CONF_ID
-DEPENDENCIES = ['i2c']
-AUTO_LOAD = ['sensor', 'voltage_sampler']
+DEPENDENCIES = ["i2c"]
+AUTO_LOAD = ["sensor", "voltage_sampler"]
MULTI_CONF = True
-ads1115_ns = cg.esphome_ns.namespace('ads1115')
-ADS1115Component = ads1115_ns.class_('ADS1115Component', cg.Component, i2c.I2CDevice)
+ads1115_ns = cg.esphome_ns.namespace("ads1115")
+ADS1115Component = ads1115_ns.class_("ADS1115Component", cg.Component, i2c.I2CDevice)
-CONF_CONTINUOUS_MODE = 'continuous_mode'
-CONFIG_SCHEMA = cv.Schema({
- cv.GenerateID(): cv.declare_id(ADS1115Component),
- cv.Optional(CONF_CONTINUOUS_MODE, default=False): cv.boolean,
-}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(None))
+CONF_CONTINUOUS_MODE = "continuous_mode"
+CONFIG_SCHEMA = (
+ cv.Schema(
+ {
+ cv.GenerateID(): cv.declare_id(ADS1115Component),
+ cv.Optional(CONF_CONTINUOUS_MODE, default=False): cv.boolean,
+ }
+ )
+ .extend(cv.COMPONENT_SCHEMA)
+ .extend(i2c.i2c_device_schema(None))
+)
-def to_code(config):
+async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
- yield cg.register_component(var, config)
- yield i2c.register_i2c_device(var, config)
+ await cg.register_component(var, config)
+ await i2c.register_i2c_device(var, config)
cg.add(var.set_continuous_mode(config[CONF_CONTINUOUS_MODE]))
diff --git a/esphome/components/ads1115/ads1115.cpp b/esphome/components/ads1115/ads1115.cpp
index 0899571a47..beb379db93 100644
--- a/esphome/components/ads1115/ads1115.cpp
+++ b/esphome/components/ads1115/ads1115.cpp
@@ -1,10 +1,11 @@
#include "ads1115.h"
#include "esphome/core/log.h"
+#include "esphome/core/hal.h"
namespace esphome {
namespace ads1115 {
-static const char *TAG = "ads1115";
+static const char *const TAG = "ads1115";
static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00;
static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
@@ -64,11 +65,6 @@ void ADS1115Component::setup() {
return;
}
this->prev_config_ = config;
-
- 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...");
@@ -107,17 +103,22 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
}
this->prev_config_ = config;
- // about 1.6 ms with 860 samples per second
+ // about 1.2 ms with 860 samples per second
delay(2);
- uint32_t start = millis();
- while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) {
- if (millis() - start > 100) {
- ESP_LOGW(TAG, "Reading ADS1115 timed out");
- this->status_set_warning();
- return NAN;
+ // in continuous mode, conversion will always be running, rely on the delay
+ // to ensure conversion is taking place with the correct settings
+ // can we use the rdy pin to trigger when a conversion is done?
+ if (!this->continuous_mode_) {
+ uint32_t start = millis();
+ while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) {
+ if (millis() - start > 100) {
+ ESP_LOGW(TAG, "Reading ADS1115 timed out");
+ this->status_set_warning();
+ return NAN;
+ }
+ yield();
}
- yield();
}
}
@@ -159,7 +160,7 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
float ADS1115Sensor::sample() { return this->parent_->request_measurement(this); }
void ADS1115Sensor::update() {
float v = this->parent_->request_measurement(this);
- if (!isnan(v)) {
+ if (!std::isnan(v)) {
ESP_LOGD(TAG, "'%s': Got Voltage=%fV", this->get_name().c_str(), v);
this->publish_state(v);
}
diff --git a/esphome/components/ads1115/sensor.py b/esphome/components/ads1115/sensor.py
index 55619b22e9..da33a39041 100644
--- a/esphome/components/ads1115/sensor.py
+++ b/esphome/components/ads1115/sensor.py
@@ -1,60 +1,79 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, voltage_sampler
-from esphome.const import CONF_GAIN, CONF_MULTIPLEXER, ICON_FLASH, UNIT_VOLT, CONF_ID
+from esphome.const import (
+ CONF_GAIN,
+ CONF_MULTIPLEXER,
+ DEVICE_CLASS_VOLTAGE,
+ STATE_CLASS_MEASUREMENT,
+ UNIT_VOLT,
+ CONF_ID,
+)
from . import ads1115_ns, ADS1115Component
-DEPENDENCIES = ['ads1115']
+DEPENDENCIES = ["ads1115"]
-ADS1115Multiplexer = ads1115_ns.enum('ADS1115Multiplexer')
+ADS1115Multiplexer = ads1115_ns.enum("ADS1115Multiplexer")
MUX = {
- 'A0_A1': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1,
- 'A0_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3,
- 'A1_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_N3,
- 'A2_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_N3,
- 'A0_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_NG,
- 'A1_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_NG,
- 'A2_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_NG,
- 'A3_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG,
+ "A0_A1": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1,
+ "A0_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3,
+ "A1_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_N3,
+ "A2_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_N3,
+ "A0_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_NG,
+ "A1_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_NG,
+ "A2_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_NG,
+ "A3_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG,
}
-ADS1115Gain = ads1115_ns.enum('ADS1115Gain')
+ADS1115Gain = ads1115_ns.enum("ADS1115Gain")
GAIN = {
- '6.144': ADS1115Gain.ADS1115_GAIN_6P144,
- '4.096': ADS1115Gain.ADS1115_GAIN_4P096,
- '2.048': ADS1115Gain.ADS1115_GAIN_2P048,
- '1.024': ADS1115Gain.ADS1115_GAIN_1P024,
- '0.512': ADS1115Gain.ADS1115_GAIN_0P512,
- '0.256': ADS1115Gain.ADS1115_GAIN_0P256,
+ "6.144": ADS1115Gain.ADS1115_GAIN_6P144,
+ "4.096": ADS1115Gain.ADS1115_GAIN_4P096,
+ "2.048": ADS1115Gain.ADS1115_GAIN_2P048,
+ "1.024": ADS1115Gain.ADS1115_GAIN_1P024,
+ "0.512": ADS1115Gain.ADS1115_GAIN_0P512,
+ "0.256": ADS1115Gain.ADS1115_GAIN_0P256,
}
def validate_gain(value):
if isinstance(value, float):
- value = f'{value:0.03f}'
+ value = f"{value:0.03f}"
elif not isinstance(value, str):
raise cv.Invalid(f'invalid gain "{value}"')
return cv.enum(GAIN)(value)
-ADS1115Sensor = ads1115_ns.class_('ADS1115Sensor', sensor.Sensor, cg.PollingComponent,
- voltage_sampler.VoltageSampler)
+ADS1115Sensor = ads1115_ns.class_(
+ "ADS1115Sensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
+)
-CONF_ADS1115_ID = 'ads1115_id'
-CONFIG_SCHEMA = sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 3).extend({
- cv.GenerateID(): cv.declare_id(ADS1115Sensor),
- cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component),
- cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space='_'),
- cv.Required(CONF_GAIN): validate_gain,
-}).extend(cv.polling_component_schema('60s'))
+CONF_ADS1115_ID = "ads1115_id"
+CONFIG_SCHEMA = (
+ sensor.sensor_schema(
+ unit_of_measurement=UNIT_VOLT,
+ accuracy_decimals=3,
+ device_class=DEVICE_CLASS_VOLTAGE,
+ state_class=STATE_CLASS_MEASUREMENT,
+ )
+ .extend(
+ {
+ cv.GenerateID(): cv.declare_id(ADS1115Sensor),
+ cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component),
+ cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space="_"),
+ cv.Required(CONF_GAIN): validate_gain,
+ }
+ )
+ .extend(cv.polling_component_schema("60s"))
+)
-def to_code(config):
- paren = yield cg.get_variable(config[CONF_ADS1115_ID])
+async def to_code(config):
+ paren = await cg.get_variable(config[CONF_ADS1115_ID])
var = cg.new_Pvariable(config[CONF_ID], paren)
- yield sensor.register_sensor(var, config)
- yield cg.register_component(var, config)
+ await sensor.register_sensor(var, config)
+ await cg.register_component(var, config)
cg.add(var.set_multiplexer(config[CONF_MULTIPLEXER]))
cg.add(var.set_gain(config[CONF_GAIN]))
diff --git a/esphome/components/aht10/aht10.cpp b/esphome/components/aht10/aht10.cpp
index 6951254e0d..3c690c39b5 100644
--- a/esphome/components/aht10/aht10.cpp
+++ b/esphome/components/aht10/aht10.cpp
@@ -10,20 +10,21 @@
//
// According to the datasheet, the component is supposed to respond in more than 75ms. In fact, it can answer almost
// immediately for temperature. But for humidity, it takes >90ms to get a valid data. From experience, we have best
-// results making successive requests; the current implementation make 3 attemps with a delay of 30ms each time.
+// results making successive requests; the current implementation makes 3 attempts with a delay of 30ms each time.
#include "aht10.h"
#include "esphome/core/log.h"
+#include "esphome/core/hal.h"
namespace esphome {
namespace aht10 {
-static const char *TAG = "aht10";
+static const char *const TAG = "aht10";
static const uint8_t AHT10_CALIBRATE_CMD[] = {0xE1};
static const uint8_t AHT10_MEASURE_CMD[] = {0xAC, 0x33, 0x00};
static const uint8_t AHT10_DEFAULT_DELAY = 5; // ms, for calibration and temperature measurement
static const uint8_t AHT10_HUMIDITY_DELAY = 30; // ms
-static const uint8_t AHT10_ATTEMPS = 3; // safety margin, normally 3 attemps are enough: 3*30=90ms
+static const uint8_t AHT10_ATTEMPTS = 3; // safety margin, normally 3 attempts are enough: 3*30=90ms
void AHT10Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up AHT10...");
@@ -33,8 +34,19 @@ void AHT10Component::setup() {
this->mark_failed();
return;
}
- uint8_t data;
- if (!this->read_byte(0, &data, AHT10_DEFAULT_DELAY)) {
+ uint8_t data = 0;
+ if (this->write(&data, 1) != i2c::ERROR_OK) {
+ ESP_LOGD(TAG, "Communication with AHT10 failed!");
+ this->mark_failed();
+ return;
+ }
+ delay(AHT10_DEFAULT_DELAY);
+ if (this->read(&data, 1) != i2c::ERROR_OK) {
+ ESP_LOGD(TAG, "Communication with AHT10 failed!");
+ this->mark_failed();
+ return;
+ }
+ if (this->read(&data, 1) != i2c::ERROR_OK) {
ESP_LOGD(TAG, "Communication with AHT10 failed!");
this->mark_failed();
return;
@@ -55,14 +67,19 @@ void AHT10Component::update() {
return;
}
uint8_t data[6];
- uint8_t delay = AHT10_DEFAULT_DELAY;
+ uint8_t delay_ms = AHT10_DEFAULT_DELAY;
if (this->humidity_sensor_ != nullptr)
- delay = AHT10_HUMIDITY_DELAY;
- for (int i = 0; i < AHT10_ATTEMPS; ++i) {
- ESP_LOGVV(TAG, "Attemps %u at %6ld", i, millis());
- if (!this->read_bytes(0, data, 6, delay)) {
+ delay_ms = AHT10_HUMIDITY_DELAY;
+ bool success = false;
+ for (int i = 0; i < AHT10_ATTEMPTS; ++i) {
+ ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis());
+ delay(delay_ms);
+ if (this->read(data, 6) != i2c::ERROR_OK) {
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
- } else if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy
+ continue;
+ }
+
+ if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy
ESP_LOGD(TAG, "AHT10 is busy, waiting...");
} else if (data[1] == 0x0 && data[2] == 0x0 && (data[3] >> 4) == 0x0) {
// Unrealistic humidity (0x0)
@@ -79,11 +96,12 @@ void AHT10Component::update() {
}
} else {
// data is valid, we can break the loop
- ESP_LOGVV(TAG, "Answer at %6ld", millis());
+ ESP_LOGVV(TAG, "Answer at %6u", millis());
+ success = true;
break;
}
}
- if ((data[0] & 0x80) == 0x80) {
+ if (!success || (data[0] & 0x80) == 0x80) {
ESP_LOGE(TAG, "Measurements reading timed-out!");
this->status_set_warning();
return;
@@ -92,19 +110,19 @@ void AHT10Component::update() {
uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4;
- float temperature = ((200.0 * (float) raw_temperature) / 1048576.0) - 50.0;
+ float temperature = ((200.0f * (float) raw_temperature) / 1048576.0f) - 50.0f;
float humidity;
if (raw_humidity == 0) { // unrealistic value
humidity = NAN;
} else {
- humidity = (float) raw_humidity * 100.0 / 1048576.0;
+ humidity = (float) raw_humidity * 100.0f / 1048576.0f;
}
if (this->temperature_sensor_ != nullptr) {
this->temperature_sensor_->publish_state(temperature);
}
if (this->humidity_sensor_ != nullptr) {
- if (isnan(humidity))
+ if (std::isnan(humidity))
ESP_LOGW(TAG, "Invalid humidity! Sensor reported 0%% Hum");
this->humidity_sensor_->publish_state(humidity);
}
diff --git a/esphome/components/aht10/sensor.py b/esphome/components/aht10/sensor.py
index 71b0adce79..654d645966 100644
--- a/esphome/components/aht10/sensor.py
+++ b/esphome/components/aht10/sensor.py
@@ -1,30 +1,54 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
-from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, \
- UNIT_CELSIUS, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_PERCENT
+from esphome.const import (
+ CONF_HUMIDITY,
+ CONF_ID,
+ CONF_TEMPERATURE,
+ DEVICE_CLASS_HUMIDITY,
+ DEVICE_CLASS_TEMPERATURE,
+ STATE_CLASS_MEASUREMENT,
+ UNIT_CELSIUS,
+ UNIT_PERCENT,
+)
-DEPENDENCIES = ['i2c']
+DEPENDENCIES = ["i2c"]
-aht10_ns = cg.esphome_ns.namespace('aht10')
-AHT10Component = aht10_ns.class_('AHT10Component', cg.PollingComponent, i2c.I2CDevice)
+aht10_ns = cg.esphome_ns.namespace("aht10")
+AHT10Component = aht10_ns.class_("AHT10Component", cg.PollingComponent, i2c.I2CDevice)
-CONFIG_SCHEMA = cv.Schema({
- cv.GenerateID(): cv.declare_id(AHT10Component),
- cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 2),
- cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 2),
-}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x38))
+CONFIG_SCHEMA = (
+ cv.Schema(
+ {
+ cv.GenerateID(): cv.declare_id(AHT10Component),
+ cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
+ unit_of_measurement=UNIT_CELSIUS,
+ accuracy_decimals=2,
+ device_class=DEVICE_CLASS_TEMPERATURE,
+ state_class=STATE_CLASS_MEASUREMENT,
+ ),
+ cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
+ unit_of_measurement=UNIT_PERCENT,
+ accuracy_decimals=2,
+ device_class=DEVICE_CLASS_HUMIDITY,
+ state_class=STATE_CLASS_MEASUREMENT,
+ ),
+ }
+ )
+ .extend(cv.polling_component_schema("60s"))
+ .extend(i2c.i2c_device_schema(0x38))
+)
-def to_code(config):
+async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
- yield cg.register_component(var, config)
- yield i2c.register_i2c_device(var, config)
+ await cg.register_component(var, config)
+ await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config:
- sens = yield sensor.new_sensor(config[CONF_TEMPERATURE])
+ sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
cg.add(var.set_temperature_sensor(sens))
if CONF_HUMIDITY in config:
- sens = yield sensor.new_sensor(config[CONF_HUMIDITY])
+ sens = await sensor.new_sensor(config[CONF_HUMIDITY])
cg.add(var.set_humidity_sensor(sens))
diff --git a/esphome/components/airthings_ble/__init__.py b/esphome/components/airthings_ble/__init__.py
new file mode 100644
index 0000000000..ca94069703
--- /dev/null
+++ b/esphome/components/airthings_ble/__init__.py
@@ -0,0 +1,23 @@
+import esphome.codegen as cg
+import esphome.config_validation as cv
+from esphome.components import esp32_ble_tracker
+from esphome.const import CONF_ID
+
+DEPENDENCIES = ["esp32_ble_tracker"]
+CODEOWNERS = ["@jeromelaban"]
+
+airthings_ble_ns = cg.esphome_ns.namespace("airthings_ble")
+AirthingsListener = airthings_ble_ns.class_(
+ "AirthingsListener", esp32_ble_tracker.ESPBTDeviceListener
+)
+
+CONFIG_SCHEMA = cv.Schema(
+ {
+ cv.GenerateID(): cv.declare_id(AirthingsListener),
+ }
+).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
+
+
+def to_code(config):
+ var = cg.new_Pvariable(config[CONF_ID])
+ yield esp32_ble_tracker.register_ble_device(var, config)
diff --git a/esphome/components/airthings_ble/airthings_listener.cpp b/esphome/components/airthings_ble/airthings_listener.cpp
new file mode 100644
index 0000000000..951961cb1b
--- /dev/null
+++ b/esphome/components/airthings_ble/airthings_listener.cpp
@@ -0,0 +1,33 @@
+#include "airthings_listener.h"
+#include "esphome/core/log.h"
+
+#ifdef USE_ESP32
+
+namespace esphome {
+namespace airthings_ble {
+
+static const char *const TAG = "airthings_ble";
+
+bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
+ for (auto &it : device.get_manufacturer_datas()) {
+ if (it.uuid == esp32_ble_tracker::ESPBTUUID::from_uint32(0x0334)) {
+ if (it.data.size() < 4)
+ continue;
+
+ uint32_t sn = it.data[0];
+ sn |= ((uint32_t) it.data[1] << 8);
+ sn |= ((uint32_t) it.data[2] << 16);
+ sn |= ((uint32_t) it.data[3] << 24);
+
+ ESP_LOGD(TAG, "Found AirThings device Serial:%u (MAC: %s)", sn, device.address_str().c_str());
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace airthings_ble
+} // namespace esphome
+
+#endif
diff --git a/esphome/components/airthings_ble/airthings_listener.h b/esphome/components/airthings_ble/airthings_listener.h
new file mode 100644
index 0000000000..52f69ea970
--- /dev/null
+++ b/esphome/components/airthings_ble/airthings_listener.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#ifdef USE_ESP32
+
+#include "esphome/core/component.h"
+#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
+
+namespace esphome {
+namespace airthings_ble {
+
+class AirthingsListener : public esp32_ble_tracker::ESPBTDeviceListener {
+ public:
+ bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
+};
+
+} // namespace airthings_ble
+} // namespace esphome
+
+#endif
diff --git a/esphome/components/airthings_wave_mini/__init__.py b/esphome/components/airthings_wave_mini/__init__.py
new file mode 100644
index 0000000000..022f35b4cf
--- /dev/null
+++ b/esphome/components/airthings_wave_mini/__init__.py
@@ -0,0 +1 @@
+CODEOWNERS = ["@ncareau"]
diff --git a/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp
new file mode 100644
index 0000000000..6b6418f7e6
--- /dev/null
+++ b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp
@@ -0,0 +1,113 @@
+#include "airthings_wave_mini.h"
+
+#ifdef USE_ESP32
+
+namespace esphome {
+namespace airthings_wave_mini {
+
+static const char *const TAG = "airthings_wave_mini";
+
+void AirthingsWaveMini::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
+ esp_ble_gattc_cb_param_t *param) {
+ switch (event) {
+ case ESP_GATTC_OPEN_EVT: {
+ if (param->open.status == ESP_GATT_OK) {
+ ESP_LOGI(TAG, "Connected successfully!");
+ }
+ break;
+ }
+
+ case ESP_GATTC_DISCONNECT_EVT: {
+ ESP_LOGW(TAG, "Disconnected!");
+ break;
+ }
+
+ case ESP_GATTC_SEARCH_CMPL_EVT: {
+ this->handle_ = 0;
+ auto chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_);
+ if (chr == nullptr) {
+ ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(),
+ sensors_data_characteristic_uuid_.to_string().c_str());
+ break;
+ }
+ this->handle_ = chr->handle;
+ this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED;
+
+ request_read_values_();
+ break;
+ }
+
+ case ESP_GATTC_READ_CHAR_EVT: {
+ if (param->read.conn_id != this->parent()->conn_id)
+ break;
+ if (param->read.status != ESP_GATT_OK) {
+ ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
+ break;
+ }
+ if (param->read.handle == this->handle_) {
+ read_sensors_(param->read.value, param->read.value_len);
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+void AirthingsWaveMini::read_sensors_(uint8_t *raw_value, uint16_t value_len) {
+ auto value = (WaveMiniReadings *) raw_value;
+
+ if (sizeof(WaveMiniReadings) <= value_len) {
+ this->humidity_sensor_->publish_state(value->humidity / 100.0f);
+ this->pressure_sensor_->publish_state(value->pressure / 50.0f);
+ this->temperature_sensor_->publish_state(value->temperature / 100.0f - 273.15f);
+ if (is_valid_voc_value_(value->voc)) {
+ this->tvoc_sensor_->publish_state(value->voc);
+ }
+
+ // This instance must not stay connected
+ // so other clients can connect to it (e.g. the
+ // mobile app).
+ parent()->set_enabled(false);
+ }
+}
+
+bool AirthingsWaveMini::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; }
+
+void AirthingsWaveMini::update() {
+ if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) {
+ if (!parent()->enabled) {
+ ESP_LOGW(TAG, "Reconnecting to device");
+ parent()->set_enabled(true);
+ parent()->connect();
+ } else {
+ ESP_LOGW(TAG, "Connection in progress");
+ }
+ }
+}
+
+void AirthingsWaveMini::request_read_values_() {
+ auto status =
+ esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE);
+ if (status) {
+ ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
+ }
+}
+
+void AirthingsWaveMini::dump_config() {
+ LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
+ LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
+ LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
+ LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
+}
+
+AirthingsWaveMini::AirthingsWaveMini()
+ : PollingComponent(10000),
+ service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)),
+ sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {}
+
+} // namespace airthings_wave_mini
+} // namespace esphome
+
+#endif // USE_ESP32
diff --git a/esphome/components/airthings_wave_mini/airthings_wave_mini.h b/esphome/components/airthings_wave_mini/airthings_wave_mini.h
new file mode 100644
index 0000000000..128774f9cb
--- /dev/null
+++ b/esphome/components/airthings_wave_mini/airthings_wave_mini.h
@@ -0,0 +1,65 @@
+#pragma once
+
+#ifdef USE_ESP32
+
+#include
+#include
+#include
+#include "esphome/components/ble_client/ble_client.h"
+#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
+#include "esphome/components/sensor/sensor.h"
+#include "esphome/core/component.h"
+#include "esphome/core/log.h"
+
+namespace esphome {
+namespace airthings_wave_mini {
+
+static const char *const SERVICE_UUID = "b42e3882-ade7-11e4-89d3-123b93f75cba";
+static const char *const CHARACTERISTIC_UUID = "b42e3b98-ade7-11e4-89d3-123b93f75cba";
+
+class AirthingsWaveMini : public PollingComponent, public ble_client::BLEClientNode {
+ public:
+ AirthingsWaveMini();
+
+ void dump_config() override;
+ void update() override;
+
+ void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
+ esp_ble_gattc_cb_param_t *param) override;
+
+ void set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
+ void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
+ void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; }
+ void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
+
+ protected:
+ bool is_valid_voc_value_(uint16_t voc);
+
+ void read_sensors_(uint8_t *value, uint16_t value_len);
+ void request_read_values_();
+
+ sensor::Sensor *temperature_sensor_{nullptr};
+ sensor::Sensor *humidity_sensor_{nullptr};
+ sensor::Sensor *pressure_sensor_{nullptr};
+ sensor::Sensor *tvoc_sensor_{nullptr};
+
+ uint16_t handle_;
+ esp32_ble_tracker::ESPBTUUID service_uuid_;
+ esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_;
+
+ struct WaveMiniReadings {
+ uint16_t unused01;
+ uint16_t temperature;
+ uint16_t pressure;
+ uint16_t humidity;
+ uint16_t voc;
+ uint16_t unused02;
+ uint32_t unused03;
+ uint32_t unused04;
+ };
+};
+
+} // namespace airthings_wave_mini
+} // namespace esphome
+
+#endif // USE_ESP32
diff --git a/esphome/components/airthings_wave_mini/sensor.py b/esphome/components/airthings_wave_mini/sensor.py
new file mode 100644
index 0000000000..d38354fa84
--- /dev/null
+++ b/esphome/components/airthings_wave_mini/sensor.py
@@ -0,0 +1,82 @@
+import esphome.codegen as cg
+import esphome.config_validation as cv
+from esphome.components import sensor, ble_client
+
+from esphome.const import (
+ DEVICE_CLASS_HUMIDITY,
+ DEVICE_CLASS_TEMPERATURE,
+ DEVICE_CLASS_PRESSURE,
+ STATE_CLASS_MEASUREMENT,
+ UNIT_PERCENT,
+ UNIT_CELSIUS,
+ UNIT_HECTOPASCAL,
+ CONF_ID,
+ CONF_HUMIDITY,
+ CONF_TVOC,
+ CONF_PRESSURE,
+ CONF_TEMPERATURE,
+ UNIT_PARTS_PER_BILLION,
+ ICON_RADIATOR,
+)
+
+DEPENDENCIES = ["ble_client"]
+
+airthings_wave_mini_ns = cg.esphome_ns.namespace("airthings_wave_mini")
+AirthingsWaveMini = airthings_wave_mini_ns.class_(
+ "AirthingsWaveMini", cg.PollingComponent, ble_client.BLEClientNode
+)
+
+
+CONFIG_SCHEMA = cv.All(
+ cv.Schema(
+ {
+ cv.GenerateID(): cv.declare_id(AirthingsWaveMini),
+ cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
+ unit_of_measurement=UNIT_PERCENT,
+ device_class=DEVICE_CLASS_HUMIDITY,
+ state_class=STATE_CLASS_MEASUREMENT,
+ accuracy_decimals=2,
+ ),
+ cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
+ unit_of_measurement=UNIT_CELSIUS,
+ accuracy_decimals=2,
+ device_class=DEVICE_CLASS_TEMPERATURE,
+ state_class=STATE_CLASS_MEASUREMENT,
+ ),
+ cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
+ unit_of_measurement=UNIT_HECTOPASCAL,
+ accuracy_decimals=2,
+ device_class=DEVICE_CLASS_PRESSURE,
+ state_class=STATE_CLASS_MEASUREMENT,
+ ),
+ cv.Optional(CONF_TVOC): sensor.sensor_schema(
+ unit_of_measurement=UNIT_PARTS_PER_BILLION,
+ icon=ICON_RADIATOR,
+ accuracy_decimals=0,
+ state_class=STATE_CLASS_MEASUREMENT,
+ ),
+ }
+ )
+ .extend(cv.polling_component_schema("5min"))
+ .extend(ble_client.BLE_CLIENT_SCHEMA),
+)
+
+
+async def to_code(config):
+ var = cg.new_Pvariable(config[CONF_ID])
+ await cg.register_component(var, config)
+
+ await ble_client.register_ble_node(var, config)
+
+ if CONF_HUMIDITY in config:
+ sens = await sensor.new_sensor(config[CONF_HUMIDITY])
+ cg.add(var.set_humidity(sens))
+ if CONF_TEMPERATURE in config:
+ sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
+ cg.add(var.set_temperature(sens))
+ if CONF_PRESSURE in config:
+ sens = await sensor.new_sensor(config[CONF_PRESSURE])
+ cg.add(var.set_pressure(sens))
+ if CONF_TVOC in config:
+ sens = await sensor.new_sensor(config[CONF_TVOC])
+ cg.add(var.set_tvoc(sens))
diff --git a/esphome/components/airthings_wave_plus/__init__.py b/esphome/components/airthings_wave_plus/__init__.py
new file mode 100644
index 0000000000..1aff461edd
--- /dev/null
+++ b/esphome/components/airthings_wave_plus/__init__.py
@@ -0,0 +1 @@
+CODEOWNERS = ["@jeromelaban"]
diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp
new file mode 100644
index 0000000000..79f2cb7741
--- /dev/null
+++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp
@@ -0,0 +1,137 @@
+#include "airthings_wave_plus.h"
+
+#ifdef USE_ESP32
+
+namespace esphome {
+namespace airthings_wave_plus {
+
+static const char *const TAG = "airthings_wave_plus";
+
+void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
+ esp_ble_gattc_cb_param_t *param) {
+ switch (event) {
+ case ESP_GATTC_OPEN_EVT: {
+ if (param->open.status == ESP_GATT_OK) {
+ ESP_LOGI(TAG, "Connected successfully!");
+ }
+ break;
+ }
+
+ case ESP_GATTC_DISCONNECT_EVT: {
+ ESP_LOGW(TAG, "Disconnected!");
+ break;
+ }
+
+ case ESP_GATTC_SEARCH_CMPL_EVT: {
+ this->handle_ = 0;
+ auto chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_);
+ if (chr == nullptr) {
+ ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(),
+ sensors_data_characteristic_uuid_.to_string().c_str());
+ break;
+ }
+ this->handle_ = chr->handle;
+ this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED;
+
+ request_read_values_();
+ break;
+ }
+
+ case ESP_GATTC_READ_CHAR_EVT: {
+ if (param->read.conn_id != this->parent()->conn_id)
+ break;
+ if (param->read.status != ESP_GATT_OK) {
+ ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
+ break;
+ }
+ if (param->read.handle == this->handle_) {
+ read_sensors_(param->read.value, param->read.value_len);
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) {
+ auto value = (WavePlusReadings *) raw_value;
+
+ if (sizeof(WavePlusReadings) <= value_len) {
+ ESP_LOGD(TAG, "version = %d", value->version);
+
+ if (value->version == 1) {
+ ESP_LOGD(TAG, "ambient light = %d", value->ambientLight);
+
+ this->humidity_sensor_->publish_state(value->humidity / 2.0f);
+ if (is_valid_radon_value_(value->radon)) {
+ this->radon_sensor_->publish_state(value->radon);
+ }
+ if (is_valid_radon_value_(value->radon_lt)) {
+ this->radon_long_term_sensor_->publish_state(value->radon_lt);
+ }
+ this->temperature_sensor_->publish_state(value->temperature / 100.0f);
+ this->pressure_sensor_->publish_state(value->pressure / 50.0f);
+ if (is_valid_co2_value_(value->co2)) {
+ this->co2_sensor_->publish_state(value->co2);
+ }
+ if (is_valid_voc_value_(value->voc)) {
+ this->tvoc_sensor_->publish_state(value->voc);
+ }
+
+ // This instance must not stay connected
+ // so other clients can connect to it (e.g. the
+ // mobile app).
+ parent()->set_enabled(false);
+ } else {
+ ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version);
+ }
+ }
+}
+
+bool AirthingsWavePlus::is_valid_radon_value_(uint16_t radon) { return 0 <= radon && radon <= 16383; }
+
+bool AirthingsWavePlus::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; }
+
+bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return 0 <= co2 && co2 <= 16383; }
+
+void AirthingsWavePlus::update() {
+ if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) {
+ if (!parent()->enabled) {
+ ESP_LOGW(TAG, "Reconnecting to device");
+ parent()->set_enabled(true);
+ parent()->connect();
+ } else {
+ ESP_LOGW(TAG, "Connection in progress");
+ }
+ }
+}
+
+void AirthingsWavePlus::request_read_values_() {
+ auto status =
+ esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE);
+ if (status) {
+ ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
+ }
+}
+
+void AirthingsWavePlus::dump_config() {
+ LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
+ LOG_SENSOR(" ", "Radon", this->radon_sensor_);
+ LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_);
+ LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
+ LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
+ LOG_SENSOR(" ", "CO2", this->co2_sensor_);
+ LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
+}
+
+AirthingsWavePlus::AirthingsWavePlus()
+ : PollingComponent(10000),
+ service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)),
+ sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {}
+
+} // namespace airthings_wave_plus
+} // namespace esphome
+
+#endif // USE_ESP32
diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.h b/esphome/components/airthings_wave_plus/airthings_wave_plus.h
new file mode 100644
index 0000000000..9dd6ed92d5
--- /dev/null
+++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.h
@@ -0,0 +1,75 @@
+#pragma once
+
+#ifdef USE_ESP32
+
+#include
+#include
+#include
+#include "esphome/components/ble_client/ble_client.h"
+#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
+#include "esphome/components/sensor/sensor.h"
+#include "esphome/core/component.h"
+#include "esphome/core/log.h"
+
+namespace esphome {
+namespace airthings_wave_plus {
+
+static const char *const SERVICE_UUID = "b42e1c08-ade7-11e4-89d3-123b93f75cba";
+static const char *const CHARACTERISTIC_UUID = "b42e2a68-ade7-11e4-89d3-123b93f75cba";
+
+class AirthingsWavePlus : public PollingComponent, public ble_client::BLEClientNode {
+ public:
+ AirthingsWavePlus();
+
+ void dump_config() override;
+ void update() override;
+
+ void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
+ esp_ble_gattc_cb_param_t *param) override;
+
+ void set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
+ void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; }
+ void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; }
+ void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
+ void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; }
+ void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; }
+ void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
+
+ protected:
+ bool is_valid_radon_value_(uint16_t radon);
+ bool is_valid_voc_value_(uint16_t voc);
+ bool is_valid_co2_value_(uint16_t co2);
+
+ void read_sensors_(uint8_t *value, uint16_t value_len);
+ void request_read_values_();
+
+ sensor::Sensor *temperature_sensor_{nullptr};
+ sensor::Sensor *radon_sensor_{nullptr};
+ sensor::Sensor *radon_long_term_sensor_{nullptr};
+ sensor::Sensor *humidity_sensor_{nullptr};
+ sensor::Sensor *pressure_sensor_{nullptr};
+ sensor::Sensor *co2_sensor_{nullptr};
+ sensor::Sensor *tvoc_sensor_{nullptr};
+
+ uint16_t handle_;
+ esp32_ble_tracker::ESPBTUUID service_uuid_;
+ esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_;
+
+ struct WavePlusReadings {
+ uint8_t version;
+ uint8_t humidity;
+ uint8_t ambientLight;
+ uint8_t unused01;
+ uint16_t radon;
+ uint16_t radon_lt;
+ uint16_t temperature;
+ uint16_t pressure;
+ uint16_t co2;
+ uint16_t voc;
+ };
+};
+
+} // namespace airthings_wave_plus
+} // namespace esphome
+
+#endif // USE_ESP32
diff --git a/esphome/components/airthings_wave_plus/sensor.py b/esphome/components/airthings_wave_plus/sensor.py
new file mode 100644
index 0000000000..727fbe15fb
--- /dev/null
+++ b/esphome/components/airthings_wave_plus/sensor.py
@@ -0,0 +1,116 @@
+import esphome.codegen as cg
+import esphome.config_validation as cv
+from esphome.components import sensor, ble_client
+
+from esphome.const import (
+ DEVICE_CLASS_CARBON_DIOXIDE,
+ DEVICE_CLASS_HUMIDITY,
+ DEVICE_CLASS_TEMPERATURE,
+ DEVICE_CLASS_PRESSURE,
+ STATE_CLASS_MEASUREMENT,
+ UNIT_PERCENT,
+ UNIT_CELSIUS,
+ UNIT_HECTOPASCAL,
+ ICON_RADIOACTIVE,
+ CONF_ID,
+ CONF_RADON,
+ CONF_RADON_LONG_TERM,
+ CONF_HUMIDITY,
+ CONF_TVOC,
+ CONF_CO2,
+ CONF_PRESSURE,
+ CONF_TEMPERATURE,
+ UNIT_BECQUEREL_PER_CUBIC_METER,
+ UNIT_PARTS_PER_MILLION,
+ UNIT_PARTS_PER_BILLION,
+ ICON_RADIATOR,
+)
+
+DEPENDENCIES = ["ble_client"]
+
+airthings_wave_plus_ns = cg.esphome_ns.namespace("airthings_wave_plus")
+AirthingsWavePlus = airthings_wave_plus_ns.class_(
+ "AirthingsWavePlus", cg.PollingComponent, ble_client.BLEClientNode
+)
+
+
+CONFIG_SCHEMA = cv.All(
+ cv.Schema(
+ {
+ cv.GenerateID(): cv.declare_id(AirthingsWavePlus),
+ cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
+ unit_of_measurement=UNIT_PERCENT,
+ device_class=DEVICE_CLASS_HUMIDITY,
+ state_class=STATE_CLASS_MEASUREMENT,
+ accuracy_decimals=0,
+ ),
+ cv.Optional(CONF_RADON): sensor.sensor_schema(
+ unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
+ icon=ICON_RADIOACTIVE,
+ accuracy_decimals=0,
+ state_class=STATE_CLASS_MEASUREMENT,
+ ),
+ cv.Optional(CONF_RADON_LONG_TERM): sensor.sensor_schema(
+ unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
+ icon=ICON_RADIOACTIVE,
+ accuracy_decimals=0,
+ state_class=STATE_CLASS_MEASUREMENT,
+ ),
+ cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
+ unit_of_measurement=UNIT_CELSIUS,
+ accuracy_decimals=2,
+ device_class=DEVICE_CLASS_TEMPERATURE,
+ state_class=STATE_CLASS_MEASUREMENT,
+ ),
+ cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
+ unit_of_measurement=UNIT_HECTOPASCAL,
+ accuracy_decimals=1,
+ device_class=DEVICE_CLASS_PRESSURE,
+ state_class=STATE_CLASS_MEASUREMENT,
+ ),
+ cv.Optional(CONF_CO2): sensor.sensor_schema(
+ unit_of_measurement=UNIT_PARTS_PER_MILLION,
+ accuracy_decimals=0,
+ device_class=DEVICE_CLASS_CARBON_DIOXIDE,
+ state_class=STATE_CLASS_MEASUREMENT,
+ ),
+ cv.Optional(CONF_TVOC): sensor.sensor_schema(
+ unit_of_measurement=UNIT_PARTS_PER_BILLION,
+ icon=ICON_RADIATOR,
+ accuracy_decimals=0,
+ state_class=STATE_CLASS_MEASUREMENT,
+ ),
+ }
+ )
+ .extend(cv.polling_component_schema("5min"))
+ .extend(ble_client.BLE_CLIENT_SCHEMA),
+)
+
+
+async def to_code(config):
+ var = cg.new_Pvariable(config[CONF_ID])
+ await cg.register_component(var, config)
+
+ await ble_client.register_ble_node(var, config)
+
+ if CONF_HUMIDITY in config:
+ sens = await sensor.new_sensor(config[CONF_HUMIDITY])
+ cg.add(var.set_humidity(sens))
+ if CONF_RADON in config:
+ sens = await sensor.new_sensor(config[CONF_RADON])
+ cg.add(var.set_radon(sens))
+ if CONF_RADON_LONG_TERM in config:
+ sens = await sensor.new_sensor(config[CONF_RADON_LONG_TERM])
+ cg.add(var.set_radon_long_term(sens))
+ if CONF_TEMPERATURE in config:
+ sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
+ cg.add(var.set_temperature(sens))
+ if CONF_PRESSURE in config:
+ sens = await sensor.new_sensor(config[CONF_PRESSURE])
+ cg.add(var.set_pressure(sens))
+ if CONF_CO2 in config:
+ sens = await sensor.new_sensor(config[CONF_CO2])
+ cg.add(var.set_co2(sens))
+ if CONF_TVOC in config:
+ sens = await sensor.new_sensor(config[CONF_TVOC])
+ cg.add(var.set_tvoc(sens))
diff --git a/esphome/components/am2320/am2320.cpp b/esphome/components/am2320/am2320.cpp
index 59cb977fe8..c06a2a34d7 100644
--- a/esphome/components/am2320/am2320.cpp
+++ b/esphome/components/am2320/am2320.cpp
@@ -5,11 +5,12 @@
#include "am2320.h"
#include "esphome/core/log.h"
+#include "esphome/core/hal.h"
namespace esphome {
namespace am2320 {
-static const char *TAG = "am2320";
+static const char *const TAG = "am2320";
// ---=== Calc CRC16 ===---
uint16_t crc_16(uint8_t *ptr, uint8_t length) {
@@ -37,9 +38,9 @@ void AM2320Component::update() {
return;
}
- float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0;
+ float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0f;
temperature = (data[4] & 0x80) ? -temperature : temperature;
- float humidity = ((data[2] << 8) + data[3]) / 10.0;
+ float humidity = ((data[2] << 8) + data[3]) / 10.0f;
ESP_LOGD(TAG, "Got temperature=%.1f°C humidity=%.1f%%", temperature, humidity);
if (this->temperature_sensor_ != nullptr)
@@ -77,7 +78,7 @@ bool AM2320Component::read_bytes_(uint8_t a_register, uint8_t *data, uint8_t len
if (conversion > 0)
delay(conversion);
- return this->parent_->raw_receive(this->address_, data, len);
+ return this->read(data, len) == i2c::ERROR_OK;
}
bool AM2320Component::read_data_(uint8_t *data) {
diff --git a/esphome/components/am2320/sensor.py b/esphome/components/am2320/sensor.py
index d62899663c..088978a8f1 100644
--- a/esphome/components/am2320/sensor.py
+++ b/esphome/components/am2320/sensor.py
@@ -1,30 +1,56 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
-from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, \
- UNIT_CELSIUS, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_PERCENT
+from esphome.const import (
+ CONF_HUMIDITY,
+ CONF_ID,
+ CONF_TEMPERATURE,
+ DEVICE_CLASS_HUMIDITY,
+ DEVICE_CLASS_TEMPERATURE,
+ STATE_CLASS_MEASUREMENT,
+ UNIT_CELSIUS,
+ UNIT_PERCENT,
+)
-DEPENDENCIES = ['i2c']
+DEPENDENCIES = ["i2c"]
-am2320_ns = cg.esphome_ns.namespace('am2320')
-AM2320Component = am2320_ns.class_('AM2320Component', cg.PollingComponent, i2c.I2CDevice)
+am2320_ns = cg.esphome_ns.namespace("am2320")
+AM2320Component = am2320_ns.class_(
+ "AM2320Component", cg.PollingComponent, i2c.I2CDevice
+)
-CONFIG_SCHEMA = cv.Schema({
- cv.GenerateID(): cv.declare_id(AM2320Component),
- cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1),
- cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1),
-}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x5C))
+CONFIG_SCHEMA = (
+ cv.Schema(
+ {
+ cv.GenerateID(): cv.declare_id(AM2320Component),
+ cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
+ unit_of_measurement=UNIT_CELSIUS,
+ accuracy_decimals=1,
+ device_class=DEVICE_CLASS_TEMPERATURE,
+ state_class=STATE_CLASS_MEASUREMENT,
+ ),
+ cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
+ unit_of_measurement=UNIT_PERCENT,
+ accuracy_decimals=1,
+ device_class=DEVICE_CLASS_HUMIDITY,
+ state_class=STATE_CLASS_MEASUREMENT,
+ ),
+ }
+ )
+ .extend(cv.polling_component_schema("60s"))
+ .extend(i2c.i2c_device_schema(0x5C))
+)
-def to_code(config):
+async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
- yield cg.register_component(var, config)
- yield i2c.register_i2c_device(var, config)
+ await cg.register_component(var, config)
+ await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config:
- sens = yield sensor.new_sensor(config[CONF_TEMPERATURE])
+ sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
cg.add(var.set_temperature_sensor(sens))
if CONF_HUMIDITY in config:
- sens = yield sensor.new_sensor(config[CONF_HUMIDITY])
+ sens = await sensor.new_sensor(config[CONF_HUMIDITY])
cg.add(var.set_humidity_sensor(sens))
diff --git a/esphome/components/xiaomi_miflora/__init__.py b/esphome/components/am43/__init__.py
similarity index 100%
rename from esphome/components/xiaomi_miflora/__init__.py
rename to esphome/components/am43/__init__.py
diff --git a/esphome/components/am43/am43.cpp b/esphome/components/am43/am43.cpp
new file mode 100644
index 0000000000..a62e3bb6df
--- /dev/null
+++ b/esphome/components/am43/am43.cpp
@@ -0,0 +1,116 @@
+#include "am43.h"
+#include "esphome/core/log.h"
+#include "esphome/core/hal.h"
+
+#ifdef USE_ESP32
+
+namespace esphome {
+namespace am43 {
+
+static const char *const TAG = "am43";
+
+void Am43::dump_config() {
+ ESP_LOGCONFIG(TAG, "AM43");
+ LOG_SENSOR(" ", "Battery", this->battery_);
+ LOG_SENSOR(" ", "Illuminance", this->illuminance_);
+}
+
+void Am43::setup() {
+ this->encoder_ = make_unique();
+ this->decoder_ = make_unique();
+ this->logged_in_ = false;
+ this->last_battery_update_ = 0;
+ this->current_sensor_ = 0;
+}
+
+void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
+ switch (event) {
+ case ESP_GATTC_OPEN_EVT: {
+ this->logged_in_ = false;
+ break;
+ }
+ case ESP_GATTC_DISCONNECT_EVT: {
+ this->logged_in_ = false;
+ this->node_state = espbt::ClientState::IDLE;
+ if (this->battery_ != nullptr)
+ this->battery_->publish_state(NAN);
+ if (this->illuminance_ != nullptr)
+ this->illuminance_->publish_state(NAN);
+ break;
+ }
+ case ESP_GATTC_SEARCH_CMPL_EVT: {
+ auto chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID);
+ if (chr == nullptr) {
+ if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) {
+ ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.",
+ this->parent_->address_str().c_str());
+ } else {
+ ESP_LOGE(TAG, "[%s] No control service found at device, not an AM43..?",
+ this->parent_->address_str().c_str());
+ }
+ break;
+ }
+ this->char_handle_ = chr->handle;
+ break;
+ }
+ case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
+ this->node_state = espbt::ClientState::ESTABLISHED;
+ this->update();
+ break;
+ }
+ case ESP_GATTC_NOTIFY_EVT: {
+ if (param->notify.handle != this->char_handle_)
+ break;
+ this->decoder_->decode(param->notify.value, param->notify.value_len);
+
+ if (this->battery_ != nullptr && this->decoder_->has_battery_level() &&
+ millis() - this->last_battery_update_ > 10000) {
+ this->battery_->publish_state(this->decoder_->battery_level_);
+ this->last_battery_update_ = millis();
+ }
+
+ if (this->illuminance_ != nullptr && this->decoder_->has_light_level()) {
+ this->illuminance_->publish_state(this->decoder_->light_level_);
+ }
+
+ if (this->current_sensor_ > 0) {
+ if (this->illuminance_ != nullptr) {
+ auto packet = this->encoder_->get_light_level_request();
+ auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
+ packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP,
+ ESP_GATT_AUTH_REQ_NONE);
+ if (status)
+ ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(),
+ status);
+ }
+ this->current_sensor_ = 0;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void Am43::update() {
+ if (this->node_state != espbt::ClientState::ESTABLISHED) {
+ ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str());
+ return;
+ }
+ if (this->current_sensor_ == 0) {
+ if (this->battery_ != nullptr) {
+ auto packet = this->encoder_->get_battery_level_request();
+ auto status =
+ esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
+ packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
+ if (status)
+ ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
+ }
+ this->current_sensor_++;
+ }
+}
+
+} // namespace am43
+} // namespace esphome
+
+#endif
diff --git a/esphome/components/am43/am43.h b/esphome/components/am43/am43.h
new file mode 100644
index 0000000000..8dfe83e3a3
--- /dev/null
+++ b/esphome/components/am43/am43.h
@@ -0,0 +1,45 @@
+#pragma once
+
+#include "esphome/core/component.h"
+#include "esphome/components/ble_client/ble_client.h"
+#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
+#include "esphome/components/sensor/sensor.h"
+#include "esphome/components/am43/am43_base.h"
+
+#ifdef USE_ESP32
+
+#include
+
+namespace esphome {
+namespace am43 {
+
+namespace espbt = esphome::esp32_ble_tracker;
+
+class Am43 : public esphome::ble_client::BLEClientNode, public PollingComponent {
+ public:
+ void setup() override;
+ void update() override;
+ void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
+ esp_ble_gattc_cb_param_t *param) override;
+ void dump_config() override;
+ float get_setup_priority() const override { return setup_priority::DATA; }
+ void set_battery(sensor::Sensor *battery) { battery_ = battery; }
+ void set_illuminance(sensor::Sensor *illuminance) { illuminance_ = illuminance; }
+
+ protected:
+ uint16_t char_handle_;
+ std::unique_ptr encoder_;
+ std::unique_ptr decoder_;
+ bool logged_in_;
+ sensor::Sensor *battery_{nullptr};
+ sensor::Sensor *illuminance_{nullptr};
+ uint8_t current_sensor_;
+ // The AM43 often gets into a state where it spams loads of battery update
+ // notifications. Here we will limit to no more than every 10s.
+ uint8_t last_battery_update_;
+};
+
+} // namespace am43
+} // namespace esphome
+
+#endif
diff --git a/esphome/components/am43/am43_base.cpp b/esphome/components/am43/am43_base.cpp
new file mode 100644
index 0000000000..af474dcb79
--- /dev/null
+++ b/esphome/components/am43/am43_base.cpp
@@ -0,0 +1,144 @@
+#include "am43_base.h"
+#include
+#include
+
+namespace esphome {
+namespace am43 {
+
+const uint8_t START_PACKET[5] = {0x00, 0xff, 0x00, 0x00, 0x9a};
+
+std::string pkt_to_hex(const uint8_t *data, uint16_t len) {
+ char buf[64];
+ memset(buf, 0, 64);
+ for (int i = 0; i < len; i++)
+ sprintf(&buf[i * 2], "%02x", data[i]);
+ std::string ret = buf;
+ return ret;
+}
+
+Am43Packet *Am43Encoder::get_battery_level_request() {
+ uint8_t data = 0x1;
+ return this->encode_(0xA2, &data, 1);
+}
+
+Am43Packet *Am43Encoder::get_light_level_request() {
+ uint8_t data = 0x1;
+ return this->encode_(0xAA, &data, 1);
+}
+
+Am43Packet *Am43Encoder::get_position_request() {
+ uint8_t data = 0x1;
+ return this->encode_(CMD_GET_POSITION, &data, 1);
+}
+
+Am43Packet *Am43Encoder::get_send_pin_request(uint16_t pin) {
+ uint8_t data[2];
+ data[0] = (pin & 0xFF00) >> 8;
+ data[1] = pin & 0xFF;
+ return this->encode_(CMD_SEND_PIN, data, 2);
+}
+
+Am43Packet *Am43Encoder::get_open_request() {
+ uint8_t data = 0xDD;
+ return this->encode_(CMD_SET_STATE, &data, 1);
+}
+
+Am43Packet *Am43Encoder::get_close_request() {
+ uint8_t data = 0xEE;
+ return this->encode_(CMD_SET_STATE, &data, 1);
+}
+
+Am43Packet *Am43Encoder::get_stop_request() {
+ uint8_t data = 0xCC;
+ return this->encode_(CMD_SET_STATE, &data, 1);
+}
+
+Am43Packet *Am43Encoder::get_set_position_request(uint8_t position) {
+ return this->encode_(CMD_SET_POSITION, &position, 1);
+}
+
+void Am43Encoder::checksum_() {
+ uint8_t checksum = 0;
+ int i = 0;
+ for (i = 0; i < this->packet_.length; i++)
+ checksum = checksum ^ this->packet_.data[i];
+ this->packet_.data[i] = checksum ^ 0xff;
+ this->packet_.length++;
+}
+
+Am43Packet *Am43Encoder::encode_(uint8_t command, uint8_t *data, uint8_t length) {
+ memcpy(this->packet_.data, START_PACKET, 5);
+ this->packet_.data[5] = command;
+ this->packet_.data[6] = length;
+ memcpy(&this->packet_.data[7], data, length);
+ this->packet_.length = length + 7;
+ this->checksum_();
+ ESP_LOGV("am43", "ENC(%d): 0x%s", packet_.length, pkt_to_hex(packet_.data, packet_.length).c_str());
+ return &this->packet_;
+}
+
+#define VERIFY_MIN_LENGTH(x) \
+ if (length < (x)) \
+ return;
+
+void Am43Decoder::decode(const uint8_t *data, uint16_t length) {
+ this->has_battery_level_ = false;
+ this->has_light_level_ = false;
+ this->has_set_position_response_ = false;
+ this->has_set_state_response_ = false;
+ this->has_position_ = false;
+ this->has_pin_response_ = false;
+ ESP_LOGV("am43", "DEC(%d): 0x%s", length, pkt_to_hex(data, length).c_str());
+
+ if (length < 2 || data[0] != 0x9a)
+ return;
+ switch (data[1]) {
+ case CMD_GET_BATTERY_LEVEL: {
+ VERIFY_MIN_LENGTH(8);
+ this->battery_level_ = data[7];
+ this->has_battery_level_ = true;
+ break;
+ }
+ case CMD_GET_LIGHT_LEVEL: {
+ VERIFY_MIN_LENGTH(5);
+ this->light_level_ = 100 * ((float) data[4] / 9);
+ this->has_light_level_ = true;
+ break;
+ }
+ case CMD_GET_POSITION: {
+ VERIFY_MIN_LENGTH(6);
+ this->position_ = data[5];
+ this->has_position_ = true;
+ break;
+ }
+ case CMD_NOTIFY_POSITION: {
+ VERIFY_MIN_LENGTH(5);
+ this->position_ = data[4];
+ this->has_position_ = true;
+ break;
+ }
+ case CMD_SEND_PIN: {
+ VERIFY_MIN_LENGTH(4);
+ this->pin_ok_ = data[3] == RESPONSE_ACK;
+ this->has_pin_response_ = true;
+ break;
+ }
+ case CMD_SET_POSITION: {
+ VERIFY_MIN_LENGTH(4);
+ this->set_position_ok_ = data[3] == RESPONSE_ACK;
+ this->has_set_position_response_ = true;
+ break;
+ }
+ case CMD_SET_STATE: {
+ VERIFY_MIN_LENGTH(4);
+ this->set_state_ok_ = data[3] == RESPONSE_ACK;
+ this->has_set_state_response_ = true;
+ break;
+ }
+ default:
+ break;
+ }
+};
+
+} // namespace am43
+} // namespace esphome
diff --git a/esphome/components/am43/am43_base.h b/esphome/components/am43/am43_base.h
new file mode 100644
index 0000000000..e817f161fe
--- /dev/null
+++ b/esphome/components/am43/am43_base.h
@@ -0,0 +1,78 @@
+#pragma once
+
+#include "esphome/core/log.h"
+#include "esphome/core/helpers.h"
+
+namespace esphome {
+namespace am43 {
+
+static const uint16_t AM43_SERVICE_UUID = 0xFE50;
+static const uint16_t AM43_CHARACTERISTIC_UUID = 0xFE51;
+//
+// Tuya identifiers, only to detect and warn users as they are incompatible.
+static const uint16_t AM43_TUYA_SERVICE_UUID = 0x1910;
+static const uint16_t AM43_TUYA_CHARACTERISTIC_UUID = 0x2b11;
+
+struct Am43Packet {
+ uint8_t length;
+ uint8_t data[24];
+};
+
+static const uint8_t CMD_GET_BATTERY_LEVEL = 0xA2;
+static const uint8_t CMD_GET_LIGHT_LEVEL = 0xAA;
+static const uint8_t CMD_GET_POSITION = 0xA7;
+static const uint8_t CMD_SEND_PIN = 0x17;
+static const uint8_t CMD_SET_STATE = 0x0A;
+static const uint8_t CMD_SET_POSITION = 0x0D;
+static const uint8_t CMD_NOTIFY_POSITION = 0xA1;
+
+static const uint8_t RESPONSE_ACK = 0x5A;
+static const uint8_t RESPONSE_NACK = 0xA5;
+
+class Am43Encoder {
+ public:
+ Am43Packet *get_battery_level_request();
+ Am43Packet *get_light_level_request();
+ Am43Packet *get_position_request();
+ Am43Packet *get_send_pin_request(uint16_t pin);
+ Am43Packet *get_open_request();
+ Am43Packet *get_close_request();
+ Am43Packet *get_stop_request();
+ Am43Packet *get_set_position_request(uint8_t position);
+
+ protected:
+ void checksum_();
+ Am43Packet *encode_(uint8_t command, uint8_t *data, uint8_t length);
+ Am43Packet packet_;
+};
+
+class Am43Decoder {
+ public:
+ void decode(const uint8_t *data, uint16_t length);
+ bool has_battery_level() { return this->has_battery_level_; }
+ bool has_light_level() { return this->has_light_level_; }
+ bool has_set_position_response() { return this->has_set_position_response_; }
+ bool has_set_state_response() { return this->has_set_state_response_; }
+ bool has_position() { return this->has_position_; }
+ bool has_pin_response() { return this->has_pin_response_; }
+
+ union {
+ uint8_t position_;
+ uint8_t battery_level_;
+ float light_level_;
+ uint8_t set_position_ok_;
+ uint8_t set_state_ok_;
+ uint8_t pin_ok_;
+ };
+
+ protected:
+ bool has_battery_level_;
+ bool has_light_level_;
+ bool has_set_position_response_;
+ bool has_set_state_response_;
+ bool has_position_;
+ bool has_pin_response_;
+};
+
+} // namespace am43
+} // namespace esphome
diff --git a/esphome/components/am43/cover/__init__.py b/esphome/components/am43/cover/__init__.py
new file mode 100644
index 0000000000..1ab0edbe78
--- /dev/null
+++ b/esphome/components/am43/cover/__init__.py
@@ -0,0 +1,36 @@
+import esphome.codegen as cg
+import esphome.config_validation as cv
+from esphome.components import cover, ble_client
+from esphome.const import CONF_ID, CONF_PIN
+
+CODEOWNERS = ["@buxtronix"]
+DEPENDENCIES = ["ble_client"]
+AUTO_LOAD = ["am43"]
+
+CONF_INVERT_POSITION = "invert_position"
+
+am43_ns = cg.esphome_ns.namespace("am43")
+Am43Component = am43_ns.class_(
+ "Am43Component", cover.Cover, ble_client.BLEClientNode, cg.Component
+)
+
+CONFIG_SCHEMA = (
+ cover.COVER_SCHEMA.extend(
+ {
+ cv.GenerateID(): cv.declare_id(Am43Component),
+ cv.Optional(CONF_PIN, default=8888): cv.int_range(min=0, max=0xFFFF),
+ cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean,
+ }
+ )
+ .extend(ble_client.BLE_CLIENT_SCHEMA)
+ .extend(cv.COMPONENT_SCHEMA)
+)
+
+
+def to_code(config):
+ var = cg.new_Pvariable(config[CONF_ID])
+ cg.add(var.set_pin(config[CONF_PIN]))
+ cg.add(var.set_invert_position(config[CONF_INVERT_POSITION]))
+ yield cg.register_component(var, config)
+ yield cover.register_cover(var, config)
+ yield ble_client.register_ble_node(var, config)
diff --git a/esphome/components/am43/cover/am43_cover.cpp b/esphome/components/am43/cover/am43_cover.cpp
new file mode 100644
index 0000000000..274c527760
--- /dev/null
+++ b/esphome/components/am43/cover/am43_cover.cpp
@@ -0,0 +1,149 @@
+#include "am43_cover.h"
+#include "esphome/core/log.h"
+
+#ifdef USE_ESP32
+
+namespace esphome {
+namespace am43 {
+
+static const char *const TAG = "am43_cover";
+
+using namespace esphome::cover;
+
+void Am43Component::dump_config() {
+ LOG_COVER("", "AM43 Cover", this);
+ ESP_LOGCONFIG(TAG, " Device Pin: %d", this->pin_);
+ ESP_LOGCONFIG(TAG, " Invert Position: %d", (int) this->invert_position_);
+}
+
+void Am43Component::setup() {
+ this->position = COVER_OPEN;
+ this->encoder_ = make_unique();
+ this->decoder_ = make_unique();
+ this->logged_in_ = false;
+}
+
+void Am43Component::loop() {
+ if (this->node_state == espbt::ClientState::ESTABLISHED && !this->logged_in_) {
+ auto packet = this->encoder_->get_send_pin_request(this->pin_);
+ auto status =
+ esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
+ packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
+ ESP_LOGI(TAG, "[%s] Logging into AM43", this->get_name().c_str());
+ if (status)
+ ESP_LOGW(TAG, "[%s] Error writing set_pin to device, error = %d", this->get_name().c_str(), status);
+ else
+ this->logged_in_ = true;
+ }
+}
+
+CoverTraits Am43Component::get_traits() {
+ auto traits = CoverTraits();
+ traits.set_supports_position(true);
+ traits.set_supports_tilt(false);
+ traits.set_is_assumed_state(false);
+ return traits;
+}
+
+void Am43Component::control(const CoverCall &call) {
+ if (this->node_state != espbt::ClientState::ESTABLISHED) {
+ ESP_LOGW(TAG, "[%s] Cannot send cover control, not connected", this->get_name().c_str());
+ return;
+ }
+ if (call.get_stop()) {
+ auto packet = this->encoder_->get_stop_request();
+ auto status =
+ esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
+ packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
+ if (status)
+ ESP_LOGW(TAG, "[%s] Error writing stop command to device, error = %d", this->get_name().c_str(), status);
+ }
+ if (call.get_position().has_value()) {
+ auto pos = *call.get_position();
+
+ if (this->invert_position_)
+ pos = 1 - pos;
+ auto packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100));
+ auto status =
+ esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
+ packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
+ if (status)
+ ESP_LOGW(TAG, "[%s] Error writing set_position command to device, error = %d", this->get_name().c_str(), status);
+ }
+}
+
+void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
+ esp_ble_gattc_cb_param_t *param) {
+ switch (event) {
+ case ESP_GATTC_DISCONNECT_EVT: {
+ this->logged_in_ = false;
+ break;
+ }
+ case ESP_GATTC_SEARCH_CMPL_EVT: {
+ auto chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID);
+ if (chr == nullptr) {
+ if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) {
+ ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.", this->get_name().c_str());
+ } else {
+ ESP_LOGE(TAG, "[%s] No control service found at device, not an AM43..?", this->get_name().c_str());
+ }
+ break;
+ }
+ this->char_handle_ = chr->handle;
+
+ auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle);
+ if (status) {
+ ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status);
+ }
+ break;
+ }
+ case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
+ this->node_state = espbt::ClientState::ESTABLISHED;
+ break;
+ }
+ case ESP_GATTC_NOTIFY_EVT: {
+ if (param->notify.handle != this->char_handle_)
+ break;
+ this->decoder_->decode(param->notify.value, param->notify.value_len);
+
+ if (this->decoder_->has_position()) {
+ this->position = ((float) this->decoder_->position_ / 100.0);
+ if (!this->invert_position_)
+ this->position = 1 - this->position;
+ if (this->position > 0.97)
+ this->position = 1.0;
+ if (this->position < 0.02)
+ this->position = 0.0;
+ this->publish_state();
+ }
+
+ if (this->decoder_->has_pin_response()) {
+ if (this->decoder_->pin_ok_) {
+ ESP_LOGI(TAG, "[%s] AM43 pin accepted.", this->get_name().c_str());
+ auto packet = this->encoder_->get_position_request();
+ auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
+ packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP,
+ ESP_GATT_AUTH_REQ_NONE);
+ if (status)
+ ESP_LOGW(TAG, "[%s] Error writing set_position to device, error = %d", this->get_name().c_str(), status);
+ } else {
+ ESP_LOGW(TAG, "[%s] AM43 pin rejected!", this->get_name().c_str());
+ }
+ }
+
+ if (this->decoder_->has_set_position_response() && !this->decoder_->set_position_ok_)
+ ESP_LOGW(TAG, "[%s] Got nack after sending set_position. Bad pin?", this->get_name().c_str());
+
+ if (this->decoder_->has_set_state_response() && !this->decoder_->set_state_ok_)
+ ESP_LOGW(TAG, "[%s] Got nack after sending set_state. Bad pin?", this->get_name().c_str());
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+} // namespace am43
+} // namespace esphome
+
+#endif
diff --git a/esphome/components/am43/cover/am43_cover.h b/esphome/components/am43/cover/am43_cover.h
new file mode 100644
index 0000000000..f33f2d1734
--- /dev/null
+++ b/esphome/components/am43/cover/am43_cover.h
@@ -0,0 +1,45 @@
+#pragma once
+
+#include "esphome/core/component.h"
+#include "esphome/components/ble_client/ble_client.h"
+#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
+#include "esphome/components/cover/cover.h"
+#include "esphome/components/am43/am43_base.h"
+
+#ifdef USE_ESP32
+
+#include
+
+namespace esphome {
+namespace am43 {
+
+namespace espbt = esphome::esp32_ble_tracker;
+
+class Am43Component : public cover::Cover, public esphome::ble_client::BLEClientNode, public Component {
+ public:
+ void setup() override;
+ void loop() override;
+ void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
+ esp_ble_gattc_cb_param_t *param) override;
+ void dump_config() override;
+ float get_setup_priority() const override { return setup_priority::DATA; }
+ cover::CoverTraits get_traits() override;
+ void set_pin(uint16_t pin) { this->pin_ = pin; }
+ void set_invert_position(bool invert_position) { this->invert_position_ = invert_position; }
+
+ protected:
+ void control(const cover::CoverCall &call) override;
+ uint16_t char_handle_;
+ uint16_t pin_;
+ bool invert_position_;
+ std::unique_ptr encoder_;
+ std::unique_ptr decoder_;
+ bool logged_in_;
+
+ float position_;
+};
+
+} // namespace am43
+} // namespace esphome
+
+#endif
diff --git a/esphome/components/am43/sensor.py b/esphome/components/am43/sensor.py
new file mode 100644
index 0000000000..68c85d0e9c
--- /dev/null
+++ b/esphome/components/am43/sensor.py
@@ -0,0 +1,52 @@
+import esphome.codegen as cg
+import esphome.config_validation as cv
+from esphome.components import sensor, ble_client
+from esphome.const import (
+ CONF_ID,
+ CONF_BATTERY_LEVEL,
+ DEVICE_CLASS_BATTERY,
+ ENTITY_CATEGORY_DIAGNOSTIC,
+ CONF_ILLUMINANCE,
+ ICON_BRIGHTNESS_5,
+ UNIT_PERCENT,
+)
+
+CODEOWNERS = ["@buxtronix"]
+
+am43_ns = cg.esphome_ns.namespace("am43")
+Am43 = am43_ns.class_("Am43", ble_client.BLEClientNode, cg.PollingComponent)
+
+CONFIG_SCHEMA = (
+ cv.Schema(
+ {
+ cv.GenerateID(): cv.declare_id(Am43),
+ cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
+ unit_of_measurement=UNIT_PERCENT,
+ device_class=DEVICE_CLASS_BATTERY,
+ accuracy_decimals=0,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
+ ),
+ cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
+ unit_of_measurement=UNIT_PERCENT,
+ icon=ICON_BRIGHTNESS_5,
+ accuracy_decimals=0,
+ ),
+ }
+ )
+ .extend(ble_client.BLE_CLIENT_SCHEMA)
+ .extend(cv.polling_component_schema("120s"))
+)
+
+
+def to_code(config):
+ var = cg.new_Pvariable(config[CONF_ID])
+ yield cg.register_component(var, config)
+ yield ble_client.register_ble_node(var, config)
+
+ if CONF_BATTERY_LEVEL in config:
+ sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL])
+ cg.add(var.set_battery(sens))
+
+ if CONF_ILLUMINANCE in config:
+ sens = yield sensor.new_sensor(config[CONF_ILLUMINANCE])
+ cg.add(var.set_illuminance(sens))
diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py
new file mode 100644
index 0000000000..7c9ff07f97
--- /dev/null
+++ b/esphome/components/animation/__init__.py
@@ -0,0 +1,119 @@
+import logging
+
+from esphome import core
+from esphome.components import display, font
+import esphome.components.image as espImage
+import esphome.config_validation as cv
+import esphome.codegen as cg
+from esphome.const import CONF_FILE, CONF_ID, CONF_RAW_DATA_ID, CONF_RESIZE, CONF_TYPE
+from esphome.core import CORE, HexInt
+
+_LOGGER = logging.getLogger(__name__)
+
+DEPENDENCIES = ["display"]
+MULTI_CONF = True
+
+Animation_ = display.display_ns.class_("Animation")
+
+ANIMATION_SCHEMA = cv.Schema(
+ {
+ cv.Required(CONF_ID): cv.declare_id(Animation_),
+ cv.Required(CONF_FILE): cv.file_,
+ cv.Optional(CONF_RESIZE): cv.dimensions,
+ cv.Optional(CONF_TYPE, default="BINARY"): cv.enum(
+ espImage.IMAGE_TYPE, upper=True
+ ),
+ cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
+ }
+)
+
+CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA)
+
+CODEOWNERS = ["@syndlex"]
+
+
+async def to_code(config):
+ from PIL import Image
+
+ path = CORE.relative_config_path(config[CONF_FILE])
+ try:
+ image = Image.open(path)
+ except Exception as e:
+ raise core.EsphomeError(f"Could not load image file {path}: {e}")
+
+ width, height = image.size
+ frames = image.n_frames
+ if CONF_RESIZE in config:
+ new_width_max, new_height_max = config[CONF_RESIZE]
+ ratio = min(new_width_max / width, new_height_max / height)
+ width, height = int(width * ratio), int(height * ratio)
+ else:
+ if width > 500 or height > 500:
+ _LOGGER.warning(
+ "The image you requested is very big. Please consider using"
+ " the resize parameter."
+ )
+
+ if config[CONF_TYPE] == "GRAYSCALE":
+ data = [0 for _ in range(height * width * frames)]
+ pos = 0
+ for frameIndex in range(frames):
+ image.seek(frameIndex)
+ frame = image.convert("L", dither=Image.NONE)
+ if CONF_RESIZE in config:
+ frame = frame.resize([width, height])
+ pixels = list(frame.getdata())
+ if len(pixels) != height * width:
+ raise core.EsphomeError(
+ f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
+ )
+ for pix in pixels:
+ data[pos] = pix
+ pos += 1
+
+ elif config[CONF_TYPE] == "RGB24":
+ data = [0 for _ in range(height * width * 3 * frames)]
+ pos = 0
+ for frameIndex in range(frames):
+ image.seek(frameIndex)
+ frame = image.convert("RGB")
+ if CONF_RESIZE in config:
+ frame = frame.resize([width, height])
+ pixels = list(frame.getdata())
+ if len(pixels) != height * width:
+ raise core.EsphomeError(
+ f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
+ )
+ for pix in pixels:
+ data[pos] = pix[0]
+ pos += 1
+ data[pos] = pix[1]
+ pos += 1
+ data[pos] = pix[2]
+ pos += 1
+
+ elif config[CONF_TYPE] == "BINARY":
+ width8 = ((width + 7) // 8) * 8
+ data = [0 for _ in range((height * width8 // 8) * frames)]
+ for frameIndex in range(frames):
+ image.seek(frameIndex)
+ frame = image.convert("1", dither=Image.NONE)
+ if CONF_RESIZE in config:
+ frame = frame.resize([width, height])
+ for y in range(height):
+ for x in range(width):
+ if frame.getpixel((x, y)):
+ continue
+ pos = x + y * width8 + (height * width8 * frameIndex)
+ data[pos // 8] |= 0x80 >> (pos % 8)
+
+ rhs = [HexInt(x) for x in data]
+ prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
+ cg.new_Pvariable(
+ config[CONF_ID],
+ prog_arr,
+ width,
+ height,
+ frames,
+ espImage.IMAGE_TYPE[config[CONF_TYPE]],
+ )
diff --git a/esphome/components/xiaomi_mijia/__init__.py b/esphome/components/anova/__init__.py
similarity index 100%
rename from esphome/components/xiaomi_mijia/__init__.py
rename to esphome/components/anova/__init__.py
diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp
new file mode 100644
index 0000000000..5d9afddc74
--- /dev/null
+++ b/esphome/components/anova/anova.cpp
@@ -0,0 +1,150 @@
+#include "anova.h"
+#include "esphome/core/log.h"
+
+#ifdef USE_ESP32
+
+namespace esphome {
+namespace anova {
+
+static const char *const TAG = "anova";
+
+using namespace esphome::climate;
+
+void Anova::dump_config() { LOG_CLIMATE("", "Anova BLE Cooker", this); }
+
+void Anova::setup() {
+ this->codec_ = make_unique();
+ this->current_request_ = 0;
+}
+
+void Anova::loop() {}
+
+void Anova::control(const ClimateCall &call) {
+ if (call.get_mode().has_value()) {
+ ClimateMode mode = *call.get_mode();
+ AnovaPacket *pkt;
+ switch (mode) {
+ case climate::CLIMATE_MODE_OFF:
+ pkt = this->codec_->get_stop_request();
+ break;
+ case climate::CLIMATE_MODE_HEAT:
+ pkt = this->codec_->get_start_request();
+ break;
+ default:
+ ESP_LOGW(TAG, "Unsupported mode: %d", mode);
+ return;
+ }
+ auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
+ pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
+ if (status)
+ ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
+ }
+ if (call.get_target_temperature().has_value()) {
+ auto pkt = this->codec_->get_set_target_temp_request(*call.get_target_temperature());
+ auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
+ pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
+ if (status)
+ ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
+ }
+}
+
+void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
+ switch (event) {
+ case ESP_GATTC_DISCONNECT_EVT: {
+ this->current_temperature = NAN;
+ this->target_temperature = NAN;
+ this->publish_state();
+ break;
+ }
+ case ESP_GATTC_SEARCH_CMPL_EVT: {
+ auto chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID);
+ if (chr == nullptr) {
+ ESP_LOGW(TAG, "[%s] No control service found at device, not an Anova..?", this->get_name().c_str());
+ ESP_LOGW(TAG, "[%s] Note, this component does not currently support Anova Nano.", this->get_name().c_str());
+ break;
+ }
+ this->char_handle_ = chr->handle;
+
+ auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle);
+ if (status) {
+ ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status);
+ }
+ break;
+ }
+ case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
+ this->node_state = espbt::ClientState::ESTABLISHED;
+ this->current_request_ = 0;
+ this->update();
+ break;
+ }
+ case ESP_GATTC_NOTIFY_EVT: {
+ if (param->notify.handle != this->char_handle_)
+ break;
+ this->codec_->decode(param->notify.value, param->notify.value_len);
+ if (this->codec_->has_target_temp()) {
+ this->target_temperature = this->codec_->target_temp_;
+ }
+ if (this->codec_->has_current_temp()) {
+ this->current_temperature = this->codec_->current_temp_;
+ }
+ if (this->codec_->has_running()) {
+ this->mode = this->codec_->running_ ? climate::CLIMATE_MODE_HEAT : climate::CLIMATE_MODE_OFF;
+ }
+ if (this->codec_->has_unit()) {
+ this->fahrenheit_ = (this->codec_->unit_ == 'f');
+ ESP_LOGD(TAG, "Anova units is %s", this->fahrenheit_ ? "fahrenheit" : "celcius");
+ this->current_request_++;
+ }
+ this->publish_state();
+
+ if (this->current_request_ > 1) {
+ AnovaPacket *pkt = nullptr;
+ switch (this->current_request_++) {
+ case 2:
+ pkt = this->codec_->get_read_target_temp_request();
+ break;
+ case 3:
+ pkt = this->codec_->get_read_current_temp_request();
+ break;
+ default:
+ this->current_request_ = 1;
+ break;
+ }
+ if (pkt != nullptr) {
+ auto status =
+ esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, pkt->length,
+ pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
+ if (status)
+ ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(),
+ status);
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void Anova::set_unit_of_measurement(const char *unit) { this->fahrenheit_ = !strncmp(unit, "f", 1); }
+
+void Anova::update() {
+ if (this->node_state != espbt::ClientState::ESTABLISHED)
+ return;
+
+ if (this->current_request_ < 2) {
+ auto pkt = this->codec_->get_read_device_status_request();
+ if (this->current_request_ == 0)
+ this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c');
+ auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
+ pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
+ if (status)
+ ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
+ this->current_request_++;
+ }
+}
+
+} // namespace anova
+} // namespace esphome
+
+#endif
diff --git a/esphome/components/anova/anova.h b/esphome/components/anova/anova.h
new file mode 100644
index 0000000000..4f8f0d0ee2
--- /dev/null
+++ b/esphome/components/anova/anova.h
@@ -0,0 +1,52 @@
+#pragma once
+
+#include "esphome/core/component.h"
+#include "esphome/components/ble_client/ble_client.h"
+#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
+#include "esphome/components/climate/climate.h"
+#include "anova_base.h"
+
+#ifdef USE_ESP32
+
+#include
+
+namespace esphome {
+namespace anova {
+
+namespace espbt = esphome::esp32_ble_tracker;
+
+static const uint16_t ANOVA_SERVICE_UUID = 0xFFE0;
+static const uint16_t ANOVA_CHARACTERISTIC_UUID = 0xFFE1;
+
+class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode, public PollingComponent {
+ public:
+ void setup() override;
+ void loop() override;
+ void update() override;
+ void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
+ esp_ble_gattc_cb_param_t *param) override;
+ void dump_config() override;
+ float get_setup_priority() const override { return setup_priority::DATA; }
+ climate::ClimateTraits traits() override {
+ auto traits = climate::ClimateTraits();
+ traits.set_supports_current_temperature(true);
+ traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::ClimateMode::CLIMATE_MODE_HEAT});
+ traits.set_visual_min_temperature(25.0);
+ traits.set_visual_max_temperature(100.0);
+ traits.set_visual_temperature_step(0.1);
+ return traits;
+ }
+ void set_unit_of_measurement(const char *);
+
+ protected:
+ std::unique_ptr codec_;
+ void control(const climate::ClimateCall &call) override;
+ uint16_t char_handle_;
+ uint8_t current_request_;
+ bool fahrenheit_;
+};
+
+} // namespace anova
+} // namespace esphome
+
+#endif
diff --git a/esphome/components/anova/anova_base.cpp b/esphome/components/anova/anova_base.cpp
new file mode 100644
index 0000000000..ce4febbe37
--- /dev/null
+++ b/esphome/components/anova/anova_base.cpp
@@ -0,0 +1,134 @@
+#include "anova_base.h"
+#include
+#include
+
+namespace esphome {
+namespace anova {
+
+float ftoc(float f) { return (f - 32.0) * (5.0f / 9.0f); }
+
+float ctof(float c) { return (c * 9.0f / 5.0f) + 32.0; }
+
+AnovaPacket *AnovaCodec::clean_packet_() {
+ this->packet_.length = strlen((char *) this->packet_.data);
+ this->packet_.data[this->packet_.length] = '\0';
+ ESP_LOGV("anova", "SendPkt: %s\n", this->packet_.data);
+ return &this->packet_;
+}
+
+AnovaPacket *AnovaCodec::get_read_device_status_request() {
+ this->current_query_ = READ_DEVICE_STATUS;
+ sprintf((char *) this->packet_.data, "%s", CMD_READ_DEVICE_STATUS);
+ return this->clean_packet_();
+}
+
+AnovaPacket *AnovaCodec::get_read_target_temp_request() {
+ this->current_query_ = READ_TARGET_TEMPERATURE;
+ sprintf((char *) this->packet_.data, "%s", CMD_READ_TARGET_TEMP);
+ return this->clean_packet_();
+}
+
+AnovaPacket *AnovaCodec::get_read_current_temp_request() {
+ this->current_query_ = READ_CURRENT_TEMPERATURE;
+ sprintf((char *) this->packet_.data, "%s", CMD_READ_CURRENT_TEMP);
+ return this->clean_packet_();
+}
+
+AnovaPacket *AnovaCodec::get_read_unit_request() {
+ this->current_query_ = READ_UNIT;
+ sprintf((char *) this->packet_.data, "%s", CMD_READ_UNIT);
+ return this->clean_packet_();
+}
+
+AnovaPacket *AnovaCodec::get_read_data_request() {
+ this->current_query_ = READ_DATA;
+ sprintf((char *) this->packet_.data, "%s", CMD_READ_DATA);
+ return this->clean_packet_();
+}
+
+AnovaPacket *AnovaCodec::get_set_target_temp_request(float temperature) {
+ this->current_query_ = SET_TARGET_TEMPERATURE;
+ if (this->fahrenheit_)
+ temperature = ctof(temperature);
+ sprintf((char *) this->packet_.data, CMD_SET_TARGET_TEMP, temperature);
+ return this->clean_packet_();
+}
+
+AnovaPacket *AnovaCodec::get_set_unit_request(char unit) {
+ this->current_query_ = SET_UNIT;
+ sprintf((char *) this->packet_.data, CMD_SET_TEMP_UNIT, unit);
+ return this->clean_packet_();
+}
+
+AnovaPacket *AnovaCodec::get_start_request() {
+ this->current_query_ = START;
+ sprintf((char *) this->packet_.data, CMD_START);
+ return this->clean_packet_();
+}
+
+AnovaPacket *AnovaCodec::get_stop_request() {
+ this->current_query_ = STOP;
+ sprintf((char *) this->packet_.data, CMD_STOP);
+ return this->clean_packet_();
+}
+
+void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
+ char buf[32];
+ memset(buf, 0, sizeof(buf));
+ strncpy(buf, (char *) data, std::min(length, sizeof(buf) - 1));
+ this->has_target_temp_ = this->has_current_temp_ = this->has_unit_ = this->has_running_ = false;
+ switch (this->current_query_) {
+ case READ_DEVICE_STATUS: {
+ if (!strncmp(buf, "stopped", 7)) {
+ this->has_running_ = true;
+ this->running_ = false;
+ }
+ if (!strncmp(buf, "running", 7)) {
+ this->has_running_ = true;
+ this->running_ = true;
+ }
+ break;
+ }
+ case START: {
+ if (!strncmp(buf, "start", 5)) {
+ this->has_running_ = true;
+ this->running_ = true;
+ }
+ break;
+ }
+ case STOP: {
+ if (!strncmp(buf, "stop", 4)) {
+ this->has_running_ = true;
+ this->running_ = false;
+ }
+ break;
+ }
+ case READ_TARGET_TEMPERATURE:
+ case SET_TARGET_TEMPERATURE: {
+ this->target_temp_ = parse_number(str_until(buf, '\r')).value_or(0.0f);
+ if (this->fahrenheit_)
+ this->target_temp_ = ftoc(this->target_temp_);
+ this->has_target_temp_ = true;
+ break;
+ }
+ case READ_CURRENT_TEMPERATURE: {
+ this->current_temp_ = parse_number(str_until(buf, '\r')).value_or(0.0f);
+ if (this->fahrenheit_)
+ this->current_temp_ = ftoc(this->current_temp_);
+ this->has_current_temp_ = true;
+ break;
+ }
+ case SET_UNIT:
+ case READ_UNIT: {
+ this->unit_ = buf[0];
+ this->fahrenheit_ = buf[0] == 'f';
+ this->has_unit_ = true;
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+} // namespace anova
+} // namespace esphome
diff --git a/esphome/components/anova/anova_base.h b/esphome/components/anova/anova_base.h
new file mode 100644
index 0000000000..b831157849
--- /dev/null
+++ b/esphome/components/anova/anova_base.h
@@ -0,0 +1,79 @@
+#pragma once
+
+#include "esphome/core/helpers.h"
+#include "esphome/core/log.h"
+
+namespace esphome {
+namespace anova {
+
+enum CurrentQuery {
+ NONE,
+ READ_DEVICE_STATUS,
+ READ_TARGET_TEMPERATURE,
+ READ_CURRENT_TEMPERATURE,
+ READ_DATA,
+ READ_UNIT,
+ SET_TARGET_TEMPERATURE,
+ SET_UNIT,
+ START,
+ STOP,
+};
+
+struct AnovaPacket {
+ uint16_t length;
+ uint8_t data[24];
+};
+
+#define CMD_READ_DEVICE_STATUS "status\r"
+#define CMD_READ_TARGET_TEMP "read set temp\r"
+#define CMD_READ_CURRENT_TEMP "read temp\r"
+#define CMD_READ_UNIT "read unit\r"
+#define CMD_READ_DATA "read data\r"
+#define CMD_SET_TARGET_TEMP "set temp %.1f\r"
+#define CMD_SET_TEMP_UNIT "set unit %c\r"
+
+#define CMD_START "start\r"
+#define CMD_STOP "stop\r"
+
+class AnovaCodec {
+ public:
+ AnovaPacket *get_read_device_status_request();
+ AnovaPacket *get_read_target_temp_request();
+ AnovaPacket *get_read_current_temp_request();
+ AnovaPacket *get_read_data_request();
+ AnovaPacket *get_read_unit_request();
+
+ AnovaPacket *get_set_target_temp_request(float temperature);
+ AnovaPacket *get_set_unit_request(char unit);
+
+ AnovaPacket *get_start_request();
+ AnovaPacket *get_stop_request();
+
+ void decode(const uint8_t *data, uint16_t length);
+ bool has_target_temp() { return this->has_target_temp_; }
+ bool has_current_temp() { return this->has_current_temp_; }
+ bool has_unit() { return this->has_unit_; }
+ bool has_running() { return this->has_running_; }
+
+ union {
+ float target_temp_;
+ float current_temp_;
+ char unit_;
+ bool running_;
+ };
+
+ protected:
+ AnovaPacket *clean_packet_();
+ AnovaPacket packet_;
+
+ bool has_target_temp_;
+ bool has_current_temp_;
+ bool has_unit_;
+ bool has_running_;
+ bool fahrenheit_;
+
+ CurrentQuery current_query_;
+};
+
+} // namespace anova
+} // namespace esphome
diff --git a/esphome/components/anova/climate.py b/esphome/components/anova/climate.py
new file mode 100644
index 0000000000..bdd77d6a33
--- /dev/null
+++ b/esphome/components/anova/climate.py
@@ -0,0 +1,36 @@
+import esphome.codegen as cg
+import esphome.config_validation as cv
+from esphome.components import climate, ble_client
+from esphome.const import CONF_ID, CONF_UNIT_OF_MEASUREMENT
+
+UNITS = {
+ "f": "f",
+ "c": "c",
+}
+
+CODEOWNERS = ["@buxtronix"]
+DEPENDENCIES = ["ble_client"]
+
+anova_ns = cg.esphome_ns.namespace("anova")
+Anova = anova_ns.class_(
+ "Anova", climate.Climate, ble_client.BLEClientNode, cg.PollingComponent
+)
+
+CONFIG_SCHEMA = (
+ climate.CLIMATE_SCHEMA.extend(
+ {
+ cv.GenerateID(): cv.declare_id(Anova),
+ cv.Required(CONF_UNIT_OF_MEASUREMENT): cv.enum(UNITS),
+ }
+ )
+ .extend(ble_client.BLE_CLIENT_SCHEMA)
+ .extend(cv.polling_component_schema("60s"))
+)
+
+
+async def to_code(config):
+ var = cg.new_Pvariable(config[CONF_ID])
+ await cg.register_component(var, config)
+ await climate.register_climate(var, config)
+ await ble_client.register_ble_node(var, config)
+ cg.add(var.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT]))
diff --git a/esphome/components/apds9960/__init__.py b/esphome/components/apds9960/__init__.py
index 4725c16032..8de83251b7 100644
--- a/esphome/components/apds9960/__init__.py
+++ b/esphome/components/apds9960/__init__.py
@@ -3,21 +3,27 @@ import esphome.config_validation as cv
from esphome.components import i2c
from esphome.const import CONF_ID
-DEPENDENCIES = ['i2c']
-AUTO_LOAD = ['sensor', 'binary_sensor']
+DEPENDENCIES = ["i2c"]
+AUTO_LOAD = ["sensor", "binary_sensor"]
MULTI_CONF = True
-CONF_APDS9960_ID = 'apds9960_id'
+CONF_APDS9960_ID = "apds9960_id"
-apds9960_nds = cg.esphome_ns.namespace('apds9960')
-APDS9960 = apds9960_nds.class_('APDS9960', cg.PollingComponent, i2c.I2CDevice)
+apds9960_nds = cg.esphome_ns.namespace("apds9960")
+APDS9960 = apds9960_nds.class_("APDS9960", cg.PollingComponent, i2c.I2CDevice)
-CONFIG_SCHEMA = cv.Schema({
- cv.GenerateID(): cv.declare_id(APDS9960),
-}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x39))
+CONFIG_SCHEMA = (
+ cv.Schema(
+ {
+ cv.GenerateID(): cv.declare_id(APDS9960),
+ }
+ )
+ .extend(cv.polling_component_schema("60s"))
+ .extend(i2c.i2c_device_schema(0x39))
+)
-def to_code(config):
+async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
- yield cg.register_component(var, config)
- yield i2c.register_i2c_device(var, config)
+ await cg.register_component(var, config)
+ await i2c.register_i2c_device(var, config)
diff --git a/esphome/components/apds9960/apds9960.cpp b/esphome/components/apds9960/apds9960.cpp
index 2e09d11182..9ee873ac64 100644
--- a/esphome/components/apds9960/apds9960.cpp
+++ b/esphome/components/apds9960/apds9960.cpp
@@ -1,13 +1,14 @@
#include "apds9960.h"
#include "esphome/core/log.h"
+#include "esphome/core/hal.h"
namespace esphome {
namespace apds9960 {
-static const char *TAG = "apds9960";
+static const char *const TAG = "apds9960";
#define APDS9960_ERROR_CHECK(func) \
- if (!func) { \
+ if (!(func)) { \
this->mark_failed(); \
return; \
}
diff --git a/esphome/components/apds9960/binary_sensor.py b/esphome/components/apds9960/binary_sensor.py
index 4404510909..4a5c69f6a9 100644
--- a/esphome/components/apds9960/binary_sensor.py
+++ b/esphome/components/apds9960/binary_sensor.py
@@ -4,24 +4,28 @@ 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']
+DEPENDENCIES = ["apds9960"]
DIRECTIONS = {
- 'UP': 'set_up_direction',
- 'DOWN': 'set_down_direction',
- 'LEFT': 'set_left_direction',
- 'RIGHT': 'set_right_direction',
+ "UP": "set_up_direction",
+ "DOWN": "set_down_direction",
+ "LEFT": "set_left_direction",
+ "RIGHT": "set_right_direction",
}
-CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({
- cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True),
- cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
- cv.Optional(CONF_DEVICE_CLASS, default=DEVICE_CLASS_MOVING): binary_sensor.device_class,
-})
+CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
+ {
+ cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True),
+ cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
+ cv.Optional(
+ CONF_DEVICE_CLASS, default=DEVICE_CLASS_MOVING
+ ): binary_sensor.device_class,
+ }
+)
-def to_code(config):
- hub = yield cg.get_variable(config[CONF_APDS9960_ID])
- var = yield binary_sensor.new_binary_sensor(config)
+async def to_code(config):
+ hub = await cg.get_variable(config[CONF_APDS9960_ID])
+ var = await binary_sensor.new_binary_sensor(config)
func = getattr(hub, DIRECTIONS[config[CONF_DIRECTION]])
cg.add(func(var))
diff --git a/esphome/components/apds9960/sensor.py b/esphome/components/apds9960/sensor.py
index 58087cbe86..e1990ec26e 100644
--- a/esphome/components/apds9960/sensor.py
+++ b/esphome/components/apds9960/sensor.py
@@ -1,27 +1,39 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
-from esphome.const import CONF_TYPE, UNIT_PERCENT, ICON_LIGHTBULB
+from esphome.const import (
+ CONF_TYPE,
+ STATE_CLASS_MEASUREMENT,
+ UNIT_PERCENT,
+ ICON_LIGHTBULB,
+)
from . import APDS9960, CONF_APDS9960_ID
-DEPENDENCIES = ['apds9960']
+DEPENDENCIES = ["apds9960"]
TYPES = {
- 'CLEAR': 'set_clear_channel',
- 'RED': 'set_red_channel',
- 'GREEN': 'set_green_channel',
- 'BLUE': 'set_blue_channel',
- 'PROXIMITY': 'set_proximity',
+ "CLEAR": "set_clear_channel",
+ "RED": "set_red_channel",
+ "GREEN": "set_green_channel",
+ "BLUE": "set_blue_channel",
+ "PROXIMITY": "set_proximity",
}
-CONFIG_SCHEMA = sensor.sensor_schema(UNIT_PERCENT, ICON_LIGHTBULB, 1).extend({
- cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
- cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
-})
+CONFIG_SCHEMA = sensor.sensor_schema(
+ unit_of_measurement=UNIT_PERCENT,
+ icon=ICON_LIGHTBULB,
+ accuracy_decimals=1,
+ state_class=STATE_CLASS_MEASUREMENT,
+).extend(
+ {
+ cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
+ cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
+ }
+)
-def to_code(config):
- hub = yield cg.get_variable(config[CONF_APDS9960_ID])
- var = yield sensor.new_sensor(config)
+async def to_code(config):
+ hub = await cg.get_variable(config[CONF_APDS9960_ID])
+ var = await sensor.new_sensor(config)
func = getattr(hub, TYPES[config[CONF_TYPE]])
cg.add(func(var))
diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py
index eef60602ba..6b2e7fd06b 100644
--- a/esphome/components/api/__init__.py
+++ b/esphome/components/api/__init__.py
@@ -1,51 +1,100 @@
+import base64
+
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import Condition
-from esphome.const import CONF_DATA, CONF_DATA_TEMPLATE, CONF_ID, CONF_PASSWORD, CONF_PORT, \
- CONF_REBOOT_TIMEOUT, CONF_SERVICE, CONF_VARIABLES, CONF_SERVICES, CONF_TRIGGER_ID, CONF_EVENT
+from esphome.const import (
+ CONF_DATA,
+ CONF_DATA_TEMPLATE,
+ CONF_ID,
+ CONF_KEY,
+ CONF_PASSWORD,
+ CONF_PORT,
+ CONF_REBOOT_TIMEOUT,
+ CONF_SERVICE,
+ CONF_VARIABLES,
+ CONF_SERVICES,
+ CONF_TRIGGER_ID,
+ CONF_EVENT,
+ CONF_TAG,
+)
from esphome.core import coroutine_with_priority
-DEPENDENCIES = ['network']
-AUTO_LOAD = ['async_tcp']
+DEPENDENCIES = ["network"]
+AUTO_LOAD = ["socket"]
+CODEOWNERS = ["@OttoWinter"]
-api_ns = cg.esphome_ns.namespace('api')
-APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller)
-HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', automation.Action)
-APIConnectedCondition = api_ns.class_('APIConnectedCondition', Condition)
+api_ns = cg.esphome_ns.namespace("api")
+APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
+HomeAssistantServiceCallAction = api_ns.class_(
+ "HomeAssistantServiceCallAction", automation.Action
+)
+APIConnectedCondition = api_ns.class_("APIConnectedCondition", Condition)
-UserServiceTrigger = api_ns.class_('UserServiceTrigger', automation.Trigger)
-ListEntitiesServicesArgument = api_ns.class_('ListEntitiesServicesArgument')
+UserServiceTrigger = api_ns.class_("UserServiceTrigger", automation.Trigger)
+ListEntitiesServicesArgument = api_ns.class_("ListEntitiesServicesArgument")
SERVICE_ARG_NATIVE_TYPES = {
- 'bool': bool,
- 'int': cg.int32,
- 'float': float,
- 'string': cg.std_string,
- 'bool[]': cg.std_vector.template(bool),
- 'int[]': cg.std_vector.template(cg.int32),
- 'float[]': cg.std_vector.template(float),
- 'string[]': cg.std_vector.template(cg.std_string),
+ "bool": bool,
+ "int": cg.int32,
+ "float": float,
+ "string": cg.std_string,
+ "bool[]": cg.std_vector.template(bool),
+ "int[]": cg.std_vector.template(cg.int32),
+ "float[]": cg.std_vector.template(float),
+ "string[]": cg.std_vector.template(cg.std_string),
}
+CONF_ENCRYPTION = "encryption"
-CONFIG_SCHEMA = cv.Schema({
- cv.GenerateID(): cv.declare_id(APIServer),
- cv.Optional(CONF_PORT, default=6053): cv.port,
- cv.Optional(CONF_PASSWORD, default=''): cv.string_strict,
- cv.Optional(CONF_REBOOT_TIMEOUT, default='15min'): cv.positive_time_period_milliseconds,
- cv.Optional(CONF_SERVICES): automation.validate_automation({
- cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
- cv.Required(CONF_SERVICE): cv.valid_name,
- cv.Optional(CONF_VARIABLES, default={}): cv.Schema({
- cv.validate_id_name: cv.one_of(*SERVICE_ARG_NATIVE_TYPES, lower=True),
- }),
- }),
-}).extend(cv.COMPONENT_SCHEMA)
+
+def validate_encryption_key(value):
+ value = cv.string_strict(value)
+ try:
+ decoded = base64.b64decode(value, validate=True)
+ except ValueError as err:
+ raise cv.Invalid("Invalid key format, please check it's using base64") from err
+
+ if len(decoded) != 32:
+ raise cv.Invalid("Encryption key must be base64 and 32 bytes long")
+
+ # Return original data for roundtrip conversion
+ return value
+
+
+CONFIG_SCHEMA = cv.Schema(
+ {
+ cv.GenerateID(): cv.declare_id(APIServer),
+ cv.Optional(CONF_PORT, default=6053): cv.port,
+ cv.Optional(CONF_PASSWORD, default=""): cv.string_strict,
+ cv.Optional(
+ CONF_REBOOT_TIMEOUT, default="15min"
+ ): cv.positive_time_period_milliseconds,
+ cv.Optional(CONF_SERVICES): automation.validate_automation(
+ {
+ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
+ cv.Required(CONF_SERVICE): cv.valid_name,
+ cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
+ {
+ cv.validate_id_name: cv.one_of(
+ *SERVICE_ARG_NATIVE_TYPES, lower=True
+ ),
+ }
+ ),
+ }
+ ),
+ cv.Optional(CONF_ENCRYPTION): cv.Schema(
+ {
+ cv.Required(CONF_KEY): validate_encryption_key,
+ }
+ ),
+ }
+).extend(cv.COMPONENT_SCHEMA)
@coroutine_with_priority(40.0)
-def to_code(config):
+async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
- yield cg.register_component(var, config)
+ await cg.register_component(var, config)
cg.add(var.set_port(config[CONF_PORT]))
cg.add(var.set_password(config[CONF_PASSWORD]))
@@ -61,81 +110,128 @@ def to_code(config):
func_args.append((native, name))
service_arg_names.append(name)
templ = cg.TemplateArguments(*template_args)
- trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], templ,
- conf[CONF_SERVICE], service_arg_names)
+ trigger = cg.new_Pvariable(
+ conf[CONF_TRIGGER_ID], templ, conf[CONF_SERVICE], service_arg_names
+ )
cg.add(var.register_user_service(trigger))
- yield automation.build_automation(trigger, func_args, conf)
+ await automation.build_automation(trigger, func_args, conf)
- cg.add_define('USE_API')
+ if CONF_ENCRYPTION in config:
+ conf = config[CONF_ENCRYPTION]
+ decoded = base64.b64decode(conf[CONF_KEY])
+ cg.add(var.set_noise_psk(list(decoded)))
+ cg.add_define("USE_API_NOISE")
+ cg.add_library("esphome/noise-c", "0.1.4")
+ else:
+ cg.add_define("USE_API_PLAINTEXT")
+
+ cg.add_define("USE_API")
cg.add_global(api_ns.using)
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)})
-HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({
- cv.GenerateID(): cv.use_id(APIServer),
- cv.Required(CONF_SERVICE): cv.templatable(cv.string),
- cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
- cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
- cv.Optional(CONF_VARIABLES, default={}): cv.Schema({cv.string: cv.returning_lambda}),
-})
+HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema(
+ {
+ cv.GenerateID(): cv.use_id(APIServer),
+ cv.Required(CONF_SERVICE): cv.templatable(cv.string),
+ cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
+ cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
+ cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
+ {cv.string: cv.returning_lambda}
+ ),
+ }
+)
-@automation.register_action('homeassistant.service', HomeAssistantServiceCallAction,
- HOMEASSISTANT_SERVICE_ACTION_SCHEMA)
-def homeassistant_service_to_code(config, action_id, template_arg, args):
- serv = yield cg.get_variable(config[CONF_ID])
+@automation.register_action(
+ "homeassistant.service",
+ HomeAssistantServiceCallAction,
+ HOMEASSISTANT_SERVICE_ACTION_SCHEMA,
+)
+async def homeassistant_service_to_code(config, action_id, template_arg, args):
+ serv = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, serv, False)
- templ = yield cg.templatable(config[CONF_SERVICE], args, None)
+ templ = await cg.templatable(config[CONF_SERVICE], args, None)
cg.add(var.set_service(templ))
for key, value in config[CONF_DATA].items():
- templ = yield cg.templatable(value, args, None)
+ templ = await cg.templatable(value, args, None)
cg.add(var.add_data(key, templ))
for key, value in config[CONF_DATA_TEMPLATE].items():
- templ = yield cg.templatable(value, args, None)
+ templ = await cg.templatable(value, args, None)
cg.add(var.add_data_template(key, templ))
for key, value in config[CONF_VARIABLES].items():
- templ = yield cg.templatable(value, args, None)
+ templ = await cg.templatable(value, args, None)
cg.add(var.add_variable(key, templ))
- yield var
+ return var
def validate_homeassistant_event(value):
value = cv.string(value)
- if not value.startswith('esphome.'):
- raise cv.Invalid("ESPHome can only generate Home Assistant events that begin with "
- "esphome. For example 'esphome.xyz'")
+ if not value.startswith("esphome."):
+ raise cv.Invalid(
+ "ESPHome can only generate Home Assistant events that begin with "
+ "esphome. For example 'esphome.xyz'"
+ )
return value
-HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema({
- cv.GenerateID(): cv.use_id(APIServer),
- cv.Required(CONF_EVENT): validate_homeassistant_event,
- cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
- cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
- cv.Optional(CONF_VARIABLES, default={}): KEY_VALUE_SCHEMA,
-})
+HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema(
+ {
+ cv.GenerateID(): cv.use_id(APIServer),
+ cv.Required(CONF_EVENT): validate_homeassistant_event,
+ cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
+ cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
+ cv.Optional(CONF_VARIABLES, default={}): KEY_VALUE_SCHEMA,
+ }
+)
-@automation.register_action('homeassistant.event', HomeAssistantServiceCallAction,
- HOMEASSISTANT_EVENT_ACTION_SCHEMA)
-def homeassistant_event_to_code(config, action_id, template_arg, args):
- serv = yield cg.get_variable(config[CONF_ID])
+@automation.register_action(
+ "homeassistant.event",
+ HomeAssistantServiceCallAction,
+ HOMEASSISTANT_EVENT_ACTION_SCHEMA,
+)
+async def homeassistant_event_to_code(config, action_id, template_arg, args):
+ serv = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, serv, True)
- templ = yield cg.templatable(config[CONF_EVENT], args, None)
+ templ = await cg.templatable(config[CONF_EVENT], args, None)
cg.add(var.set_service(templ))
for key, value in config[CONF_DATA].items():
- templ = yield cg.templatable(value, args, None)
+ templ = await cg.templatable(value, args, None)
cg.add(var.add_data(key, templ))
for key, value in config[CONF_DATA_TEMPLATE].items():
- templ = yield cg.templatable(value, args, None)
+ templ = await cg.templatable(value, args, None)
cg.add(var.add_data_template(key, templ))
for key, value in config[CONF_VARIABLES].items():
- templ = yield cg.templatable(value, args, None)
+ templ = await cg.templatable(value, args, None)
cg.add(var.add_variable(key, templ))
- yield var
+ return var
-@automation.register_condition('api.connected', APIConnectedCondition, {})
-def api_connected_to_code(config, condition_id, template_arg, args):
- yield cg.new_Pvariable(condition_id, template_arg)
+HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value(
+ {
+ cv.GenerateID(): cv.use_id(APIServer),
+ cv.Required(CONF_TAG): cv.templatable(cv.string_strict),
+ },
+ key=CONF_TAG,
+)
+
+
+@automation.register_action(
+ "homeassistant.tag_scanned",
+ HomeAssistantServiceCallAction,
+ HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
+)
+async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
+ serv = await cg.get_variable(config[CONF_ID])
+ var = cg.new_Pvariable(action_id, template_arg, serv, True)
+ cg.add(var.set_service("esphome.tag_scanned"))
+ templ = await cg.templatable(config[CONF_TAG], args, cg.std_string)
+ cg.add(var.add_data("tag_id", templ))
+ return var
+
+
+@automation.register_condition("api.connected", APIConnectedCondition, {})
+async def api_connected_to_code(config, condition_id, template_arg, args):
+ return cg.new_Pvariable(condition_id, template_arg)
diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto
index 4bb7d1b555..dca722dca5 100644
--- a/esphome/components/api/api.proto
+++ b/esphome/components/api/api.proto
@@ -38,6 +38,9 @@ service APIConnection {
rpc switch_command (SwitchCommandRequest) returns (void) {}
rpc camera_image (CameraImageRequest) returns (void) {}
rpc climate_command (ClimateCommandRequest) returns (void) {}
+ rpc number_command (NumberCommandRequest) returns (void) {}
+ rpc select_command (SelectCommandRequest) returns (void) {}
+ rpc button_command (ButtonCommandRequest) returns (void) {}
}
@@ -46,6 +49,7 @@ service APIConnection {
// The Home Assistant protocol is structured as a simple
// TCP socket with short binary messages encoded in the protocol buffers format
// First, a message in this protocol has a specific format:
+// * A zero byte.
// * VarInt denoting the size of the message object. (type is not part of this)
// * VarInt denoting the type of message.
// * The message object encoded as a ProtoBuf message
@@ -175,6 +179,12 @@ message DeviceInfoResponse {
string model = 6;
bool has_deep_sleep = 7;
+
+ // The esphome project details if set
+ string project_name = 8;
+ string project_version = 9;
+
+ uint32 webserver_port = 10;
}
message ListEntitiesRequest {
@@ -194,6 +204,14 @@ message SubscribeStatesRequest {
// Empty
}
+// ==================== COMMON =====================
+
+enum EntityCategory {
+ ENTITY_CATEGORY_NONE = 0;
+ ENTITY_CATEGORY_CONFIG = 1;
+ ENTITY_CATEGORY_DIAGNOSTIC = 2;
+}
+
// ==================== BINARY SENSOR ====================
message ListEntitiesBinarySensorResponse {
option (id) = 12;
@@ -207,6 +225,9 @@ message ListEntitiesBinarySensorResponse {
string device_class = 5;
bool is_status_binary_sensor = 6;
+ bool disabled_by_default = 7;
+ string icon = 8;
+ EntityCategory entity_category = 9;
}
message BinarySensorStateResponse {
option (id) = 21;
@@ -236,6 +257,9 @@ message ListEntitiesCoverResponse {
bool supports_position = 6;
bool supports_tilt = 7;
string device_class = 8;
+ bool disabled_by_default = 9;
+ string icon = 10;
+ EntityCategory entity_category = 11;
}
enum LegacyCoverState {
@@ -301,12 +325,21 @@ message ListEntitiesFanResponse {
bool supports_oscillation = 5;
bool supports_speed = 6;
+ bool supports_direction = 7;
+ int32 supported_speed_count = 8;
+ bool disabled_by_default = 9;
+ string icon = 10;
+ EntityCategory entity_category = 11;
}
enum FanSpeed {
FAN_SPEED_LOW = 0;
FAN_SPEED_MEDIUM = 1;
FAN_SPEED_HIGH = 2;
}
+enum FanDirection {
+ FAN_DIRECTION_FORWARD = 0;
+ FAN_DIRECTION_REVERSE = 1;
+}
message FanStateResponse {
option (id) = 23;
option (source) = SOURCE_SERVER;
@@ -316,7 +349,9 @@ message FanStateResponse {
fixed32 key = 1;
bool state = 2;
bool oscillating = 3;
- FanSpeed speed = 4;
+ FanSpeed speed = 4 [deprecated = true];
+ FanDirection direction = 5;
+ int32 speed_level = 6;
}
message FanCommandRequest {
option (id) = 31;
@@ -327,13 +362,29 @@ message FanCommandRequest {
fixed32 key = 1;
bool has_state = 2;
bool state = 3;
- bool has_speed = 4;
- FanSpeed speed = 5;
+ bool has_speed = 4 [deprecated = true];
+ FanSpeed speed = 5 [deprecated = true];
bool has_oscillating = 6;
bool oscillating = 7;
+ bool has_direction = 8;
+ FanDirection direction = 9;
+ bool has_speed_level = 10;
+ int32 speed_level = 11;
}
// ==================== LIGHT ====================
+enum ColorMode {
+ COLOR_MODE_UNKNOWN = 0;
+ COLOR_MODE_ON_OFF = 1;
+ COLOR_MODE_BRIGHTNESS = 2;
+ COLOR_MODE_WHITE = 7;
+ COLOR_MODE_COLOR_TEMPERATURE = 11;
+ COLOR_MODE_COLD_WARM_WHITE = 19;
+ COLOR_MODE_RGB = 35;
+ COLOR_MODE_RGB_WHITE = 39;
+ COLOR_MODE_RGB_COLOR_TEMPERATURE = 47;
+ COLOR_MODE_RGB_COLD_WARM_WHITE = 51;
+}
message ListEntitiesLightResponse {
option (id) = 15;
option (source) = SOURCE_SERVER;
@@ -344,13 +395,18 @@ message ListEntitiesLightResponse {
string name = 3;
string unique_id = 4;
- bool supports_brightness = 5;
- bool supports_rgb = 6;
- bool supports_white_value = 7;
- bool supports_color_temperature = 8;
+ repeated ColorMode supported_color_modes = 12;
+ // next four supports_* are for legacy clients, newer clients should use color modes
+ bool legacy_supports_brightness = 5 [deprecated=true];
+ bool legacy_supports_rgb = 6 [deprecated=true];
+ bool legacy_supports_white_value = 7 [deprecated=true];
+ bool legacy_supports_color_temperature = 8 [deprecated=true];
float min_mireds = 9;
float max_mireds = 10;
repeated string effects = 11;
+ bool disabled_by_default = 13;
+ string icon = 14;
+ EntityCategory entity_category = 15;
}
message LightStateResponse {
option (id) = 24;
@@ -361,11 +417,15 @@ message LightStateResponse {
fixed32 key = 1;
bool state = 2;
float brightness = 3;
+ ColorMode color_mode = 11;
+ float color_brightness = 10;
float red = 4;
float green = 5;
float blue = 6;
float white = 7;
float color_temperature = 8;
+ float cold_white = 12;
+ float warm_white = 13;
string effect = 9;
}
message LightCommandRequest {
@@ -379,6 +439,10 @@ message LightCommandRequest {
bool state = 3;
bool has_brightness = 4;
float brightness = 5;
+ bool has_color_mode = 22;
+ ColorMode color_mode = 23;
+ bool has_color_brightness = 20;
+ float color_brightness = 21;
bool has_rgb = 6;
float red = 7;
float green = 8;
@@ -387,6 +451,10 @@ message LightCommandRequest {
float white = 11;
bool has_color_temperature = 12;
float color_temperature = 13;
+ bool has_cold_white = 24;
+ float cold_white = 25;
+ bool has_warm_white = 26;
+ float warm_white = 27;
bool has_transition_length = 14;
uint32 transition_length = 15;
bool has_flash_length = 16;
@@ -396,6 +464,18 @@ message LightCommandRequest {
}
// ==================== SENSOR ====================
+enum SensorStateClass {
+ STATE_CLASS_NONE = 0;
+ STATE_CLASS_MEASUREMENT = 1;
+ STATE_CLASS_TOTAL_INCREASING = 2;
+}
+
+enum SensorLastResetType {
+ LAST_RESET_NONE = 0;
+ LAST_RESET_NEVER = 1;
+ LAST_RESET_AUTO = 2;
+}
+
message ListEntitiesSensorResponse {
option (id) = 16;
option (source) = SOURCE_SERVER;
@@ -410,6 +490,12 @@ message ListEntitiesSensorResponse {
string unit_of_measurement = 6;
int32 accuracy_decimals = 7;
bool force_update = 8;
+ string device_class = 9;
+ SensorStateClass state_class = 10;
+ // Last reset type removed in 2021.9.0
+ SensorLastResetType legacy_last_reset_type = 11;
+ bool disabled_by_default = 12;
+ EntityCategory entity_category = 13;
}
message SensorStateResponse {
option (id) = 25;
@@ -437,6 +523,8 @@ message ListEntitiesSwitchResponse {
string icon = 5;
bool assumed_state = 6;
+ bool disabled_by_default = 7;
+ EntityCategory entity_category = 8;
}
message SwitchStateResponse {
option (id) = 26;
@@ -469,6 +557,8 @@ message ListEntitiesTextSensorResponse {
string unique_id = 4;
string icon = 5;
+ bool disabled_by_default = 6;
+ EntityCategory entity_category = 7;
}
message TextSensorStateResponse {
option (id) = 27;
@@ -489,9 +579,10 @@ enum LogLevel {
LOG_LEVEL_ERROR = 1;
LOG_LEVEL_WARN = 2;
LOG_LEVEL_INFO = 3;
- LOG_LEVEL_DEBUG = 4;
- LOG_LEVEL_VERBOSE = 5;
- LOG_LEVEL_VERY_VERBOSE = 6;
+ LOG_LEVEL_CONFIG = 4;
+ LOG_LEVEL_DEBUG = 5;
+ LOG_LEVEL_VERBOSE = 6;
+ LOG_LEVEL_VERY_VERBOSE = 7;
}
message SubscribeLogsRequest {
option (id) = 28;
@@ -506,7 +597,6 @@ message SubscribeLogsResponse {
option (no_delay) = false;
LogLevel level = 1;
- string tag = 2;
string message = 3;
bool send_failed = 4;
}
@@ -547,6 +637,7 @@ message SubscribeHomeAssistantStateResponse {
option (id) = 39;
option (source) = SOURCE_SERVER;
string entity_id = 1;
+ string attribute = 2;
}
message HomeAssistantStateResponse {
@@ -556,6 +647,7 @@ message HomeAssistantStateResponse {
string entity_id = 1;
string state = 2;
+ string attribute = 3;
}
// ==================== IMPORT TIME ====================
@@ -626,6 +718,9 @@ message ListEntitiesCameraResponse {
fixed32 key = 2;
string name = 3;
string unique_id = 4;
+ bool disabled_by_default = 5;
+ string icon = 6;
+ EntityCategory entity_category = 7;
}
message CameraImageResponse {
@@ -650,11 +745,12 @@ message CameraImageRequest {
// ==================== CLIMATE ====================
enum ClimateMode {
CLIMATE_MODE_OFF = 0;
- CLIMATE_MODE_AUTO = 1;
+ CLIMATE_MODE_HEAT_COOL = 1;
CLIMATE_MODE_COOL = 2;
CLIMATE_MODE_HEAT = 3;
CLIMATE_MODE_FAN_ONLY = 4;
CLIMATE_MODE_DRY = 5;
+ CLIMATE_MODE_AUTO = 6;
}
enum ClimateFanMode {
CLIMATE_FAN_ON = 0;
@@ -671,7 +767,7 @@ enum ClimateSwingMode {
CLIMATE_SWING_OFF = 0;
CLIMATE_SWING_BOTH = 1;
CLIMATE_SWING_VERTICAL = 2;
- CLIMATE_SWINT_HORIZONTAL = 3;
+ CLIMATE_SWING_HORIZONTAL = 3;
}
enum ClimateAction {
CLIMATE_ACTION_OFF = 0;
@@ -682,6 +778,16 @@ enum ClimateAction {
CLIMATE_ACTION_DRYING = 5;
CLIMATE_ACTION_FAN = 6;
}
+enum ClimatePreset {
+ CLIMATE_PRESET_NONE = 0;
+ CLIMATE_PRESET_HOME = 1;
+ CLIMATE_PRESET_AWAY = 2;
+ CLIMATE_PRESET_BOOST = 3;
+ CLIMATE_PRESET_COMFORT = 4;
+ CLIMATE_PRESET_ECO = 5;
+ CLIMATE_PRESET_SLEEP = 6;
+ CLIMATE_PRESET_ACTIVITY = 7;
+}
message ListEntitiesClimateResponse {
option (id) = 46;
option (source) = SOURCE_SERVER;
@@ -698,10 +804,18 @@ message ListEntitiesClimateResponse {
float visual_min_temperature = 8;
float visual_max_temperature = 9;
float visual_temperature_step = 10;
- bool supports_away = 11;
+ // for older peer versions - in new system this
+ // is if CLIMATE_PRESET_AWAY exists is supported_presets
+ bool legacy_supports_away = 11;
bool supports_action = 12;
repeated ClimateFanMode supported_fan_modes = 13;
repeated ClimateSwingMode supported_swing_modes = 14;
+ repeated string supported_custom_fan_modes = 15;
+ repeated ClimatePreset supported_presets = 16;
+ repeated string supported_custom_presets = 17;
+ bool disabled_by_default = 18;
+ string icon = 19;
+ EntityCategory entity_category = 20;
}
message ClimateStateResponse {
option (id) = 47;
@@ -715,10 +829,14 @@ message ClimateStateResponse {
float target_temperature = 4;
float target_temperature_low = 5;
float target_temperature_high = 6;
- bool away = 7;
+ // For older peers, equal to preset == CLIMATE_PRESET_AWAY
+ bool legacy_away = 7;
ClimateAction action = 8;
ClimateFanMode fan_mode = 9;
ClimateSwingMode swing_mode = 10;
+ string custom_fan_mode = 11;
+ ClimatePreset preset = 12;
+ string custom_preset = 13;
}
message ClimateCommandRequest {
option (id) = 48;
@@ -735,10 +853,127 @@ message ClimateCommandRequest {
float target_temperature_low = 7;
bool has_target_temperature_high = 8;
float target_temperature_high = 9;
- bool has_away = 10;
- bool away = 11;
+ // legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset
+ bool has_legacy_away = 10;
+ bool legacy_away = 11;
bool has_fan_mode = 12;
ClimateFanMode fan_mode = 13;
bool has_swing_mode = 14;
ClimateSwingMode swing_mode = 15;
+ bool has_custom_fan_mode = 16;
+ string custom_fan_mode = 17;
+ bool has_preset = 18;
+ ClimatePreset preset = 19;
+ bool has_custom_preset = 20;
+ string custom_preset = 21;
+}
+
+// ==================== NUMBER ====================
+enum NumberMode {
+ NUMBER_MODE_AUTO = 0;
+ NUMBER_MODE_BOX = 1;
+ NUMBER_MODE_SLIDER = 2;
+}
+message ListEntitiesNumberResponse {
+ option (id) = 49;
+ option (source) = SOURCE_SERVER;
+ option (ifdef) = "USE_NUMBER";
+
+ string object_id = 1;
+ fixed32 key = 2;
+ string name = 3;
+ string unique_id = 4;
+
+ string icon = 5;
+ float min_value = 6;
+ float max_value = 7;
+ float step = 8;
+ bool disabled_by_default = 9;
+ EntityCategory entity_category = 10;
+ string unit_of_measurement = 11;
+ NumberMode mode = 12;
+}
+message NumberStateResponse {
+ option (id) = 50;
+ option (source) = SOURCE_SERVER;
+ option (ifdef) = "USE_NUMBER";
+ option (no_delay) = true;
+
+ fixed32 key = 1;
+ float state = 2;
+ // If the number does not have a valid state yet.
+ // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
+ bool missing_state = 3;
+}
+message NumberCommandRequest {
+ option (id) = 51;
+ option (source) = SOURCE_CLIENT;
+ option (ifdef) = "USE_NUMBER";
+ option (no_delay) = true;
+
+ fixed32 key = 1;
+ float state = 2;
+}
+
+// ==================== SELECT ====================
+message ListEntitiesSelectResponse {
+ option (id) = 52;
+ option (source) = SOURCE_SERVER;
+ option (ifdef) = "USE_SELECT";
+
+ string object_id = 1;
+ fixed32 key = 2;
+ string name = 3;
+ string unique_id = 4;
+
+ string icon = 5;
+ repeated string options = 6;
+ bool disabled_by_default = 7;
+ EntityCategory entity_category = 8;
+}
+message SelectStateResponse {
+ option (id) = 53;
+ option (source) = SOURCE_SERVER;
+ option (ifdef) = "USE_SELECT";
+ option (no_delay) = true;
+
+ fixed32 key = 1;
+ string state = 2;
+ // If the select does not have a valid state yet.
+ // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
+ bool missing_state = 3;
+}
+message SelectCommandRequest {
+ option (id) = 54;
+ option (source) = SOURCE_CLIENT;
+ option (ifdef) = "USE_SELECT";
+ option (no_delay) = true;
+
+ fixed32 key = 1;
+ string state = 2;
+}
+
+// ==================== BUTTON ====================
+message ListEntitiesButtonResponse {
+ option (id) = 61;
+ option (source) = SOURCE_SERVER;
+ option (ifdef) = "USE_BUTTON";
+
+ string object_id = 1;
+ fixed32 key = 2;
+ string name = 3;
+ string unique_id = 4;
+
+ string icon = 5;
+ bool disabled_by_default = 6;
+ EntityCategory entity_category = 7;
+ string device_class = 8;
+}
+message ButtonCommandRequest {
+ option (id) = 62;
+ option (source) = SOURCE_CLIENT;
+ option (ifdef) = "USE_BUTTON";
+ option (no_delay) = true;
+
+ fixed32 key = 1;
}
diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp
index beccf91d27..f615815023 100644
--- a/esphome/components/api/api_connection.cpp
+++ b/esphome/components/api/api_connection.cpp
@@ -1,7 +1,10 @@
#include "api_connection.h"
+#include "esphome/core/entity_base.h"
#include "esphome/core/log.h"
-#include "esphome/core/util.h"
+#include "esphome/components/network/util.h"
#include "esphome/core/version.h"
+#include "esphome/core/hal.h"
+#include
#ifdef USE_DEEP_SLEEP
#include "esphome/components/deep_sleep/deep_sleep_component.h"
@@ -9,149 +12,155 @@
#ifdef USE_HOMEASSISTANT_TIME
#include "esphome/components/homeassistant/time/homeassistant_time.h"
#endif
+#ifdef USE_FAN
+#include "esphome/components/fan/fan_helpers.h"
+#endif
namespace esphome {
namespace api {
-static const char *TAG = "api.connection";
+static const char *const TAG = "api.connection";
-APIConnection::APIConnection(AsyncClient *client, APIServer *parent)
- : client_(client), parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) {
- this->client_->onError([](void *s, AsyncClient *c, int8_t error) { ((APIConnection *) s)->on_error_(error); }, this);
- this->client_->onDisconnect([](void *s, AsyncClient *c) { ((APIConnection *) s)->on_disconnect_(); }, this);
- this->client_->onTimeout([](void *s, AsyncClient *c, uint32_t time) { ((APIConnection *) s)->on_timeout_(time); },
- this);
- this->client_->onData([](void *s, AsyncClient *c, void *buf,
- size_t len) { ((APIConnection *) s)->on_data_(reinterpret_cast(buf), len); },
- this);
+APIConnection::APIConnection(std::unique_ptr sock, APIServer *parent)
+ : parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) {
+ this->proto_write_buffer_.reserve(64);
- this->send_buffer_.reserve(64);
- this->recv_buffer_.reserve(32);
- this->client_info_ = this->client_->remoteIP().toString().c_str();
+#if defined(USE_API_PLAINTEXT)
+ helper_ = std::unique_ptr{new APIPlaintextFrameHelper(std::move(sock))};
+#elif defined(USE_API_NOISE)
+ helper_ = std::unique_ptr{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())};
+#else
+#error "No frame helper defined"
+#endif
+}
+void APIConnection::start() {
this->last_traffic_ = millis();
-}
-APIConnection::~APIConnection() { delete this->client_; }
-void APIConnection::on_error_(int8_t error) { this->remove_ = true; }
-void APIConnection::on_disconnect_() { this->remove_ = true; }
-void APIConnection::on_timeout_(uint32_t time) { this->on_fatal_error(); }
-void APIConnection::on_data_(uint8_t *buf, size_t len) {
- if (len == 0 || buf == nullptr)
+
+ APIError err = helper_->init();
+ if (err != APIError::OK) {
+ on_fatal_error();
+ ESP_LOGW(TAG, "%s: Helper init failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
return;
- this->recv_buffer_.insert(this->recv_buffer_.end(), buf, buf + len);
-}
-void APIConnection::parse_recv_buffer_() {
- if (this->recv_buffer_.empty() || this->remove_)
- return;
-
- while (!this->recv_buffer_.empty()) {
- if (this->recv_buffer_[0] != 0x00) {
- ESP_LOGW(TAG, "Invalid preamble from %s", this->client_info_.c_str());
- this->on_fatal_error();
- return;
- }
- uint32_t i = 1;
- const uint32_t size = this->recv_buffer_.size();
- uint32_t consumed;
- auto msg_size_varint = ProtoVarInt::parse(&this->recv_buffer_[i], size - i, &consumed);
- if (!msg_size_varint.has_value())
- // not enough data there yet
- return;
- i += consumed;
- uint32_t msg_size = msg_size_varint->as_uint32();
-
- auto msg_type_varint = ProtoVarInt::parse(&this->recv_buffer_[i], size - i, &consumed);
- if (!msg_type_varint.has_value())
- // not enough data there yet
- return;
- i += consumed;
- uint32_t msg_type = msg_type_varint->as_uint32();
-
- if (size - i < msg_size)
- // message body not fully received
- return;
-
- uint8_t *msg = &this->recv_buffer_[i];
- this->read_message(msg_size, msg_type, msg);
- if (this->remove_)
- return;
- // pop front
- uint32_t total = i + msg_size;
- this->recv_buffer_.erase(this->recv_buffer_.begin(), this->recv_buffer_.begin() + total);
- this->last_traffic_ = millis();
}
-}
-
-void APIConnection::disconnect_client() {
- this->client_->close();
- this->remove_ = true;
+ client_info_ = helper_->getpeername();
+ helper_->set_log_info(client_info_);
}
void APIConnection::loop() {
if (this->remove_)
return;
- if (this->next_close_) {
- this->disconnect_client();
- return;
- }
-
- if (!network_is_connected()) {
+ if (!network::is_connected()) {
// when network is disconnected force disconnect immediately
// don't wait for timeout
this->on_fatal_error();
+ ESP_LOGW(TAG, "%s: Network unavailable, disconnecting", client_info_.c_str());
return;
}
- if (this->client_->disconnected()) {
- // failsafe for disconnect logic
- this->on_disconnect_();
+ if (this->next_close_) {
+ // requested a disconnect
+ this->helper_->close();
+ this->remove_ = true;
return;
}
- this->parse_recv_buffer_();
+
+ APIError err = helper_->loop();
+ if (err != APIError::OK) {
+ on_fatal_error();
+ ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
+ return;
+ }
+ ReadPacketBuffer buffer;
+ err = helper_->read_packet(&buffer);
+ if (err == APIError::WOULD_BLOCK) {
+ // pass
+ } else if (err != APIError::OK) {
+ on_fatal_error();
+ if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) {
+ ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str());
+ } else if (err == APIError::CONNECTION_CLOSED) {
+ ESP_LOGW(TAG, "%s: Connection closed", client_info_.c_str());
+ } else {
+ ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
+ }
+ return;
+ } else {
+ this->last_traffic_ = millis();
+ // read a packet
+ this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
+ if (this->remove_)
+ return;
+ }
this->list_entities_iterator_.advance();
this->initial_state_iterator_.advance();
const uint32_t keepalive = 60000;
+ const uint32_t now = millis();
if (this->sent_ping_) {
// Disconnect if not responded within 2.5*keepalive
- if (millis() - this->last_traffic_ > (keepalive * 5) / 2) {
- ESP_LOGW(TAG, "'%s' didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str());
- this->disconnect_client();
+ if (now - this->last_traffic_ > (keepalive * 5) / 2) {
+ on_fatal_error();
+ ESP_LOGW(TAG, "%s didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str());
}
- } else if (millis() - this->last_traffic_ > keepalive) {
+ } else if (now - this->last_traffic_ > keepalive) {
this->sent_ping_ = true;
this->send_ping_request(PingRequest());
}
#ifdef USE_ESP32_CAMERA
- if (this->image_reader_.available()) {
- uint32_t space = this->client_->space();
- // reserve 15 bytes for metadata, and at least 64 bytes of data
- if (space >= 15 + 64) {
- uint32_t to_send = std::min(space - 15, this->image_reader_.available());
- auto buffer = this->create_buffer();
- // fixed32 key = 1;
- buffer.encode_fixed32(1, esp32_camera::global_esp32_camera->get_object_id_hash());
- // bytes data = 2;
- buffer.encode_bytes(2, this->image_reader_.peek_data_buffer(), to_send);
- // bool done = 3;
- bool done = this->image_reader_.available() == to_send;
- buffer.encode_bool(3, done);
- bool success = this->send_buffer(buffer, 44);
+ if (this->image_reader_.available() && this->helper_->can_write_without_blocking()) {
+ uint32_t to_send = std::min((size_t) 1024, this->image_reader_.available());
+ auto buffer = this->create_buffer();
+ // fixed32 key = 1;
+ buffer.encode_fixed32(1, esp32_camera::global_esp32_camera->get_object_id_hash());
+ // bytes data = 2;
+ buffer.encode_bytes(2, this->image_reader_.peek_data_buffer(), to_send);
+ // bool done = 3;
+ bool done = this->image_reader_.available() == to_send;
+ buffer.encode_bool(3, done);
+ bool success = this->send_buffer(buffer, 44);
- if (success) {
- this->image_reader_.consume_data(to_send);
- }
- if (success && done) {
- this->image_reader_.return_image();
- }
+ if (success) {
+ this->image_reader_.consume_data(to_send);
+ }
+ if (success && done) {
+ this->image_reader_.return_image();
}
}
#endif
+
+ if (state_subs_at_ != -1) {
+ const auto &subs = this->parent_->get_state_subs();
+ if (state_subs_at_ >= (int) subs.size()) {
+ state_subs_at_ = -1;
+ } else {
+ auto &it = subs[state_subs_at_];
+ SubscribeHomeAssistantStateResponse resp;
+ resp.entity_id = it.entity_id;
+ resp.attribute = it.attribute.value();
+ if (this->send_subscribe_home_assistant_state_response(resp)) {
+ state_subs_at_++;
+ }
+ }
+ }
}
-std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) {
- return App.get_name() + component_type + nameable->get_object_id();
+std::string get_default_unique_id(const std::string &component_type, EntityBase *entity) {
+ return App.get_name() + component_type + entity->get_object_id();
+}
+
+DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) {
+ // remote initiated disconnect_client
+ // don't close yet, we still need to send the disconnect response
+ // close will happen on next loop
+ ESP_LOGD(TAG, "%s requested disconnected", client_info_.c_str());
+ this->next_close_ = true;
+ DisconnectResponse resp;
+ return resp;
+}
+void APIConnection::on_disconnect_response(const DisconnectResponse &value) {
+ // pass
}
#ifdef USE_BINARY_SENSOR
@@ -173,6 +182,9 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_
msg.unique_id = get_default_unique_id("binary_sensor", binary_sensor);
msg.device_class = binary_sensor->get_device_class();
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
+ msg.disabled_by_default = binary_sensor->is_disabled_by_default();
+ msg.icon = binary_sensor->get_icon();
+ msg.entity_category = static_cast(binary_sensor->get_entity_category());
return this->send_list_entities_binary_sensor_response(msg);
}
#endif
@@ -204,6 +216,9 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
msg.supports_position = traits.get_supports_position();
msg.supports_tilt = traits.get_supports_tilt();
msg.device_class = cover->get_device_class();
+ msg.disabled_by_default = cover->is_disabled_by_default();
+ msg.icon = cover->get_icon();
+ msg.entity_category = static_cast(cover->get_entity_category());
return this->send_list_entities_cover_response(msg);
}
void APIConnection::cover_command(const CoverCommandRequest &msg) {
@@ -236,6 +251,9 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) {
#endif
#ifdef USE_FAN
+// Shut-up about usage of deprecated speed_level_to_enum/speed_enum_to_level functions for a bit.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
bool APIConnection::send_fan_state(fan::FanState *fan) {
if (!this->state_subscription_)
return false;
@@ -246,8 +264,12 @@ bool APIConnection::send_fan_state(fan::FanState *fan) {
resp.state = fan->state;
if (traits.supports_oscillation())
resp.oscillating = fan->oscillating;
- if (traits.supports_speed())
- resp.speed = static_cast(fan->speed);
+ if (traits.supports_speed()) {
+ resp.speed_level = fan->speed;
+ resp.speed = static_cast(fan::speed_level_to_enum(fan->speed, traits.supported_speed_count()));
+ }
+ if (traits.supports_direction())
+ resp.direction = static_cast(fan->direction);
return this->send_fan_state_response(resp);
}
bool APIConnection::send_fan_info(fan::FanState *fan) {
@@ -259,6 +281,11 @@ bool APIConnection::send_fan_info(fan::FanState *fan) {
msg.unique_id = get_default_unique_id("fan", fan);
msg.supports_oscillation = traits.supports_oscillation();
msg.supports_speed = traits.supports_speed();
+ msg.supports_direction = traits.supports_direction();
+ msg.supported_speed_count = traits.supported_speed_count();
+ msg.disabled_by_default = fan->is_disabled_by_default();
+ msg.icon = fan->get_icon();
+ msg.entity_category = static_cast(fan->get_entity_category());
return this->send_list_entities_fan_response(msg);
}
void APIConnection::fan_command(const FanCommandRequest &msg) {
@@ -266,15 +293,24 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
if (fan == nullptr)
return;
+ auto traits = fan->get_traits();
+
auto call = fan->make_call();
if (msg.has_state)
call.set_state(msg.state);
if (msg.has_oscillating)
call.set_oscillating(msg.oscillating);
- if (msg.has_speed)
- call.set_speed(static_cast(msg.speed));
+ if (msg.has_speed_level) {
+ // Prefer level
+ call.set_speed(msg.speed_level);
+ } else if (msg.has_speed) {
+ call.set_speed(fan::speed_enum_to_level(static_cast(msg.speed), traits.supported_speed_count()));
+ }
+ if (msg.has_direction)
+ call.set_direction(static_cast(msg.direction));
call.perform();
}
+#pragma GCC diagnostic pop
#endif
#ifdef USE_LIGHT
@@ -284,21 +320,21 @@ bool APIConnection::send_light_state(light::LightState *light) {
auto traits = light->get_traits();
auto values = light->remote_values;
+ auto color_mode = values.get_color_mode();
LightStateResponse resp{};
resp.key = light->get_object_id_hash();
resp.state = values.is_on();
- if (traits.get_supports_brightness())
- resp.brightness = values.get_brightness();
- if (traits.get_supports_rgb()) {
- resp.red = values.get_red();
- resp.green = values.get_green();
- resp.blue = values.get_blue();
- }
- if (traits.get_supports_rgb_white_value())
- resp.white = values.get_white();
- if (traits.get_supports_color_temperature())
- resp.color_temperature = values.get_color_temperature();
+ resp.color_mode = static_cast(color_mode);
+ resp.brightness = values.get_brightness();
+ resp.color_brightness = values.get_color_brightness();
+ resp.red = values.get_red();
+ resp.green = values.get_green();
+ resp.blue = values.get_blue();
+ resp.white = values.get_white();
+ resp.color_temperature = values.get_color_temperature();
+ resp.cold_white = values.get_cold_white();
+ resp.warm_white = values.get_warm_white();
if (light->supports_effects())
resp.effect = light->get_effect_name();
return this->send_light_state_response(resp);
@@ -310,11 +346,23 @@ bool APIConnection::send_light_info(light::LightState *light) {
msg.object_id = light->get_object_id();
msg.name = light->get_name();
msg.unique_id = get_default_unique_id("light", light);
- msg.supports_brightness = traits.get_supports_brightness();
- msg.supports_rgb = traits.get_supports_rgb();
- msg.supports_white_value = traits.get_supports_rgb_white_value();
- msg.supports_color_temperature = traits.get_supports_color_temperature();
- if (msg.supports_color_temperature) {
+
+ msg.disabled_by_default = light->is_disabled_by_default();
+ msg.icon = light->get_icon();
+ msg.entity_category = static_cast(light->get_entity_category());
+
+ for (auto mode : traits.get_supported_color_modes())
+ msg.supported_color_modes.push_back(static_cast(mode));
+
+ msg.legacy_supports_brightness = traits.supports_color_capability(light::ColorCapability::BRIGHTNESS);
+ msg.legacy_supports_rgb = traits.supports_color_capability(light::ColorCapability::RGB);
+ msg.legacy_supports_white_value =
+ msg.legacy_supports_rgb && (traits.supports_color_capability(light::ColorCapability::WHITE) ||
+ traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE));
+ msg.legacy_supports_color_temperature = traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) ||
+ traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE);
+
+ if (msg.legacy_supports_color_temperature) {
msg.min_mireds = traits.get_min_mireds();
msg.max_mireds = traits.get_max_mireds();
}
@@ -335,6 +383,10 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
call.set_state(msg.state);
if (msg.has_brightness)
call.set_brightness(msg.brightness);
+ if (msg.has_color_mode)
+ call.set_color_mode(static_cast(msg.color_mode));
+ if (msg.has_color_brightness)
+ call.set_color_brightness(msg.color_brightness);
if (msg.has_rgb) {
call.set_red(msg.red);
call.set_green(msg.green);
@@ -344,6 +396,10 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
call.set_white(msg.white);
if (msg.has_color_temperature)
call.set_color_temperature(msg.color_temperature);
+ if (msg.has_cold_white)
+ call.set_cold_white(msg.cold_white);
+ if (msg.has_warm_white)
+ call.set_warm_white(msg.warm_white);
if (msg.has_transition_length)
call.set_transition_length(msg.transition_length);
if (msg.has_flash_length)
@@ -377,6 +433,10 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
msg.unit_of_measurement = sensor->get_unit_of_measurement();
msg.accuracy_decimals = sensor->get_accuracy_decimals();
msg.force_update = sensor->get_force_update();
+ msg.device_class = sensor->get_device_class();
+ msg.state_class = static_cast(sensor->get_state_class());
+ msg.disabled_by_default = sensor->is_disabled_by_default();
+ msg.entity_category = static_cast(sensor->get_entity_category());
return this->send_list_entities_sensor_response(msg);
}
#endif
@@ -399,6 +459,8 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
msg.unique_id = get_default_unique_id("switch", a_switch);
msg.icon = a_switch->get_icon();
msg.assumed_state = a_switch->assumed_state();
+ msg.disabled_by_default = a_switch->is_disabled_by_default();
+ msg.entity_category = static_cast(a_switch->get_entity_category());
return this->send_list_entities_switch_response(msg);
}
void APIConnection::switch_command(const SwitchCommandRequest &msg) {
@@ -433,6 +495,8 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor)
if (msg.unique_id.empty())
msg.unique_id = get_default_unique_id("text_sensor", text_sensor);
msg.icon = text_sensor->get_icon();
+ msg.disabled_by_default = text_sensor->is_disabled_by_default();
+ msg.entity_category = static_cast(text_sensor->get_entity_category());
return this->send_list_entities_text_sensor_response(msg);
}
#endif
@@ -455,10 +519,16 @@ bool APIConnection::send_climate_state(climate::Climate *climate) {
} else {
resp.target_temperature = climate->target_temperature;
}
- if (traits.get_supports_away())
- resp.away = climate->away;
- if (traits.get_supports_fan_modes())
- resp.fan_mode = static_cast(climate->fan_mode);
+ if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
+ resp.fan_mode = static_cast(climate->fan_mode.value());
+ if (!traits.get_supported_custom_fan_modes().empty() && climate->custom_fan_mode.has_value())
+ resp.custom_fan_mode = climate->custom_fan_mode.value();
+ if (traits.get_supports_presets() && climate->preset.has_value()) {
+ resp.preset = static_cast(climate->preset.value());
+ resp.legacy_away = resp.preset == enums::CLIMATE_PRESET_AWAY;
+ }
+ if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value())
+ resp.custom_preset = climate->custom_preset.value();
if (traits.get_supports_swing_modes())
resp.swing_mode = static_cast(climate->swing_mode);
return this->send_climate_state_response(resp);
@@ -470,29 +540,33 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
msg.object_id = climate->get_object_id();
msg.name = climate->get_name();
msg.unique_id = get_default_unique_id("climate", climate);
+
+ msg.disabled_by_default = climate->is_disabled_by_default();
+ msg.icon = climate->get_icon();
+ msg.entity_category = static_cast(climate->get_entity_category());
+
msg.supports_current_temperature = traits.get_supports_current_temperature();
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
- for (auto mode : {climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL,
- climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_DRY, climate::CLIMATE_MODE_FAN_ONLY}) {
- if (traits.supports_mode(mode))
- msg.supported_modes.push_back(static_cast(mode));
- }
+
+ for (auto mode : traits.get_supported_modes())
+ msg.supported_modes.push_back(static_cast(mode));
+
msg.visual_min_temperature = traits.get_visual_min_temperature();
msg.visual_max_temperature = traits.get_visual_max_temperature();
msg.visual_temperature_step = traits.get_visual_temperature_step();
- msg.supports_away = traits.get_supports_away();
+ msg.legacy_supports_away = traits.supports_preset(climate::CLIMATE_PRESET_AWAY);
msg.supports_action = traits.get_supports_action();
- for (auto fan_mode : {climate::CLIMATE_FAN_ON, climate::CLIMATE_FAN_OFF, climate::CLIMATE_FAN_AUTO,
- climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH,
- climate::CLIMATE_FAN_MIDDLE, climate::CLIMATE_FAN_FOCUS, climate::CLIMATE_FAN_DIFFUSE}) {
- if (traits.supports_fan_mode(fan_mode))
- msg.supported_fan_modes.push_back(static_cast(fan_mode));
- }
- for (auto swing_mode : {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL,
- climate::CLIMATE_SWING_HORIZONTAL}) {
- if (traits.supports_swing_mode(swing_mode))
- msg.supported_swing_modes.push_back(static_cast(swing_mode));
- }
+
+ for (auto fan_mode : traits.get_supported_fan_modes())
+ msg.supported_fan_modes.push_back(static_cast(fan_mode));
+ for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes())
+ msg.supported_custom_fan_modes.push_back(custom_fan_mode);
+ for (auto preset : traits.get_supported_presets())
+ msg.supported_presets.push_back(static_cast(preset));
+ for (auto const &custom_preset : traits.get_supported_custom_presets())
+ msg.supported_custom_presets.push_back(custom_preset);
+ for (auto swing_mode : traits.get_supported_swing_modes())
+ msg.supported_swing_modes.push_back(static_cast(swing_mode));
return this->send_list_entities_climate_response(msg);
}
void APIConnection::climate_command(const ClimateCommandRequest &msg) {
@@ -509,23 +583,128 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
call.set_target_temperature_low(msg.target_temperature_low);
if (msg.has_target_temperature_high)
call.set_target_temperature_high(msg.target_temperature_high);
- if (msg.has_away)
- call.set_away(msg.away);
+ if (msg.has_legacy_away)
+ call.set_preset(msg.legacy_away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME);
if (msg.has_fan_mode)
call.set_fan_mode(static_cast(msg.fan_mode));
+ if (msg.has_custom_fan_mode)
+ call.set_fan_mode(msg.custom_fan_mode);
+ if (msg.has_preset)
+ call.set_preset(static_cast(msg.preset));
+ if (msg.has_custom_preset)
+ call.set_preset(msg.custom_preset);
if (msg.has_swing_mode)
call.set_swing_mode(static_cast(msg.swing_mode));
call.perform();
}
#endif
+#ifdef USE_NUMBER
+bool APIConnection::send_number_state(number::Number *number, float state) {
+ if (!this->state_subscription_)
+ return false;
+
+ NumberStateResponse resp{};
+ resp.key = number->get_object_id_hash();
+ resp.state = state;
+ resp.missing_state = !number->has_state();
+ return this->send_number_state_response(resp);
+}
+bool APIConnection::send_number_info(number::Number *number) {
+ ListEntitiesNumberResponse msg;
+ msg.key = number->get_object_id_hash();
+ msg.object_id = number->get_object_id();
+ msg.name = number->get_name();
+ msg.unique_id = get_default_unique_id("number", number);
+ msg.icon = number->get_icon();
+ msg.disabled_by_default = number->is_disabled_by_default();
+ msg.entity_category = static_cast(number->get_entity_category());
+ msg.unit_of_measurement = number->traits.get_unit_of_measurement();
+ msg.mode = static_cast(number->traits.get_mode());
+
+ msg.min_value = number->traits.get_min_value();
+ msg.max_value = number->traits.get_max_value();
+ msg.step = number->traits.get_step();
+
+ return this->send_list_entities_number_response(msg);
+}
+void APIConnection::number_command(const NumberCommandRequest &msg) {
+ number::Number *number = App.get_number_by_key(msg.key);
+ if (number == nullptr)
+ return;
+
+ auto call = number->make_call();
+ call.set_value(msg.state);
+ call.perform();
+}
+#endif
+
+#ifdef USE_SELECT
+bool APIConnection::send_select_state(select::Select *select, std::string state) {
+ if (!this->state_subscription_)
+ return false;
+
+ SelectStateResponse resp{};
+ resp.key = select->get_object_id_hash();
+ resp.state = std::move(state);
+ resp.missing_state = !select->has_state();
+ return this->send_select_state_response(resp);
+}
+bool APIConnection::send_select_info(select::Select *select) {
+ ListEntitiesSelectResponse msg;
+ msg.key = select->get_object_id_hash();
+ msg.object_id = select->get_object_id();
+ msg.name = select->get_name();
+ msg.unique_id = get_default_unique_id("select", select);
+ msg.icon = select->get_icon();
+ msg.disabled_by_default = select->is_disabled_by_default();
+ msg.entity_category = static_cast(select->get_entity_category());
+
+ for (const auto &option : select->traits.get_options())
+ msg.options.push_back(option);
+
+ return this->send_list_entities_select_response(msg);
+}
+void APIConnection::select_command(const SelectCommandRequest &msg) {
+ select::Select *select = App.get_select_by_key(msg.key);
+ if (select == nullptr)
+ return;
+
+ auto call = select->make_call();
+ call.set_option(msg.state);
+ call.perform();
+}
+#endif
+
+#ifdef USE_BUTTON
+bool APIConnection::send_button_info(button::Button *button) {
+ ListEntitiesButtonResponse msg;
+ msg.key = button->get_object_id_hash();
+ msg.object_id = button->get_object_id();
+ msg.name = button->get_name();
+ msg.unique_id = get_default_unique_id("button", button);
+ msg.icon = button->get_icon();
+ msg.disabled_by_default = button->is_disabled_by_default();
+ msg.entity_category = static_cast(button->get_entity_category());
+ msg.device_class = button->get_device_class();
+ return this->send_list_entities_button_response(msg);
+}
+void APIConnection::button_command(const ButtonCommandRequest &msg) {
+ button::Button *button = App.get_button_by_key(msg.key);
+ if (button == nullptr)
+ return;
+
+ button->press();
+}
+#endif
+
#ifdef USE_ESP32_CAMERA
void APIConnection::send_camera_state(std::shared_ptr image) {
if (!this->state_subscription_)
return;
if (this->image_reader_.available())
return;
- this->image_reader_.set_image(image);
+ this->image_reader_.set_image(std::move(image));
}
bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
ListEntitiesCameraResponse msg;
@@ -533,6 +712,9 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
msg.object_id = camera->get_object_id();
msg.name = camera->get_name();
msg.unique_id = get_default_unique_id("camera", camera);
+ msg.disabled_by_default = camera->is_disabled_by_default();
+ msg.icon = camera->get_icon();
+ msg.entity_category = static_cast(camera->get_entity_category());
return this->send_list_entities_camera_response(msg);
}
void APIConnection::camera_image(const CameraImageRequest &msg) {
@@ -561,30 +743,20 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin
auto buffer = this->create_buffer();
// LogLevel level = 1;
buffer.encode_uint32(1, static_cast(level));
- // string tag = 2;
- // buffer.encode_string(2, tag, strlen(tag));
// string message = 3;
buffer.encode_string(3, line, strlen(line));
// SubscribeLogsResponse - 29
- bool success = this->send_buffer(buffer, 29);
- if (!success) {
- buffer = this->create_buffer();
- // bool send_failed = 4;
- buffer.encode_bool(4, true);
- return this->send_buffer(buffer, 29);
- } else {
- return true;
- }
+ return this->send_buffer(buffer, 29);
}
HelloResponse APIConnection::hello(const HelloRequest &msg) {
- this->client_info_ = msg.client_info + " (" + this->client_->remoteIP().toString().c_str();
- this->client_info_ += ")";
+ this->client_info_ = msg.client_info + " (" + this->helper_->getpeername() + ")";
+ this->helper_->set_log_info(client_info_);
ESP_LOGV(TAG, "Hello from client: '%s'", this->client_info_.c_str());
HelloResponse resp;
resp.api_version_major = 1;
- resp.api_version_minor = 3;
+ resp.api_version_minor = 6;
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
this->connection_state_ = ConnectionState::CONNECTED;
return resp;
@@ -596,7 +768,7 @@ ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
// bool invalid_password = 1;
resp.invalid_password = !correct;
if (correct) {
- ESP_LOGD(TAG, "Client '%s' connected successfully!", this->client_info_.c_str());
+ ESP_LOGD(TAG, "%s: Connected successfully", this->client_info_.c_str());
this->connection_state_ = ConnectionState::AUTHENTICATED;
#ifdef USE_HOMEASSISTANT_TIME
@@ -614,18 +786,24 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
resp.mac_address = get_mac_address_pretty();
resp.esphome_version = ESPHOME_VERSION;
resp.compilation_time = App.get_compilation_time();
-#ifdef ARDUINO_BOARD
- resp.model = ARDUINO_BOARD;
-#endif
+ resp.model = ESPHOME_BOARD;
#ifdef USE_DEEP_SLEEP
resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;
+#endif
+#ifdef ESPHOME_PROJECT_NAME
+ resp.project_name = ESPHOME_PROJECT_NAME;
+ resp.project_version = ESPHOME_PROJECT_VERSION;
+#endif
+#ifdef USE_WEBSERVER
+ resp.webserver_port = WEBSERVER_PORT;
#endif
return resp;
}
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
for (auto &it : this->parent_->get_state_subs())
- if (it.entity_id == msg.entity_id)
+ if (it.entity_id == msg.entity_id && it.attribute.value() == msg.attribute) {
it.callback(msg.state);
+ }
}
void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
bool found = false;
@@ -639,29 +817,20 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
}
}
void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) {
- for (auto &it : this->parent_->get_state_subs()) {
- SubscribeHomeAssistantStateResponse resp;
- resp.entity_id = it.entity_id;
- if (!this->send_subscribe_home_assistant_state_response(resp)) {
- this->on_fatal_error();
- return;
- }
- }
+ state_subs_at_ = 0;
}
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) {
if (this->remove_)
return false;
-
- std::vector header;
- header.push_back(0x00);
- ProtoVarInt(buffer.get_buffer()->size()).encode(header);
- ProtoVarInt(message_type).encode(header);
-
- size_t needed_space = buffer.get_buffer()->size() + header.size();
-
- if (needed_space > this->client_->space()) {
+ if (!this->helper_->can_write_without_blocking()) {
delay(0);
- if (needed_space > this->client_->space()) {
+ APIError err = helper_->loop();
+ if (err != APIError::OK) {
+ on_fatal_error();
+ ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
+ return false;
+ }
+ if (!this->helper_->can_write_without_blocking()) {
// SubscribeLogsResponse
if (message_type != 29) {
ESP_LOGV(TAG, "Cannot send message because of TCP buffer space");
@@ -671,22 +840,31 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type)
}
}
- this->client_->add(reinterpret_cast(header.data()), header.size());
- this->client_->add(reinterpret_cast(buffer.get_buffer()->data()), buffer.get_buffer()->size());
- bool ret = this->client_->send();
- return ret;
+ APIError err = this->helper_->write_packet(message_type, buffer.get_buffer()->data(), buffer.get_buffer()->size());
+ if (err == APIError::WOULD_BLOCK)
+ return false;
+ if (err != APIError::OK) {
+ on_fatal_error();
+ if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) {
+ ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str());
+ } else {
+ ESP_LOGW(TAG, "%s: Packet write failed %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
+ }
+ return false;
+ }
+ this->last_traffic_ = millis();
+ return true;
}
void APIConnection::on_unauthenticated_access() {
- ESP_LOGD(TAG, "'%s' tried to access without authentication.", this->client_info_.c_str());
this->on_fatal_error();
+ ESP_LOGD(TAG, "%s: tried to access without authentication.", this->client_info_.c_str());
}
void APIConnection::on_no_setup_connection() {
- ESP_LOGD(TAG, "'%s' tried to access without full connection.", this->client_info_.c_str());
this->on_fatal_error();
+ ESP_LOGD(TAG, "%s: tried to access without full connection.", this->client_info_.c_str());
}
void APIConnection::on_fatal_error() {
- ESP_LOGV(TAG, "Error: Disconnecting %s", this->client_info_.c_str());
- this->client_->close();
+ this->helper_->close();
this->remove_ = true;
}
diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h
index 3e91ead52c..72697b5911 100644
--- a/esphome/components/api/api_connection.h
+++ b/esphome/components/api/api_connection.h
@@ -5,16 +5,17 @@
#include "api_pb2.h"
#include "api_pb2_service.h"
#include "api_server.h"
+#include "api_frame_helper.h"
namespace esphome {
namespace api {
class APIConnection : public APIServerConnection {
public:
- APIConnection(AsyncClient *client, APIServer *parent);
- virtual ~APIConnection();
+ APIConnection(std::unique_ptr socket, APIServer *parent);
+ virtual ~APIConnection() = default;
- void disconnect_client();
+ void start();
void loop();
bool send_list_info_done() {
@@ -62,6 +63,20 @@ class APIConnection : public APIServerConnection {
bool send_climate_state(climate::Climate *climate);
bool send_climate_info(climate::Climate *climate);
void climate_command(const ClimateCommandRequest &msg) override;
+#endif
+#ifdef USE_NUMBER
+ bool send_number_state(number::Number *number, float state);
+ bool send_number_info(number::Number *number);
+ void number_command(const NumberCommandRequest &msg) override;
+#endif
+#ifdef USE_SELECT
+ bool send_select_state(select::Select *select, std::string state);
+ bool send_select_info(select::Select *select);
+ void select_command(const SelectCommandRequest &msg) override;
+#endif
+#ifdef USE_BUTTON
+ bool send_button_info(button::Button *button);
+ void button_command(const ButtonCommandRequest &msg) override;
#endif
bool send_log_message(int level, const char *tag, const char *line);
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
@@ -76,10 +91,7 @@ class APIConnection : public APIServerConnection {
}
#endif
- void on_disconnect_response(const DisconnectResponse &value) override {
- // we initiated disconnect_client
- this->next_close_ = true;
- }
+ void on_disconnect_response(const DisconnectResponse &value) override;
void on_ping_response(const PingResponse &value) override {
// we initiated ping
this->sent_ping_ = false;
@@ -90,12 +102,7 @@ class APIConnection : public APIServerConnection {
#endif
HelloResponse hello(const HelloRequest &msg) override;
ConnectResponse connect(const ConnectRequest &msg) override;
- DisconnectResponse disconnect(const DisconnectRequest &msg) override {
- // remote initiated disconnect_client
- this->next_close_ = true;
- DisconnectResponse resp;
- return resp;
- }
+ DisconnectResponse disconnect(const DisconnectRequest &msg) override;
PingResponse ping(const PingRequest &msg) override { return {}; }
DeviceInfoResponse device_info(const DeviceInfoRequest &msg) override;
void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); }
@@ -125,19 +132,16 @@ class APIConnection : public APIServerConnection {
void on_unauthenticated_access() override;
void on_no_setup_connection() override;
ProtoWriteBuffer create_buffer() override {
- this->send_buffer_.clear();
- return {&this->send_buffer_};
+ // FIXME: ensure no recursive writes can happen
+ this->proto_write_buffer_.clear();
+ return {&this->proto_write_buffer_};
}
bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) override;
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 parse_recv_buffer_();
+ bool send_(const void *buf, size_t len, bool force);
enum class ConnectionState {
WAITING_FOR_HELLO,
@@ -147,8 +151,10 @@ class APIConnection : public APIServerConnection {
bool remove_{false};
- std::vector send_buffer_;
- std::vector recv_buffer_;
+ // Buffer used to encode proto messages
+ // Re-use to prevent allocations
+ std::vector proto_write_buffer_;
+ std::unique_ptr helper_;
std::string client_info_;
#ifdef USE_ESP32_CAMERA
@@ -160,12 +166,11 @@ class APIConnection : public APIServerConnection {
uint32_t last_traffic_;
bool sent_ping_{false};
bool service_call_subscription_{false};
- bool current_nodelay_{false};
- bool next_close_{false};
- AsyncClient *client_;
+ bool next_close_ = false;
APIServer *parent_;
InitialStateIterator initial_state_iterator_;
ListEntitiesIterator list_entities_iterator_;
+ int state_subs_at_ = -1;
};
} // namespace api
diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp
new file mode 100644
index 0000000000..151c2512de
--- /dev/null
+++ b/esphome/components/api/api_frame_helper.cpp
@@ -0,0 +1,1010 @@
+#include "api_frame_helper.h"
+
+#include "esphome/core/log.h"
+#include "esphome/core/helpers.h"
+#include "proto.h"
+#include
+
+namespace esphome {
+namespace api {
+
+static const char *const TAG = "api.socket";
+
+/// Is the given return value (from write syscalls) a wouldblock error?
+bool is_would_block(ssize_t ret) {
+ if (ret == -1) {
+ return errno == EWOULDBLOCK || errno == EAGAIN;
+ }
+ return ret == 0;
+}
+
+const char *api_error_to_str(APIError err) {
+ // not using switch to ensure compiler doesn't try to build a big table out of it
+ if (err == APIError::OK) {
+ return "OK";
+ } else if (err == APIError::WOULD_BLOCK) {
+ return "WOULD_BLOCK";
+ } else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) {
+ return "BAD_HANDSHAKE_PACKET_LEN";
+ } else if (err == APIError::BAD_INDICATOR) {
+ return "BAD_INDICATOR";
+ } else if (err == APIError::BAD_DATA_PACKET) {
+ return "BAD_DATA_PACKET";
+ } else if (err == APIError::TCP_NODELAY_FAILED) {
+ return "TCP_NODELAY_FAILED";
+ } else if (err == APIError::TCP_NONBLOCKING_FAILED) {
+ return "TCP_NONBLOCKING_FAILED";
+ } else if (err == APIError::CLOSE_FAILED) {
+ return "CLOSE_FAILED";
+ } else if (err == APIError::SHUTDOWN_FAILED) {
+ return "SHUTDOWN_FAILED";
+ } else if (err == APIError::BAD_STATE) {
+ return "BAD_STATE";
+ } else if (err == APIError::BAD_ARG) {
+ return "BAD_ARG";
+ } else if (err == APIError::SOCKET_READ_FAILED) {
+ return "SOCKET_READ_FAILED";
+ } else if (err == APIError::SOCKET_WRITE_FAILED) {
+ return "SOCKET_WRITE_FAILED";
+ } else if (err == APIError::HANDSHAKESTATE_READ_FAILED) {
+ return "HANDSHAKESTATE_READ_FAILED";
+ } else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) {
+ return "HANDSHAKESTATE_WRITE_FAILED";
+ } else if (err == APIError::HANDSHAKESTATE_BAD_STATE) {
+ return "HANDSHAKESTATE_BAD_STATE";
+ } else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) {
+ return "CIPHERSTATE_DECRYPT_FAILED";
+ } else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) {
+ return "CIPHERSTATE_ENCRYPT_FAILED";
+ } else if (err == APIError::OUT_OF_MEMORY) {
+ return "OUT_OF_MEMORY";
+ } else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) {
+ return "HANDSHAKESTATE_SETUP_FAILED";
+ } else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) {
+ return "HANDSHAKESTATE_SPLIT_FAILED";
+ } else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) {
+ return "BAD_HANDSHAKE_ERROR_BYTE";
+ } else if (err == APIError::CONNECTION_CLOSED) {
+ return "CONNECTION_CLOSED";
+ }
+ return "UNKNOWN";
+}
+
+#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__)
+// uncomment to log raw packets
+//#define HELPER_LOG_PACKETS
+
+#ifdef USE_API_NOISE
+static const char *const PROLOGUE_INIT = "NoiseAPIInit";
+
+/// Convert a noise error code to a readable error
+std::string noise_err_to_str(int err) {
+ if (err == NOISE_ERROR_NO_MEMORY)
+ return "NO_MEMORY";
+ if (err == NOISE_ERROR_UNKNOWN_ID)
+ return "UNKNOWN_ID";
+ if (err == NOISE_ERROR_UNKNOWN_NAME)
+ return "UNKNOWN_NAME";
+ if (err == NOISE_ERROR_MAC_FAILURE)
+ return "MAC_FAILURE";
+ if (err == NOISE_ERROR_NOT_APPLICABLE)
+ return "NOT_APPLICABLE";
+ if (err == NOISE_ERROR_SYSTEM)
+ return "SYSTEM";
+ if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED)
+ return "REMOTE_KEY_REQUIRED";
+ if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED)
+ return "LOCAL_KEY_REQUIRED";
+ if (err == NOISE_ERROR_PSK_REQUIRED)
+ return "PSK_REQUIRED";
+ if (err == NOISE_ERROR_INVALID_LENGTH)
+ return "INVALID_LENGTH";
+ if (err == NOISE_ERROR_INVALID_PARAM)
+ return "INVALID_PARAM";
+ if (err == NOISE_ERROR_INVALID_STATE)
+ return "INVALID_STATE";
+ if (err == NOISE_ERROR_INVALID_NONCE)
+ return "INVALID_NONCE";
+ if (err == NOISE_ERROR_INVALID_PRIVATE_KEY)
+ return "INVALID_PRIVATE_KEY";
+ if (err == NOISE_ERROR_INVALID_PUBLIC_KEY)
+ return "INVALID_PUBLIC_KEY";
+ if (err == NOISE_ERROR_INVALID_FORMAT)
+ return "INVALID_FORMAT";
+ if (err == NOISE_ERROR_INVALID_SIGNATURE)
+ return "INVALID_SIGNATURE";
+ return to_string(err);
+}
+
+/// Initialize the frame helper, returns OK if successful.
+APIError APINoiseFrameHelper::init() {
+ if (state_ != State::INITIALIZE || socket_ == nullptr) {
+ HELPER_LOG("Bad state for init %d", (int) state_);
+ return APIError::BAD_STATE;
+ }
+ int err = socket_->setblocking(false);
+ if (err != 0) {
+ state_ = State::FAILED;
+ HELPER_LOG("Setting nonblocking failed with errno %d", errno);
+ return APIError::TCP_NONBLOCKING_FAILED;
+ }
+
+ int enable = 1;
+ err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
+ if (err != 0) {
+ state_ = State::FAILED;
+ HELPER_LOG("Setting nodelay failed with errno %d", errno);
+ return APIError::TCP_NODELAY_FAILED;
+ }
+
+ // init prologue
+ prologue_.insert(prologue_.end(), PROLOGUE_INIT, PROLOGUE_INIT + strlen(PROLOGUE_INIT));
+
+ state_ = State::CLIENT_HELLO;
+ return APIError::OK;
+}
+/// Run through handshake messages (if in that phase)
+APIError APINoiseFrameHelper::loop() {
+ APIError err = state_action_();
+ if (err == APIError::WOULD_BLOCK)
+ return APIError::OK;
+ if (err != APIError::OK)
+ return err;
+ if (!tx_buf_.empty()) {
+ err = try_send_tx_buf_();
+ if (err != APIError::OK) {
+ return err;
+ }
+ }
+ return APIError::OK;
+}
+
+/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
+ *
+ * @param frame: The struct to hold the frame information in.
+ * msg_start: points to the start of the payload - this pointer is only valid until the next
+ * try_receive_raw_ call
+ *
+ * @return 0 if a full packet is in rx_buf_
+ * @return -1 if error, check errno.
+ *
+ * errno EWOULDBLOCK: Packet could not be read without blocking. Try again later.
+ * errno ENOMEM: Not enough memory for reading packet.
+ * errno API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
+ * errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase.
+ */
+APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
+ if (frame == nullptr) {
+ HELPER_LOG("Bad argument for try_read_frame_");
+ return APIError::BAD_ARG;
+ }
+
+ // read header
+ if (rx_header_buf_len_ < 3) {
+ // no header information yet
+ size_t to_read = 3 - rx_header_buf_len_;
+ ssize_t received = socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
+ if (received == -1) {
+ if (errno == EWOULDBLOCK || errno == EAGAIN) {
+ return APIError::WOULD_BLOCK;
+ }
+ state_ = State::FAILED;
+ HELPER_LOG("Socket read failed with errno %d", errno);
+ return APIError::SOCKET_READ_FAILED;
+ } else if (received == 0) {
+ state_ = State::FAILED;
+ HELPER_LOG("Connection closed");
+ return APIError::CONNECTION_CLOSED;
+ }
+ rx_header_buf_len_ += received;
+ if ((size_t) received != to_read) {
+ // not a full read
+ return APIError::WOULD_BLOCK;
+ }
+
+ // header reading done
+ }
+
+ // read body
+ uint8_t indicator = rx_header_buf_[0];
+ if (indicator != 0x01) {
+ state_ = State::FAILED;
+ HELPER_LOG("Bad indicator byte %u", indicator);
+ return APIError::BAD_INDICATOR;
+ }
+
+ uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2];
+
+ if (state_ != State::DATA && msg_size > 128) {
+ // for handshake message only permit up to 128 bytes
+ state_ = State::FAILED;
+ HELPER_LOG("Bad packet len for handshake: %d", msg_size);
+ return APIError::BAD_HANDSHAKE_PACKET_LEN;
+ }
+
+ // reserve space for body
+ if (rx_buf_.size() != msg_size) {
+ rx_buf_.resize(msg_size);
+ }
+
+ if (rx_buf_len_ < msg_size) {
+ // more data to read
+ size_t to_read = msg_size - rx_buf_len_;
+ ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read);
+ if (received == -1) {
+ if (errno == EWOULDBLOCK || errno == EAGAIN) {
+ return APIError::WOULD_BLOCK;
+ }
+ state_ = State::FAILED;
+ HELPER_LOG("Socket read failed with errno %d", errno);
+ return APIError::SOCKET_READ_FAILED;
+ } else if (received == 0) {
+ state_ = State::FAILED;
+ HELPER_LOG("Connection closed");
+ return APIError::CONNECTION_CLOSED;
+ }
+ rx_buf_len_ += received;
+ if ((size_t) received != to_read) {
+ // not all read
+ return APIError::WOULD_BLOCK;
+ }
+ }
+
+ // uncomment for even more debugging
+#ifdef HELPER_LOG_PACKETS
+ ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str());
+#endif
+ frame->msg = std::move(rx_buf_);
+ // consume msg
+ rx_buf_ = {};
+ rx_buf_len_ = 0;
+ rx_header_buf_len_ = 0;
+ return APIError::OK;
+}
+
+/** To be called from read/write methods.
+ *
+ * This method runs through the internal handshake methods, if in that state.
+ *
+ * If the handshake is still active when this method returns and a read/write can't take place at
+ * the moment, returns WOULD_BLOCK.
+ * If an error occured, returns that error. Only returns OK if the transport is ready for data
+ * traffic.
+ */
+APIError APINoiseFrameHelper::state_action_() {
+ int err;
+ APIError aerr;
+ if (state_ == State::INITIALIZE) {
+ HELPER_LOG("Bad state for method: %d", (int) state_);
+ return APIError::BAD_STATE;
+ }
+ if (state_ == State::CLIENT_HELLO) {
+ // waiting for client hello
+ ParsedFrame frame;
+ aerr = try_read_frame_(&frame);
+ if (aerr == APIError::BAD_INDICATOR) {
+ send_explicit_handshake_reject_("Bad indicator byte");
+ return aerr;
+ }
+ if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
+ send_explicit_handshake_reject_("Bad handshake packet len");
+ return aerr;
+ }
+ if (aerr != APIError::OK)
+ return aerr;
+ // ignore contents, may be used in future for flags
+ prologue_.push_back((uint8_t)(frame.msg.size() >> 8));
+ prologue_.push_back((uint8_t) frame.msg.size());
+ prologue_.insert(prologue_.end(), frame.msg.begin(), frame.msg.end());
+
+ state_ = State::SERVER_HELLO;
+ }
+ if (state_ == State::SERVER_HELLO) {
+ // send server hello
+ uint8_t msg[1];
+ msg[0] = 0x01; // chosen proto
+ aerr = write_frame_(msg, 1);
+ if (aerr != APIError::OK)
+ return aerr;
+
+ // start handshake
+ aerr = init_handshake_();
+ if (aerr != APIError::OK)
+ return aerr;
+
+ state_ = State::HANDSHAKE;
+ }
+ if (state_ == State::HANDSHAKE) {
+ int action = noise_handshakestate_get_action(handshake_);
+ if (action == NOISE_ACTION_READ_MESSAGE) {
+ // waiting for handshake msg
+ ParsedFrame frame;
+ aerr = try_read_frame_(&frame);
+ if (aerr == APIError::BAD_INDICATOR) {
+ send_explicit_handshake_reject_("Bad indicator byte");
+ return aerr;
+ }
+ if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
+ send_explicit_handshake_reject_("Bad handshake packet len");
+ return aerr;
+ }
+ if (aerr != APIError::OK)
+ return aerr;
+
+ if (frame.msg.empty()) {
+ send_explicit_handshake_reject_("Empty handshake message");
+ return APIError::BAD_HANDSHAKE_ERROR_BYTE;
+ } else if (frame.msg[0] != 0x00) {
+ HELPER_LOG("Bad handshake error byte: %u", frame.msg[0]);
+ send_explicit_handshake_reject_("Bad handshake error byte");
+ return APIError::BAD_HANDSHAKE_ERROR_BYTE;
+ }
+
+ NoiseBuffer mbuf;
+ noise_buffer_init(mbuf);
+ noise_buffer_set_input(mbuf, frame.msg.data() + 1, frame.msg.size() - 1);
+ err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
+ if (err != 0) {
+ state_ = State::FAILED;
+ HELPER_LOG("noise_handshakestate_read_message failed: %s", noise_err_to_str(err).c_str());
+ if (err == NOISE_ERROR_MAC_FAILURE) {
+ send_explicit_handshake_reject_("Handshake MAC failure");
+ } else {
+ send_explicit_handshake_reject_("Handshake error");
+ }
+ return APIError::HANDSHAKESTATE_READ_FAILED;
+ }
+
+ aerr = check_handshake_finished_();
+ if (aerr != APIError::OK)
+ return aerr;
+ } else if (action == NOISE_ACTION_WRITE_MESSAGE) {
+ uint8_t buffer[65];
+ NoiseBuffer mbuf;
+ noise_buffer_init(mbuf);
+ noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1);
+
+ err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr);
+ if (err != 0) {
+ state_ = State::FAILED;
+ HELPER_LOG("noise_handshakestate_write_message failed: %s", noise_err_to_str(err).c_str());
+ return APIError::HANDSHAKESTATE_WRITE_FAILED;
+ }
+ buffer[0] = 0x00; // success
+
+ aerr = write_frame_(buffer, mbuf.size + 1);
+ if (aerr != APIError::OK)
+ return aerr;
+ aerr = check_handshake_finished_();
+ if (aerr != APIError::OK)
+ return aerr;
+ } else {
+ // bad state for action
+ state_ = State::FAILED;
+ HELPER_LOG("Bad action for handshake: %d", action);
+ return APIError::HANDSHAKESTATE_BAD_STATE;
+ }
+ }
+ if (state_ == State::CLOSED || state_ == State::FAILED) {
+ return APIError::BAD_STATE;
+ }
+ return APIError::OK;
+}
+void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &reason) {
+ std::vector data;
+ data.resize(reason.length() + 1);
+ data[0] = 0x01; // failure
+ for (size_t i = 0; i < reason.length(); i++) {
+ data[i + 1] = (uint8_t) reason[i];
+ }
+ // temporarily remove failed state
+ auto orig_state = state_;
+ state_ = State::EXPLICIT_REJECT;
+ write_frame_(data.data(), data.size());
+ state_ = orig_state;
+}
+
+APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
+ int err;
+ APIError aerr;
+ aerr = state_action_();
+ if (aerr != APIError::OK) {
+ return aerr;
+ }
+
+ if (state_ != State::DATA) {
+ return APIError::WOULD_BLOCK;
+ }
+
+ ParsedFrame frame;
+ aerr = try_read_frame_(&frame);
+ if (aerr != APIError::OK)
+ return aerr;
+
+ NoiseBuffer mbuf;
+ noise_buffer_init(mbuf);
+ noise_buffer_set_inout(mbuf, frame.msg.data(), frame.msg.size(), frame.msg.size());
+ err = noise_cipherstate_decrypt(recv_cipher_, &mbuf);
+ if (err != 0) {
+ state_ = State::FAILED;
+ HELPER_LOG("noise_cipherstate_decrypt failed: %s", noise_err_to_str(err).c_str());
+ return APIError::CIPHERSTATE_DECRYPT_FAILED;
+ }
+
+ size_t msg_size = mbuf.size;
+ uint8_t *msg_data = frame.msg.data();
+ if (msg_size < 4) {
+ state_ = State::FAILED;
+ HELPER_LOG("Bad data packet: size %d too short", msg_size);
+ return APIError::BAD_DATA_PACKET;
+ }
+
+ // uint16_t type;
+ // uint16_t data_len;
+ // uint8_t *data;
+ // uint8_t *padding; zero or more bytes to fill up the rest of the packet
+ uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1];
+ uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3];
+ if (data_len > msg_size - 4) {
+ state_ = State::FAILED;
+ HELPER_LOG("Bad data packet: data_len %u greater than msg_size %u", data_len, msg_size);
+ return APIError::BAD_DATA_PACKET;
+ }
+
+ buffer->container = std::move(frame.msg);
+ buffer->data_offset = 4;
+ buffer->data_len = data_len;
+ buffer->type = type;
+ return APIError::OK;
+}
+bool APINoiseFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
+APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
+ int err;
+ APIError aerr;
+ aerr = state_action_();
+ if (aerr != APIError::OK) {
+ return aerr;
+ }
+
+ if (state_ != State::DATA) {
+ return APIError::WOULD_BLOCK;
+ }
+
+ size_t padding = 0;
+ size_t msg_len = 4 + payload_len + padding;
+ size_t frame_len = 3 + msg_len + noise_cipherstate_get_mac_length(send_cipher_);
+ auto tmpbuf = std::unique_ptr{new (std::nothrow) uint8_t[frame_len]};
+ if (tmpbuf == nullptr) {
+ HELPER_LOG("Could not allocate for writing packet");
+ return APIError::OUT_OF_MEMORY;
+ }
+
+ tmpbuf[0] = 0x01; // indicator
+ // tmpbuf[1], tmpbuf[2] to be set later
+ const uint8_t msg_offset = 3;
+ const uint8_t payload_offset = msg_offset + 4;
+ tmpbuf[msg_offset + 0] = (uint8_t)(type >> 8); // type
+ tmpbuf[msg_offset + 1] = (uint8_t) type;
+ tmpbuf[msg_offset + 2] = (uint8_t)(payload_len >> 8); // data_len
+ tmpbuf[msg_offset + 3] = (uint8_t) payload_len;
+ // copy data
+ std::copy(payload, payload + payload_len, &tmpbuf[payload_offset]);
+ // fill padding with zeros
+ std::fill(&tmpbuf[payload_offset + payload_len], &tmpbuf[frame_len], 0);
+
+ NoiseBuffer mbuf;
+ noise_buffer_init(mbuf);
+ noise_buffer_set_inout(mbuf, &tmpbuf[msg_offset], msg_len, frame_len - msg_offset);
+ err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
+ if (err != 0) {
+ state_ = State::FAILED;
+ HELPER_LOG("noise_cipherstate_encrypt failed: %s", noise_err_to_str(err).c_str());
+ return APIError::CIPHERSTATE_ENCRYPT_FAILED;
+ }
+
+ size_t total_len = 3 + mbuf.size;
+ tmpbuf[1] = (uint8_t)(mbuf.size >> 8);
+ tmpbuf[2] = (uint8_t) mbuf.size;
+
+ struct iovec iov;
+ iov.iov_base = &tmpbuf[0];
+ iov.iov_len = total_len;
+
+ // write raw to not have two packets sent if NAGLE disabled
+ return write_raw_(&iov, 1);
+}
+APIError APINoiseFrameHelper::try_send_tx_buf_() {
+ // try send from tx_buf
+ while (state_ != State::CLOSED && !tx_buf_.empty()) {
+ ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size());
+ if (sent == -1) {
+ if (errno == EWOULDBLOCK || errno == EAGAIN)
+ break;
+ state_ = State::FAILED;
+ HELPER_LOG("Socket write failed with errno %d", errno);
+ return APIError::SOCKET_WRITE_FAILED;
+ } else if (sent == 0) {
+ break;
+ }
+ // TODO: inefficient if multiple packets in txbuf
+ // replace with deque of buffers
+ tx_buf_.erase(tx_buf_.begin(), tx_buf_.begin() + sent);
+ }
+
+ return APIError::OK;
+}
+/** Write the data to the socket, or buffer it a write would block
+ *
+ * @param data The data to write
+ * @param len The length of data
+ */
+APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
+ if (iovcnt == 0)
+ return APIError::OK;
+ APIError aerr;
+
+ size_t total_write_len = 0;
+ for (int i = 0; i < iovcnt; i++) {
+#ifdef HELPER_LOG_PACKETS
+ ESP_LOGVV(TAG, "Sending raw: %s",
+ format_hex_pretty(reinterpret_cast(iov[i].iov_base), iov[i].iov_len).c_str());
+#endif
+ total_write_len += iov[i].iov_len;
+ }
+
+ if (!tx_buf_.empty()) {
+ // try to empty tx_buf_ first
+ aerr = try_send_tx_buf_();
+ if (aerr != APIError::OK && aerr != APIError::WOULD_BLOCK)
+ return aerr;
+ }
+
+ if (!tx_buf_.empty()) {
+ // tx buf not empty, can't write now because then stream would be inconsistent
+ for (int i = 0; i < iovcnt; i++) {
+ tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base),
+ reinterpret_cast(iov[i].iov_base) + iov[i].iov_len);
+ }
+ return APIError::OK;
+ }
+
+ ssize_t sent = socket_->writev(iov, iovcnt);
+ if (is_would_block(sent)) {
+ // operation would block, add buffer to tx_buf
+ for (int i = 0; i < iovcnt; i++) {
+ tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base),
+ reinterpret_cast(iov[i].iov_base) + iov[i].iov_len);
+ }
+ return APIError::OK;
+ } else if (sent == -1) {
+ // an error occured
+ state_ = State::FAILED;
+ HELPER_LOG("Socket write failed with errno %d", errno);
+ return APIError::SOCKET_WRITE_FAILED;
+ } else if ((size_t) sent != total_write_len) {
+ // partially sent, add end to tx_buf
+ size_t to_consume = sent;
+ for (int i = 0; i < iovcnt; i++) {
+ if (to_consume >= iov[i].iov_len) {
+ to_consume -= iov[i].iov_len;
+ } else {
+ tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base) + to_consume,
+ reinterpret_cast(iov[i].iov_base) + iov[i].iov_len);
+ to_consume = 0;
+ }
+ }
+ return APIError::OK;
+ }
+ // fully sent
+ return APIError::OK;
+}
+APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
+ uint8_t header[3];
+ header[0] = 0x01; // indicator
+ header[1] = (uint8_t)(len >> 8);
+ header[2] = (uint8_t) len;
+
+ struct iovec iov[2];
+ iov[0].iov_base = header;
+ iov[0].iov_len = 3;
+ iov[1].iov_base = const_cast(data);
+ iov[1].iov_len = len;
+
+ return write_raw_(iov, 2);
+}
+
+/** Initiate the data structures for the handshake.
+ *
+ * @return 0 on success, -1 on error (check errno)
+ */
+APIError APINoiseFrameHelper::init_handshake_() {
+ int err;
+ memset(&nid_, 0, sizeof(nid_));
+ // const char *proto = "Noise_NNpsk0_25519_ChaChaPoly_SHA256";
+ // err = noise_protocol_name_to_id(&nid_, proto, strlen(proto));
+ nid_.pattern_id = NOISE_PATTERN_NN;
+ nid_.cipher_id = NOISE_CIPHER_CHACHAPOLY;
+ nid_.dh_id = NOISE_DH_CURVE25519;
+ nid_.prefix_id = NOISE_PREFIX_STANDARD;
+ nid_.hybrid_id = NOISE_DH_NONE;
+ nid_.hash_id = NOISE_HASH_SHA256;
+ nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0;
+
+ err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER);
+ if (err != 0) {
+ state_ = State::FAILED;
+ HELPER_LOG("noise_handshakestate_new_by_id failed: %s", noise_err_to_str(err).c_str());
+ return APIError::HANDSHAKESTATE_SETUP_FAILED;
+ }
+
+ const auto &psk = ctx_->get_psk();
+ err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
+ if (err != 0) {
+ state_ = State::FAILED;
+ HELPER_LOG("noise_handshakestate_set_pre_shared_key failed: %s", noise_err_to_str(err).c_str());
+ return APIError::HANDSHAKESTATE_SETUP_FAILED;
+ }
+
+ err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size());
+ if (err != 0) {
+ state_ = State::FAILED;
+ HELPER_LOG("noise_handshakestate_set_prologue failed: %s", noise_err_to_str(err).c_str());
+ return APIError::HANDSHAKESTATE_SETUP_FAILED;
+ }
+ // set_prologue copies it into handshakestate, so we can get rid of it now
+ prologue_ = {};
+
+ err = noise_handshakestate_start(handshake_);
+ if (err != 0) {
+ state_ = State::FAILED;
+ HELPER_LOG("noise_handshakestate_start failed: %s", noise_err_to_str(err).c_str());
+ return APIError::HANDSHAKESTATE_SETUP_FAILED;
+ }
+ return APIError::OK;
+}
+
+APIError APINoiseFrameHelper::check_handshake_finished_() {
+ assert(state_ == State::HANDSHAKE);
+
+ int action = noise_handshakestate_get_action(handshake_);
+ if (action == NOISE_ACTION_READ_MESSAGE || action == NOISE_ACTION_WRITE_MESSAGE)
+ return APIError::OK;
+ if (action != NOISE_ACTION_SPLIT) {
+ state_ = State::FAILED;
+ HELPER_LOG("Bad action for handshake: %d", action);
+ return APIError::HANDSHAKESTATE_BAD_STATE;
+ }
+ int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_);
+ if (err != 0) {
+ state_ = State::FAILED;
+ HELPER_LOG("noise_handshakestate_split failed: %s", noise_err_to_str(err).c_str());
+ return APIError::HANDSHAKESTATE_SPLIT_FAILED;
+ }
+
+ HELPER_LOG("Handshake complete!");
+ noise_handshakestate_free(handshake_);
+ handshake_ = nullptr;
+ state_ = State::DATA;
+ return APIError::OK;
+}
+
+APINoiseFrameHelper::~APINoiseFrameHelper() {
+ if (handshake_ != nullptr) {
+ noise_handshakestate_free(handshake_);
+ handshake_ = nullptr;
+ }
+ if (send_cipher_ != nullptr) {
+ noise_cipherstate_free(send_cipher_);
+ send_cipher_ = nullptr;
+ }
+ if (recv_cipher_ != nullptr) {
+ noise_cipherstate_free(recv_cipher_);
+ recv_cipher_ = nullptr;
+ }
+}
+
+APIError APINoiseFrameHelper::close() {
+ state_ = State::CLOSED;
+ int err = socket_->close();
+ if (err == -1)
+ return APIError::CLOSE_FAILED;
+ return APIError::OK;
+}
+APIError APINoiseFrameHelper::shutdown(int how) {
+ int err = socket_->shutdown(how);
+ if (err == -1)
+ return APIError::SHUTDOWN_FAILED;
+ if (how == SHUT_RDWR) {
+ state_ = State::CLOSED;
+ }
+ return APIError::OK;
+}
+extern "C" {
+// declare how noise generates random bytes (here with a good HWRNG based on the RF system)
+void noise_rand_bytes(void *output, size_t len) { esphome::fill_random(reinterpret_cast(output), len); }
+}
+#endif // USE_API_NOISE
+
+#ifdef USE_API_PLAINTEXT
+
+/// Initialize the frame helper, returns OK if successful.
+APIError APIPlaintextFrameHelper::init() {
+ if (state_ != State::INITIALIZE || socket_ == nullptr) {
+ HELPER_LOG("Bad state for init %d", (int) state_);
+ return APIError::BAD_STATE;
+ }
+ int err = socket_->setblocking(false);
+ if (err != 0) {
+ state_ = State::FAILED;
+ HELPER_LOG("Setting nonblocking failed with errno %d", errno);
+ return APIError::TCP_NONBLOCKING_FAILED;
+ }
+ int enable = 1;
+ err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
+ if (err != 0) {
+ state_ = State::FAILED;
+ HELPER_LOG("Setting nodelay failed with errno %d", errno);
+ return APIError::TCP_NODELAY_FAILED;
+ }
+
+ state_ = State::DATA;
+ return APIError::OK;
+}
+/// Not used for plaintext
+APIError APIPlaintextFrameHelper::loop() {
+ if (state_ != State::DATA) {
+ return APIError::BAD_STATE;
+ }
+ // try send pending TX data
+ if (!tx_buf_.empty()) {
+ APIError err = try_send_tx_buf_();
+ if (err != APIError::OK) {
+ return err;
+ }
+ }
+ return APIError::OK;
+}
+
+/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
+ *
+ * @param frame: The struct to hold the frame information in.
+ * msg: store the parsed frame in that struct
+ *
+ * @return See APIError
+ *
+ * error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
+ */
+APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
+ if (frame == nullptr) {
+ HELPER_LOG("Bad argument for try_read_frame_");
+ return APIError::BAD_ARG;
+ }
+
+ // read header
+ while (!rx_header_parsed_) {
+ uint8_t data;
+ ssize_t received = socket_->read(&data, 1);
+ if (received == -1) {
+ if (errno == EWOULDBLOCK || errno == EAGAIN) {
+ return APIError::WOULD_BLOCK;
+ }
+ state_ = State::FAILED;
+ HELPER_LOG("Socket read failed with errno %d", errno);
+ return APIError::SOCKET_READ_FAILED;
+ } else if (received == 0) {
+ state_ = State::FAILED;
+ HELPER_LOG("Connection closed");
+ return APIError::CONNECTION_CLOSED;
+ }
+ rx_header_buf_.push_back(data);
+
+ // try parse header
+ if (rx_header_buf_[0] != 0x00) {
+ state_ = State::FAILED;
+ HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
+ return APIError::BAD_INDICATOR;
+ }
+
+ size_t i = 1;
+ uint32_t consumed = 0;
+ auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed);
+ if (!msg_size_varint.has_value()) {
+ // not enough data there yet
+ continue;
+ }
+
+ i += consumed;
+ rx_header_parsed_len_ = msg_size_varint->as_uint32();
+
+ auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed);
+ if (!msg_type_varint.has_value()) {
+ // not enough data there yet
+ continue;
+ }
+ rx_header_parsed_type_ = msg_type_varint->as_uint32();
+ rx_header_parsed_ = true;
+ }
+ // header reading done
+
+ // reserve space for body
+ if (rx_buf_.size() != rx_header_parsed_len_) {
+ rx_buf_.resize(rx_header_parsed_len_);
+ }
+
+ if (rx_buf_len_ < rx_header_parsed_len_) {
+ // more data to read
+ size_t to_read = rx_header_parsed_len_ - rx_buf_len_;
+ ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read);
+ if (received == -1) {
+ if (errno == EWOULDBLOCK || errno == EAGAIN) {
+ return APIError::WOULD_BLOCK;
+ }
+ state_ = State::FAILED;
+ HELPER_LOG("Socket read failed with errno %d", errno);
+ return APIError::SOCKET_READ_FAILED;
+ } else if (received == 0) {
+ state_ = State::FAILED;
+ HELPER_LOG("Connection closed");
+ return APIError::CONNECTION_CLOSED;
+ }
+ rx_buf_len_ += received;
+ if ((size_t) received != to_read) {
+ // not all read
+ return APIError::WOULD_BLOCK;
+ }
+ }
+
+ // uncomment for even more debugging
+#ifdef HELPER_LOG_PACKETS
+ ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str());
+#endif
+ frame->msg = std::move(rx_buf_);
+ // consume msg
+ rx_buf_ = {};
+ rx_buf_len_ = 0;
+ rx_header_buf_.clear();
+ rx_header_parsed_ = false;
+ return APIError::OK;
+}
+
+APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
+ APIError aerr;
+
+ if (state_ != State::DATA) {
+ return APIError::WOULD_BLOCK;
+ }
+
+ ParsedFrame frame;
+ aerr = try_read_frame_(&frame);
+ if (aerr != APIError::OK)
+ return aerr;
+
+ buffer->container = std::move(frame.msg);
+ buffer->data_offset = 0;
+ buffer->data_len = rx_header_parsed_len_;
+ buffer->type = rx_header_parsed_type_;
+ return APIError::OK;
+}
+bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
+APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
+ if (state_ != State::DATA) {
+ return APIError::BAD_STATE;
+ }
+
+ std::vector header;
+ header.push_back(0x00);
+ ProtoVarInt(payload_len).encode(header);
+ ProtoVarInt(type).encode(header);
+
+ struct iovec iov[2];
+ iov[0].iov_base = &header[0];
+ iov[0].iov_len = header.size();
+ iov[1].iov_base = const_cast(payload);
+ iov[1].iov_len = payload_len;
+
+ return write_raw_(iov, 2);
+}
+APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
+ // try send from tx_buf
+ while (state_ != State::CLOSED && !tx_buf_.empty()) {
+ ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size());
+ if (is_would_block(sent)) {
+ break;
+ } else if (sent == -1) {
+ state_ = State::FAILED;
+ HELPER_LOG("Socket write failed with errno %d", errno);
+ return APIError::SOCKET_WRITE_FAILED;
+ }
+ // TODO: inefficient if multiple packets in txbuf
+ // replace with deque of buffers
+ tx_buf_.erase(tx_buf_.begin(), tx_buf_.begin() + sent);
+ }
+
+ return APIError::OK;
+}
+/** Write the data to the socket, or buffer it a write would block
+ *
+ * @param data The data to write
+ * @param len The length of data
+ */
+APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
+ if (iovcnt == 0)
+ return APIError::OK;
+ APIError aerr;
+
+ size_t total_write_len = 0;
+ for (int i = 0; i < iovcnt; i++) {
+#ifdef HELPER_LOG_PACKETS
+ ESP_LOGVV(TAG, "Sending raw: %s",
+ format_hex_pretty(reinterpret_cast(iov[i].iov_base), iov[i].iov_len).c_str());
+#endif
+ total_write_len += iov[i].iov_len;
+ }
+
+ if (!tx_buf_.empty()) {
+ // try to empty tx_buf_ first
+ aerr = try_send_tx_buf_();
+ if (aerr != APIError::OK && aerr != APIError::WOULD_BLOCK)
+ return aerr;
+ }
+
+ if (!tx_buf_.empty()) {
+ // tx buf not empty, can't write now because then stream would be inconsistent
+ for (int i = 0; i < iovcnt; i++) {
+ tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base),
+ reinterpret_cast(iov[i].iov_base) + iov[i].iov_len);
+ }
+ return APIError::OK;
+ }
+
+ ssize_t sent = socket_->writev(iov, iovcnt);
+ if (is_would_block(sent)) {
+ // operation would block, add buffer to tx_buf
+ for (int i = 0; i < iovcnt; i++) {
+ tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base),
+ reinterpret_cast(iov[i].iov_base) + iov[i].iov_len);
+ }
+ return APIError::OK;
+ } else if (sent == -1) {
+ // an error occured
+ state_ = State::FAILED;
+ HELPER_LOG("Socket write failed with errno %d", errno);
+ return APIError::SOCKET_WRITE_FAILED;
+ } else if ((size_t) sent != total_write_len) {
+ // partially sent, add end to tx_buf
+ size_t to_consume = sent;
+ for (int i = 0; i < iovcnt; i++) {
+ if (to_consume >= iov[i].iov_len) {
+ to_consume -= iov[i].iov_len;
+ } else {
+ tx_buf_.insert(tx_buf_.end(), reinterpret_cast(iov[i].iov_base) + to_consume,
+ reinterpret_cast(iov[i].iov_base) + iov[i].iov_len);
+ to_consume = 0;
+ }
+ }
+ return APIError::OK;
+ }
+ // fully sent
+ return APIError::OK;
+}
+
+APIError APIPlaintextFrameHelper::close() {
+ state_ = State::CLOSED;
+ int err = socket_->close();
+ if (err == -1)
+ return APIError::CLOSE_FAILED;
+ return APIError::OK;
+}
+APIError APIPlaintextFrameHelper::shutdown(int how) {
+ int err = socket_->shutdown(how);
+ if (err == -1)
+ return APIError::SHUTDOWN_FAILED;
+ if (how == SHUT_RDWR) {
+ state_ = State::CLOSED;
+ }
+ return APIError::OK;
+}
+#endif // USE_API_PLAINTEXT
+
+} // namespace api
+} // namespace esphome
diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h
new file mode 100644
index 0000000000..57e3c961d5
--- /dev/null
+++ b/esphome/components/api/api_frame_helper.h
@@ -0,0 +1,185 @@
+#pragma once
+#include
+#include
+#include
+#include
+
+#include "esphome/core/defines.h"
+
+#ifdef USE_API_NOISE
+#include "noise/protocol.h"
+#endif
+
+#include "esphome/components/socket/socket.h"
+#include "api_noise_context.h"
+
+namespace esphome {
+namespace api {
+
+struct ReadPacketBuffer {
+ std::vector container;
+ uint16_t type;
+ size_t data_offset;
+ size_t data_len;
+};
+
+struct PacketBuffer {
+ const std::vector container;
+ uint16_t type;
+ uint8_t data_offset;
+ uint8_t data_len;
+};
+
+enum class APIError : int {
+ OK = 0,
+ WOULD_BLOCK = 1001,
+ BAD_HANDSHAKE_PACKET_LEN = 1002,
+ BAD_INDICATOR = 1003,
+ BAD_DATA_PACKET = 1004,
+ TCP_NODELAY_FAILED = 1005,
+ TCP_NONBLOCKING_FAILED = 1006,
+ CLOSE_FAILED = 1007,
+ SHUTDOWN_FAILED = 1008,
+ BAD_STATE = 1009,
+ BAD_ARG = 1010,
+ SOCKET_READ_FAILED = 1011,
+ SOCKET_WRITE_FAILED = 1012,
+ HANDSHAKESTATE_READ_FAILED = 1013,
+ HANDSHAKESTATE_WRITE_FAILED = 1014,
+ HANDSHAKESTATE_BAD_STATE = 1015,
+ CIPHERSTATE_DECRYPT_FAILED = 1016,
+ CIPHERSTATE_ENCRYPT_FAILED = 1017,
+ OUT_OF_MEMORY = 1018,
+ HANDSHAKESTATE_SETUP_FAILED = 1019,
+ HANDSHAKESTATE_SPLIT_FAILED = 1020,
+ BAD_HANDSHAKE_ERROR_BYTE = 1021,
+ CONNECTION_CLOSED = 1022,
+};
+
+const char *api_error_to_str(APIError err);
+
+class APIFrameHelper {
+ public:
+ virtual ~APIFrameHelper() = default;
+ virtual APIError init() = 0;
+ virtual APIError loop() = 0;
+ virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
+ virtual bool can_write_without_blocking() = 0;
+ virtual APIError write_packet(uint16_t type, const uint8_t *data, size_t len) = 0;
+ virtual std::string getpeername() = 0;
+ virtual APIError close() = 0;
+ virtual APIError shutdown(int how) = 0;
+ // Give this helper a name for logging
+ virtual void set_log_info(std::string info) = 0;
+};
+
+#ifdef USE_API_NOISE
+class APINoiseFrameHelper : public APIFrameHelper {
+ public:
+ APINoiseFrameHelper(std::unique_ptr socket, std::shared_ptr ctx)
+ : socket_(std::move(socket)), ctx_(std::move(std::move(ctx))) {}
+ ~APINoiseFrameHelper() override;
+ APIError init() override;
+ APIError loop() override;
+ APIError read_packet(ReadPacketBuffer *buffer) override;
+ bool can_write_without_blocking() override;
+ APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
+ std::string getpeername() override { return socket_->getpeername(); }
+ APIError close() override;
+ APIError shutdown(int how) override;
+ // Give this helper a name for logging
+ void set_log_info(std::string info) override { info_ = std::move(info); }
+
+ protected:
+ struct ParsedFrame {
+ std::vector msg;
+ };
+
+ APIError state_action_();
+ APIError try_read_frame_(ParsedFrame *frame);
+ APIError try_send_tx_buf_();
+ APIError write_frame_(const uint8_t *data, size_t len);
+ APIError write_raw_(const struct iovec *iov, int iovcnt);
+ APIError init_handshake_();
+ APIError check_handshake_finished_();
+ void send_explicit_handshake_reject_(const std::string &reason);
+
+ std::unique_ptr socket_;
+
+ std::string info_;
+ uint8_t rx_header_buf_[3];
+ size_t rx_header_buf_len_ = 0;
+ std::vector rx_buf_;
+ size_t rx_buf_len_ = 0;
+
+ std::vector tx_buf_;
+ std::vector prologue_;
+
+ std::shared_ptr ctx_;
+ NoiseHandshakeState *handshake_ = nullptr;
+ NoiseCipherState *send_cipher_ = nullptr;
+ NoiseCipherState *recv_cipher_ = nullptr;
+ NoiseProtocolId nid_;
+
+ enum class State {
+ INITIALIZE = 1,
+ CLIENT_HELLO = 2,
+ SERVER_HELLO = 3,
+ HANDSHAKE = 4,
+ DATA = 5,
+ CLOSED = 6,
+ FAILED = 7,
+ EXPLICIT_REJECT = 8,
+ } state_ = State::INITIALIZE;
+};
+#endif // USE_API_NOISE
+
+#ifdef USE_API_PLAINTEXT
+class APIPlaintextFrameHelper : public APIFrameHelper {
+ public:
+ APIPlaintextFrameHelper(std::unique_ptr socket) : socket_(std::move(socket)) {}
+ ~APIPlaintextFrameHelper() override = default;
+ APIError init() override;
+ APIError loop() override;
+ APIError read_packet(ReadPacketBuffer *buffer) override;
+ bool can_write_without_blocking() override;
+ APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
+ std::string getpeername() override { return socket_->getpeername(); }
+ APIError close() override;
+ APIError shutdown(int how) override;
+ // Give this helper a name for logging
+ void set_log_info(std::string info) override { info_ = std::move(info); }
+
+ protected:
+ struct ParsedFrame {
+ std::vector msg;
+ };
+
+ APIError try_read_frame_(ParsedFrame *frame);
+ APIError try_send_tx_buf_();
+ APIError write_raw_(const struct iovec *iov, int iovcnt);
+
+ std::unique_ptr socket_;
+
+ std::string info_;
+ std::vector rx_header_buf_;
+ bool rx_header_parsed_ = false;
+ uint32_t rx_header_parsed_type_ = 0;
+ uint32_t rx_header_parsed_len_ = 0;
+
+ std::vector rx_buf_;
+ size_t rx_buf_len_ = 0;
+
+ std::vector tx_buf_;
+
+ enum class State {
+ INITIALIZE = 1,
+ DATA = 2,
+ CLOSED = 3,
+ FAILED = 4,
+ } state_ = State::INITIALIZE;
+};
+#endif
+
+} // namespace api
+} // namespace esphome
diff --git a/esphome/components/api/api_noise_context.h b/esphome/components/api/api_noise_context.h
new file mode 100644
index 0000000000..324e69d945
--- /dev/null
+++ b/esphome/components/api/api_noise_context.h
@@ -0,0 +1,23 @@
+#pragma once
+#include
+#include
+#include "esphome/core/defines.h"
+
+namespace esphome {
+namespace api {
+
+#ifdef USE_API_NOISE
+using psk_t = std::array;
+
+class APINoiseContext {
+ public:
+ void set_psk(psk_t psk) { psk_ = psk; }
+ const psk_t &get_psk() const { return psk_; }
+
+ protected:
+ psk_t psk_;
+};
+#endif // USE_API_NOISE
+
+} // namespace api
+} // namespace esphome
diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp
index 6b98f95f53..5b6853c276 100644
--- a/esphome/components/api/api_pb2.cpp
+++ b/esphome/components/api/api_pb2.cpp
@@ -6,6 +6,18 @@
namespace esphome {
namespace api {
+template<> const char *proto_enum_to_string(enums::EntityCategory value) {
+ switch (value) {
+ case enums::ENTITY_CATEGORY_NONE:
+ return "ENTITY_CATEGORY_NONE";
+ case enums::ENTITY_CATEGORY_CONFIG:
+ return "ENTITY_CATEGORY_CONFIG";
+ case enums::ENTITY_CATEGORY_DIAGNOSTIC:
+ return "ENTITY_CATEGORY_DIAGNOSTIC";
+ default:
+ return "UNKNOWN";
+ }
+}
template<> const char *proto_enum_to_string(enums::LegacyCoverState value) {
switch (value) {
case enums::LEGACY_COVER_STATE_OPEN:
@@ -52,6 +64,66 @@ template<> const char *proto_enum_to_string(enums::FanSpeed val
return "UNKNOWN";
}
}
+template<> const char *proto_enum_to_string(enums::FanDirection value) {
+ switch (value) {
+ case enums::FAN_DIRECTION_FORWARD:
+ return "FAN_DIRECTION_FORWARD";
+ case enums::FAN_DIRECTION_REVERSE:
+ return "FAN_DIRECTION_REVERSE";
+ default:
+ return "UNKNOWN";
+ }
+}
+template<> const char *proto_enum_to_string(enums::ColorMode value) {
+ switch (value) {
+ case enums::COLOR_MODE_UNKNOWN:
+ return "COLOR_MODE_UNKNOWN";
+ case enums::COLOR_MODE_ON_OFF:
+ return "COLOR_MODE_ON_OFF";
+ case enums::COLOR_MODE_BRIGHTNESS:
+ return "COLOR_MODE_BRIGHTNESS";
+ case enums::COLOR_MODE_WHITE:
+ return "COLOR_MODE_WHITE";
+ case enums::COLOR_MODE_COLOR_TEMPERATURE:
+ return "COLOR_MODE_COLOR_TEMPERATURE";
+ case enums::COLOR_MODE_COLD_WARM_WHITE:
+ return "COLOR_MODE_COLD_WARM_WHITE";
+ case enums::COLOR_MODE_RGB:
+ return "COLOR_MODE_RGB";
+ case enums::COLOR_MODE_RGB_WHITE:
+ return "COLOR_MODE_RGB_WHITE";
+ case enums::COLOR_MODE_RGB_COLOR_TEMPERATURE:
+ return "COLOR_MODE_RGB_COLOR_TEMPERATURE";
+ case enums::COLOR_MODE_RGB_COLD_WARM_WHITE:
+ return "COLOR_MODE_RGB_COLD_WARM_WHITE";
+ default:
+ return "UNKNOWN";
+ }
+}
+template<> const char *proto_enum_to_string(enums::SensorStateClass value) {
+ switch (value) {
+ case enums::STATE_CLASS_NONE:
+ return "STATE_CLASS_NONE";
+ case enums::STATE_CLASS_MEASUREMENT:
+ return "STATE_CLASS_MEASUREMENT";
+ case enums::STATE_CLASS_TOTAL_INCREASING:
+ return "STATE_CLASS_TOTAL_INCREASING";
+ default:
+ return "UNKNOWN";
+ }
+}
+template<> const char *proto_enum_to_string(enums::SensorLastResetType value) {
+ switch (value) {
+ case enums::LAST_RESET_NONE:
+ return "LAST_RESET_NONE";
+ case enums::LAST_RESET_NEVER:
+ return "LAST_RESET_NEVER";
+ case enums::LAST_RESET_AUTO:
+ return "LAST_RESET_AUTO";
+ default:
+ return "UNKNOWN";
+ }
+}
template<> const char *proto_enum_to_string(enums::LogLevel value) {
switch (value) {
case enums::LOG_LEVEL_NONE:
@@ -62,6 +134,8 @@ template<> const char *proto_enum_to_string(enums::LogLevel val
return "LOG_LEVEL_WARN";
case enums::LOG_LEVEL_INFO:
return "LOG_LEVEL_INFO";
+ case enums::LOG_LEVEL_CONFIG:
+ return "LOG_LEVEL_CONFIG";
case enums::LOG_LEVEL_DEBUG:
return "LOG_LEVEL_DEBUG";
case enums::LOG_LEVEL_VERBOSE:
@@ -98,8 +172,8 @@ template<> const char *proto_enum_to_string(enums::ClimateMo
switch (value) {
case enums::CLIMATE_MODE_OFF:
return "CLIMATE_MODE_OFF";
- case enums::CLIMATE_MODE_AUTO:
- return "CLIMATE_MODE_AUTO";
+ case enums::CLIMATE_MODE_HEAT_COOL:
+ return "CLIMATE_MODE_HEAT_COOL";
case enums::CLIMATE_MODE_COOL:
return "CLIMATE_MODE_COOL";
case enums::CLIMATE_MODE_HEAT:
@@ -108,6 +182,8 @@ template<> const char *proto_enum_to_string(enums::ClimateMo
return "CLIMATE_MODE_FAN_ONLY";
case enums::CLIMATE_MODE_DRY:
return "CLIMATE_MODE_DRY";
+ case enums::CLIMATE_MODE_AUTO:
+ return "CLIMATE_MODE_AUTO";
default:
return "UNKNOWN";
}
@@ -144,8 +220,8 @@ template<> const char *proto_enum_to_string(enums::Clim
return "CLIMATE_SWING_BOTH";
case enums::CLIMATE_SWING_VERTICAL:
return "CLIMATE_SWING_VERTICAL";
- case enums::CLIMATE_SWINT_HORIZONTAL:
- return "CLIMATE_SWINT_HORIZONTAL";
+ case enums::CLIMATE_SWING_HORIZONTAL:
+ return "CLIMATE_SWING_HORIZONTAL";
default:
return "UNKNOWN";
}
@@ -168,6 +244,40 @@ template<> const char *proto_enum_to_string(enums::Climate
return "UNKNOWN";
}
}
+template<> const char *proto_enum_to_string(enums::ClimatePreset value) {
+ switch (value) {
+ case enums::CLIMATE_PRESET_NONE:
+ return "CLIMATE_PRESET_NONE";
+ case enums::CLIMATE_PRESET_HOME:
+ return "CLIMATE_PRESET_HOME";
+ case enums::CLIMATE_PRESET_AWAY:
+ return "CLIMATE_PRESET_AWAY";
+ case enums::CLIMATE_PRESET_BOOST:
+ return "CLIMATE_PRESET_BOOST";
+ case enums::CLIMATE_PRESET_COMFORT:
+ return "CLIMATE_PRESET_COMFORT";
+ case enums::CLIMATE_PRESET_ECO:
+ return "CLIMATE_PRESET_ECO";
+ case enums::CLIMATE_PRESET_SLEEP:
+ return "CLIMATE_PRESET_SLEEP";
+ case enums::CLIMATE_PRESET_ACTIVITY:
+ return "CLIMATE_PRESET_ACTIVITY";
+ default:
+ return "UNKNOWN";
+ }
+}
+template<> const char *proto_enum_to_string(enums::NumberMode value) {
+ switch (value) {
+ case enums::NUMBER_MODE_AUTO:
+ return "NUMBER_MODE_AUTO";
+ case enums::NUMBER_MODE_BOX:
+ return "NUMBER_MODE_BOX";
+ case enums::NUMBER_MODE_SLIDER:
+ return "NUMBER_MODE_SLIDER";
+ default:
+ return "UNKNOWN";
+ }
+}
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
@@ -179,14 +289,16 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value)
}
}
void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); }
+#ifdef HAS_PROTO_MESSAGE_DUMP
void HelloRequest::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("HelloRequest {\n");
out.append(" client_info: ");
out.append("'").append(this->client_info).append("'");
out.append("\n");
out.append("}");
}
+#endif
bool HelloResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
@@ -216,8 +328,9 @@ void HelloResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(2, this->api_version_minor);
buffer.encode_string(3, this->server_info);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void HelloResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("HelloResponse {\n");
out.append(" api_version_major: ");
sprintf(buffer, "%u", this->api_version_major);
@@ -234,6 +347,7 @@ void HelloResponse::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
+#endif
bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
@@ -245,14 +359,16 @@ bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value
}
}
void ConnectRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->password); }
+#ifdef HAS_PROTO_MESSAGE_DUMP
void ConnectRequest::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("ConnectRequest {\n");
out.append(" password: ");
out.append("'").append(this->password).append("'");
out.append("\n");
out.append("}");
}
+#endif
bool ConnectResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
@@ -264,24 +380,36 @@ bool ConnectResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
}
}
void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); }
+#ifdef HAS_PROTO_MESSAGE_DUMP
void ConnectResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("ConnectResponse {\n");
out.append(" invalid_password: ");
out.append(YESNO(this->invalid_password));
out.append("\n");
out.append("}");
}
+#endif
void DisconnectRequest::encode(ProtoWriteBuffer buffer) const {}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void DisconnectRequest::dump_to(std::string &out) const { out.append("DisconnectRequest {}"); }
+#endif
void DisconnectResponse::encode(ProtoWriteBuffer buffer) const {}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void DisconnectResponse::dump_to(std::string &out) const { out.append("DisconnectResponse {}"); }
+#endif
void PingRequest::encode(ProtoWriteBuffer buffer) const {}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void PingRequest::dump_to(std::string &out) const { out.append("PingRequest {}"); }
+#endif
void PingResponse::encode(ProtoWriteBuffer buffer) const {}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void PingResponse::dump_to(std::string &out) const { out.append("PingResponse {}"); }
+#endif
void DeviceInfoRequest::encode(ProtoWriteBuffer buffer) const {}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfoRequest {}"); }
+#endif
bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
@@ -292,6 +420,10 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
this->has_deep_sleep = value.as_bool();
return true;
}
+ case 10: {
+ this->webserver_port = value.as_uint32();
+ return true;
+ }
default:
return false;
}
@@ -318,6 +450,14 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v
this->model = value.as_string();
return true;
}
+ case 8: {
+ this->project_name = value.as_string();
+ return true;
+ }
+ case 9: {
+ this->project_version = value.as_string();
+ return true;
+ }
default:
return false;
}
@@ -330,9 +470,13 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(5, this->compilation_time);
buffer.encode_string(6, this->model);
buffer.encode_bool(7, this->has_deep_sleep);
+ buffer.encode_string(8, this->project_name);
+ buffer.encode_string(9, this->project_version);
+ buffer.encode_uint32(10, this->webserver_port);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void DeviceInfoResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("DeviceInfoResponse {\n");
out.append(" uses_password: ");
out.append(YESNO(this->uses_password));
@@ -361,20 +505,48 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
out.append(" has_deep_sleep: ");
out.append(YESNO(this->has_deep_sleep));
out.append("\n");
+
+ out.append(" project_name: ");
+ out.append("'").append(this->project_name).append("'");
+ out.append("\n");
+
+ out.append(" project_version: ");
+ out.append("'").append(this->project_version).append("'");
+ out.append("\n");
+
+ out.append(" webserver_port: ");
+ sprintf(buffer, "%u", this->webserver_port);
+ out.append(buffer);
+ out.append("\n");
out.append("}");
}
+#endif
void ListEntitiesRequest::encode(ProtoWriteBuffer buffer) const {}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesRequest::dump_to(std::string &out) const { out.append("ListEntitiesRequest {}"); }
+#endif
void ListEntitiesDoneResponse::encode(ProtoWriteBuffer buffer) const {}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesDoneResponse::dump_to(std::string &out) const { out.append("ListEntitiesDoneResponse {}"); }
+#endif
void SubscribeStatesRequest::encode(ProtoWriteBuffer buffer) const {}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeStatesRequest::dump_to(std::string &out) const { out.append("SubscribeStatesRequest {}"); }
+#endif
bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {
this->is_status_binary_sensor = value.as_bool();
return true;
}
+ case 7: {
+ this->disabled_by_default = value.as_bool();
+ return true;
+ }
+ case 9: {
+ this->entity_category = value.as_enum();
+ return true;
+ }
default:
return false;
}
@@ -397,6 +569,10 @@ bool ListEntitiesBinarySensorResponse::decode_length(uint32_t field_id, ProtoLen
this->device_class = value.as_string();
return true;
}
+ case 8: {
+ this->icon = value.as_string();
+ return true;
+ }
default:
return false;
}
@@ -418,9 +594,13 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->device_class);
buffer.encode_bool(6, this->is_status_binary_sensor);
+ buffer.encode_bool(7, this->disabled_by_default);
+ buffer.encode_string(8, this->icon);
+ buffer.encode_enum(9, this->entity_category);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("ListEntitiesBinarySensorResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -446,8 +626,21 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
out.append(" is_status_binary_sensor: ");
out.append(YESNO(this->is_status_binary_sensor));
out.append("\n");
+
+ out.append(" disabled_by_default: ");
+ out.append(YESNO(this->disabled_by_default));
+ out.append("\n");
+
+ out.append(" icon: ");
+ out.append("'").append(this->icon).append("'");
+ out.append("\n");
+
+ out.append(" entity_category: ");
+ out.append(proto_enum_to_string(this->entity_category));
+ out.append("\n");
out.append("}");
}
+#endif
bool BinarySensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@@ -477,8 +670,9 @@ void BinarySensorStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(2, this->state);
buffer.encode_bool(3, this->missing_state);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void BinarySensorStateResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("BinarySensorStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -494,6 +688,7 @@ void BinarySensorStateResponse::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
+#endif
bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 5: {
@@ -508,6 +703,14 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->supports_tilt = value.as_bool();
return true;
}
+ case 9: {
+ this->disabled_by_default = value.as_bool();
+ return true;
+ }
+ case 11: {
+ this->entity_category = value.as_enum();
+ return true;
+ }
default:
return false;
}
@@ -530,6 +733,10 @@ bool ListEntitiesCoverResponse::decode_length(uint32_t field_id, ProtoLengthDeli
this->device_class = value.as_string();
return true;
}
+ case 10: {
+ this->icon = value.as_string();
+ return true;
+ }
default:
return false;
}
@@ -553,9 +760,13 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(6, this->supports_position);
buffer.encode_bool(7, this->supports_tilt);
buffer.encode_string(8, this->device_class);
+ buffer.encode_bool(9, this->disabled_by_default);
+ buffer.encode_string(10, this->icon);
+ buffer.encode_enum(11, this->entity_category);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesCoverResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("ListEntitiesCoverResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -589,8 +800,21 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const {
out.append(" device_class: ");
out.append("'").append(this->device_class).append("'");
out.append("\n");
+
+ out.append(" disabled_by_default: ");
+ out.append(YESNO(this->disabled_by_default));
+ out.append("\n");
+
+ out.append(" icon: ");
+ out.append("'").append(this->icon).append("'");
+ out.append("\n");
+
+ out.append(" entity_category: ");
+ out.append(proto_enum_to_string(this->entity_category));
+ out.append("\n");
out.append("}");
}
+#endif
bool CoverStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@@ -630,8 +854,9 @@ void CoverStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(4, this->tilt);
buffer.encode_enum(5, this->current_operation);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void CoverStateResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("CoverStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -657,6 +882,7 @@ void CoverStateResponse::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
+#endif
bool CoverCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@@ -711,8 +937,9 @@ void CoverCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(7, this->tilt);
buffer.encode_bool(8, this->stop);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void CoverCommandRequest::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("CoverCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -750,6 +977,7 @@ void CoverCommandRequest::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
+#endif
bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 5: {
@@ -760,6 +988,22 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value
this->supports_speed = value.as_bool();
return true;
}
+ case 7: {
+ this->supports_direction = value.as_bool();
+ return true;
+ }
+ case 8: {
+ this->supported_speed_count = value.as_int32();
+ return true;
+ }
+ case 9: {
+ this->disabled_by_default = value.as_bool();
+ return true;
+ }
+ case 11: {
+ this->entity_category = value.as_enum();
+ return true;
+ }
default:
return false;
}
@@ -778,6 +1022,10 @@ bool ListEntitiesFanResponse::decode_length(uint32_t field_id, ProtoLengthDelimi
this->unique_id = value.as_string();
return true;
}
+ case 10: {
+ this->icon = value.as_string();
+ return true;
+ }
default:
return false;
}
@@ -799,9 +1047,15 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(4, this->unique_id);
buffer.encode_bool(5, this->supports_oscillation);
buffer.encode_bool(6, this->supports_speed);
+ buffer.encode_bool(7, this->supports_direction);
+ buffer.encode_int32(8, this->supported_speed_count);
+ buffer.encode_bool(9, this->disabled_by_default);
+ buffer.encode_string(10, this->icon);
+ buffer.encode_enum(11, this->entity_category);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesFanResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("ListEntitiesFanResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -827,8 +1081,30 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
out.append(" supports_speed: ");
out.append(YESNO(this->supports_speed));
out.append("\n");
+
+ out.append(" supports_direction: ");
+ out.append(YESNO(this->supports_direction));
+ out.append("\n");
+
+ out.append(" supported_speed_count: ");
+ sprintf(buffer, "%d", this->supported_speed_count);
+ out.append(buffer);
+ out.append("\n");
+
+ out.append(" disabled_by_default: ");
+ out.append(YESNO(this->disabled_by_default));
+ out.append("\n");
+
+ out.append(" icon: ");
+ out.append("'").append(this->icon).append("'");
+ out.append("\n");
+
+ out.append(" entity_category: ");
+ out.append(proto_enum_to_string(this->entity_category));
+ out.append("\n");
out.append("}");
}
+#endif
bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@@ -843,6 +1119,14 @@ bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
this->speed = value.as_enum();
return true;
}
+ case 5: {
+ this->direction = value.as_enum();
+ return true;
+ }
+ case 6: {
+ this->speed_level = value.as_int32();
+ return true;
+ }
default:
return false;
}
@@ -862,9 +1146,12 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(2, this->state);
buffer.encode_bool(3, this->oscillating);
buffer.encode_enum(4, this->speed);
+ buffer.encode_enum(5, this->direction);
+ buffer.encode_int32(6, this->speed_level);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void FanStateResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("FanStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -882,8 +1169,18 @@ void FanStateResponse::dump_to(std::string &out) const {
out.append(" speed: ");
out.append(proto_enum_to_string(this->speed));
out.append("\n");
+
+ out.append(" direction: ");
+ out.append(proto_enum_to_string(this->direction));
+ out.append("\n");
+
+ out.append(" speed_level: ");
+ sprintf(buffer, "%d", this->speed_level);
+ out.append(buffer);
+ out.append("\n");
out.append("}");
}
+#endif
bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@@ -910,6 +1207,22 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
this->oscillating = value.as_bool();
return true;
}
+ case 8: {
+ this->has_direction = value.as_bool();
+ return true;
+ }
+ case 9: {
+ this->direction = value.as_enum();
+ return true;
+ }
+ case 10: {
+ this->has_speed_level = value.as_bool();
+ return true;
+ }
+ case 11: {
+ this->speed_level = value.as_int32();
+ return true;
+ }
default:
return false;
}
@@ -932,9 +1245,14 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_enum(5, this->speed);
buffer.encode_bool(6, this->has_oscillating);
buffer.encode_bool(7, this->oscillating);
+ buffer.encode_bool(8, this->has_direction);
+ buffer.encode_enum(9, this->direction);
+ buffer.encode_bool(10, this->has_speed_level);
+ buffer.encode_int32(11, this->speed_level);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void FanCommandRequest::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("FanCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -964,24 +1282,54 @@ void FanCommandRequest::dump_to(std::string &out) const {
out.append(" oscillating: ");
out.append(YESNO(this->oscillating));
out.append("\n");
+
+ out.append(" has_direction: ");
+ out.append(YESNO(this->has_direction));
+ out.append("\n");
+
+ out.append(" direction: ");
+ out.append(proto_enum_to_string(this->direction));
+ out.append("\n");
+
+ out.append(" has_speed_level: ");
+ out.append(YESNO(this->has_speed_level));
+ out.append("\n");
+
+ out.append(" speed_level: ");
+ sprintf(buffer, "%d", this->speed_level);
+ out.append(buffer);
+ out.append("\n");
out.append("}");
}
+#endif
bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
+ case 12: {
+ this->supported_color_modes.push_back(value.as_enum());
+ return true;
+ }
case 5: {
- this->supports_brightness = value.as_bool();
+ this->legacy_supports_brightness = value.as_bool();
return true;
}
case 6: {
- this->supports_rgb = value.as_bool();
+ this->legacy_supports_rgb = value.as_bool();
return true;
}
case 7: {
- this->supports_white_value = value.as_bool();
+ this->legacy_supports_white_value = value.as_bool();
return true;
}
case 8: {
- this->supports_color_temperature = value.as_bool();
+ this->legacy_supports_color_temperature = value.as_bool();
+ return true;
+ }
+ case 13: {
+ this->disabled_by_default = value.as_bool();
+ return true;
+ }
+ case 15: {
+ this->entity_category = value.as_enum();
return true;
}
default:
@@ -1006,6 +1354,10 @@ bool ListEntitiesLightResponse::decode_length(uint32_t field_id, ProtoLengthDeli
this->effects.push_back(value.as_string());
return true;
}
+ case 14: {
+ this->icon = value.as_string();
+ return true;
+ }
default:
return false;
}
@@ -1033,18 +1385,25 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
- buffer.encode_bool(5, this->supports_brightness);
- buffer.encode_bool(6, this->supports_rgb);
- buffer.encode_bool(7, this->supports_white_value);
- buffer.encode_bool(8, this->supports_color_temperature);
+ for (auto &it : this->supported_color_modes) {
+ buffer.encode_enum(12, it, true);
+ }
+ buffer.encode_bool(5, this->legacy_supports_brightness);
+ buffer.encode_bool(6, this->legacy_supports_rgb);
+ buffer.encode_bool(7, this->legacy_supports_white_value);
+ buffer.encode_bool(8, this->legacy_supports_color_temperature);
buffer.encode_float(9, this->min_mireds);
buffer.encode_float(10, this->max_mireds);
for (auto &it : this->effects) {
buffer.encode_string(11, it, true);
}
+ buffer.encode_bool(13, this->disabled_by_default);
+ buffer.encode_string(14, this->icon);
+ buffer.encode_enum(15, this->entity_category);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesLightResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("ListEntitiesLightResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -1063,20 +1422,26 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
out.append("'").append(this->unique_id).append("'");
out.append("\n");
- out.append(" supports_brightness: ");
- out.append(YESNO(this->supports_brightness));
+ for (const auto &it : this->supported_color_modes) {
+ out.append(" supported_color_modes: ");
+ out.append(proto_enum_to_string(it));
+ out.append("\n");
+ }
+
+ out.append(" legacy_supports_brightness: ");
+ out.append(YESNO(this->legacy_supports_brightness));
out.append("\n");
- out.append(" supports_rgb: ");
- out.append(YESNO(this->supports_rgb));
+ out.append(" legacy_supports_rgb: ");
+ out.append(YESNO(this->legacy_supports_rgb));
out.append("\n");
- out.append(" supports_white_value: ");
- out.append(YESNO(this->supports_white_value));
+ out.append(" legacy_supports_white_value: ");
+ out.append(YESNO(this->legacy_supports_white_value));
out.append("\n");
- out.append(" supports_color_temperature: ");
- out.append(YESNO(this->supports_color_temperature));
+ out.append(" legacy_supports_color_temperature: ");
+ out.append(YESNO(this->legacy_supports_color_temperature));
out.append("\n");
out.append(" min_mireds: ");
@@ -1094,14 +1459,31 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
out.append("'").append(it).append("'");
out.append("\n");
}
+
+ out.append(" disabled_by_default: ");
+ out.append(YESNO(this->disabled_by_default));
+ out.append("\n");
+
+ out.append(" icon: ");
+ out.append("'").append(this->icon).append("'");
+ out.append("\n");
+
+ out.append(" entity_category: ");
+ out.append(proto_enum_to_string(this->entity_category));
+ out.append("\n");
out.append("}");
}
+#endif
bool LightStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
this->state = value.as_bool();
return true;
}
+ case 11: {
+ this->color_mode = value.as_enum();
+ return true;
+ }
default:
return false;
}
@@ -1126,6 +1508,10 @@ bool LightStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
this->brightness = value.as_float();
return true;
}
+ case 10: {
+ this->color_brightness = value.as_float();
+ return true;
+ }
case 4: {
this->red = value.as_float();
return true;
@@ -1146,6 +1532,14 @@ bool LightStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
this->color_temperature = value.as_float();
return true;
}
+ case 12: {
+ this->cold_white = value.as_float();
+ return true;
+ }
+ case 13: {
+ this->warm_white = value.as_float();
+ return true;
+ }
default:
return false;
}
@@ -1154,15 +1548,20 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_bool(2, this->state);
buffer.encode_float(3, this->brightness);
+ buffer.encode_enum(11, this->color_mode);
+ buffer.encode_float(10, this->color_brightness);
buffer.encode_float(4, this->red);
buffer.encode_float(5, this->green);
buffer.encode_float(6, this->blue);
buffer.encode_float(7, this->white);
buffer.encode_float(8, this->color_temperature);
+ buffer.encode_float(12, this->cold_white);
+ buffer.encode_float(13, this->warm_white);
buffer.encode_string(9, this->effect);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void LightStateResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("LightStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -1178,6 +1577,15 @@ void LightStateResponse::dump_to(std::string &out) const {
out.append(buffer);
out.append("\n");
+ out.append(" color_mode: ");
+ out.append(proto_enum_to_string(this->color_mode));
+ out.append("\n");
+
+ out.append(" color_brightness: ");
+ sprintf(buffer, "%g", this->color_brightness);
+ out.append(buffer);
+ out.append("\n");
+
out.append(" red: ");
sprintf(buffer, "%g", this->red);
out.append(buffer);
@@ -1203,11 +1611,22 @@ void LightStateResponse::dump_to(std::string &out) const {
out.append(buffer);
out.append("\n");
+ out.append(" cold_white: ");
+ sprintf(buffer, "%g", this->cold_white);
+ out.append(buffer);
+ out.append("\n");
+
+ out.append(" warm_white: ");
+ sprintf(buffer, "%g", this->warm_white);
+ out.append(buffer);
+ out.append("\n");
+
out.append(" effect: ");
out.append("'").append(this->effect).append("'");
out.append("\n");
out.append("}");
}
+#endif
bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@@ -1222,6 +1641,18 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
this->has_brightness = value.as_bool();
return true;
}
+ case 22: {
+ this->has_color_mode = value.as_bool();
+ return true;
+ }
+ case 23: {
+ this->color_mode = value.as_enum();
+ return true;
+ }
+ case 20: {
+ this->has_color_brightness = value.as_bool();
+ return true;
+ }
case 6: {
this->has_rgb = value.as_bool();
return true;
@@ -1234,6 +1665,14 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
this->has_color_temperature = value.as_bool();
return true;
}
+ case 24: {
+ this->has_cold_white = value.as_bool();
+ return true;
+ }
+ case 26: {
+ this->has_warm_white = value.as_bool();
+ return true;
+ }
case 14: {
this->has_transition_length = value.as_bool();
return true;
@@ -1278,6 +1717,10 @@ bool LightCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
this->brightness = value.as_float();
return true;
}
+ case 21: {
+ this->color_brightness = value.as_float();
+ return true;
+ }
case 7: {
this->red = value.as_float();
return true;
@@ -1298,6 +1741,14 @@ bool LightCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
this->color_temperature = value.as_float();
return true;
}
+ case 25: {
+ this->cold_white = value.as_float();
+ return true;
+ }
+ case 27: {
+ this->warm_white = value.as_float();
+ return true;
+ }
default:
return false;
}
@@ -1308,6 +1759,10 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(3, this->state);
buffer.encode_bool(4, this->has_brightness);
buffer.encode_float(5, this->brightness);
+ buffer.encode_bool(22, this->has_color_mode);
+ buffer.encode_enum(23, this->color_mode);
+ buffer.encode_bool(20, this->has_color_brightness);
+ buffer.encode_float(21, this->color_brightness);
buffer.encode_bool(6, this->has_rgb);
buffer.encode_float(7, this->red);
buffer.encode_float(8, this->green);
@@ -1316,6 +1771,10 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(11, this->white);
buffer.encode_bool(12, this->has_color_temperature);
buffer.encode_float(13, this->color_temperature);
+ buffer.encode_bool(24, this->has_cold_white);
+ buffer.encode_float(25, this->cold_white);
+ buffer.encode_bool(26, this->has_warm_white);
+ buffer.encode_float(27, this->warm_white);
buffer.encode_bool(14, this->has_transition_length);
buffer.encode_uint32(15, this->transition_length);
buffer.encode_bool(16, this->has_flash_length);
@@ -1323,8 +1782,9 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(18, this->has_effect);
buffer.encode_string(19, this->effect);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void LightCommandRequest::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("LightCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -1348,6 +1808,23 @@ void LightCommandRequest::dump_to(std::string &out) const {
out.append(buffer);
out.append("\n");
+ out.append(" has_color_mode: ");
+ out.append(YESNO(this->has_color_mode));
+ out.append("\n");
+
+ out.append(" color_mode: ");
+ out.append(proto_enum_to_string(this->color_mode));
+ out.append("\n");
+
+ out.append(" has_color_brightness: ");
+ out.append(YESNO(this->has_color_brightness));
+ out.append("\n");
+
+ out.append(" color_brightness: ");
+ sprintf(buffer, "%g", this->color_brightness);
+ out.append(buffer);
+ out.append("\n");
+
out.append(" has_rgb: ");
out.append(YESNO(this->has_rgb));
out.append("\n");
@@ -1385,6 +1862,24 @@ void LightCommandRequest::dump_to(std::string &out) const {
out.append(buffer);
out.append("\n");
+ out.append(" has_cold_white: ");
+ out.append(YESNO(this->has_cold_white));
+ out.append("\n");
+
+ out.append(" cold_white: ");
+ sprintf(buffer, "%g", this->cold_white);
+ out.append(buffer);
+ out.append("\n");
+
+ out.append(" has_warm_white: ");
+ out.append(YESNO(this->has_warm_white));
+ out.append("\n");
+
+ out.append(" warm_white: ");
+ sprintf(buffer, "%g", this->warm_white);
+ out.append(buffer);
+ out.append("\n");
+
out.append(" has_transition_length: ");
out.append(YESNO(this->has_transition_length));
out.append("\n");
@@ -1412,6 +1907,7 @@ void LightCommandRequest::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
+#endif
bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 7: {
@@ -1422,6 +1918,22 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->force_update = value.as_bool();
return true;
}
+ case 10: {
+ this->state_class = value.as_enum();
+ return true;
+ }
+ case 11: {
+ this->legacy_last_reset_type = value.as_enum();
+ return true;
+ }
+ case 12: {
+ this->disabled_by_default = value.as_bool();
+ return true;
+ }
+ case 13: {
+ this->entity_category = value.as_enum();
+ return true;
+ }
default:
return false;
}
@@ -1448,6 +1960,10 @@ bool ListEntitiesSensorResponse::decode_length(uint32_t field_id, ProtoLengthDel
this->unit_of_measurement = value.as_string();
return true;
}
+ case 9: {
+ this->device_class = value.as_string();
+ return true;
+ }
default:
return false;
}
@@ -1471,9 +1987,15 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(6, this->unit_of_measurement);
buffer.encode_int32(7, this->accuracy_decimals);
buffer.encode_bool(8, this->force_update);
+ buffer.encode_string(9, this->device_class);
+ buffer.encode_enum(10, this->state_class);
+ buffer.encode_enum(11, this->legacy_last_reset_type);
+ buffer.encode_bool(12, this->disabled_by_default);
+ buffer.encode_enum(13, this->entity_category);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("ListEntitiesSensorResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -1508,8 +2030,29 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
out.append(" force_update: ");
out.append(YESNO(this->force_update));
out.append("\n");
+
+ out.append(" device_class: ");
+ out.append("'").append(this->device_class).append("'");
+ out.append("\n");
+
+ out.append(" state_class: ");
+ out.append(proto_enum_to_string(this->state_class));
+ out.append("\n");
+
+ out.append(" legacy_last_reset_type: ");
+ out.append(proto_enum_to_string(this->legacy_last_reset_type));
+ out.append("\n");
+
+ out.append(" disabled_by_default: ");
+ out.append(YESNO(this->disabled_by_default));
+ out.append("\n");
+
+ out.append(" entity_category: ");
+ out.append(proto_enum_to_string(this->entity_category));
+ out.append("\n");
out.append("}");
}
+#endif
bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 3: {
@@ -1539,8 +2082,9 @@ void SensorStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(2, this->state);
buffer.encode_bool(3, this->missing_state);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void SensorStateResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("SensorStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -1557,12 +2101,21 @@ void SensorStateResponse::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
+#endif
bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {
this->assumed_state = value.as_bool();
return true;
}
+ case 7: {
+ this->disabled_by_default = value.as_bool();
+ return true;
+ }
+ case 8: {
+ this->entity_category = value.as_enum();
+ return true;
+ }
default:
return false;
}
@@ -1606,9 +2159,12 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->assumed_state);
+ buffer.encode_bool(7, this->disabled_by_default);
+ buffer.encode_enum(8, this->entity_category);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("ListEntitiesSwitchResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -1634,8 +2190,17 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
out.append(" assumed_state: ");
out.append(YESNO(this->assumed_state));
out.append("\n");
+
+ out.append(" disabled_by_default: ");
+ out.append(YESNO(this->disabled_by_default));
+ out.append("\n");
+
+ out.append(" entity_category: ");
+ out.append(proto_enum_to_string(this->entity_category));
+ out.append("\n");
out.append("}");
}
+#endif
bool SwitchStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@@ -1660,8 +2225,9 @@ void SwitchStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_bool(2, this->state);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void SwitchStateResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("SwitchStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -1673,6 +2239,7 @@ void SwitchStateResponse::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
+#endif
bool SwitchCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@@ -1697,8 +2264,9 @@ void SwitchCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_bool(2, this->state);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void SwitchCommandRequest::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("SwitchCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -1710,6 +2278,21 @@ void SwitchCommandRequest::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
+#endif
+bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
+ switch (field_id) {
+ case 6: {
+ this->disabled_by_default = value.as_bool();
+ return true;
+ }
+ case 7: {
+ this->entity_category = value.as_enum();
+ return true;
+ }
+ default:
+ return false;
+ }
+}
bool ListEntitiesTextSensorResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
@@ -1748,9 +2331,12 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
+ buffer.encode_bool(6, this->disabled_by_default);
+ buffer.encode_enum(7, this->entity_category);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("ListEntitiesTextSensorResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -1772,8 +2358,17 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
+
+ out.append(" disabled_by_default: ");
+ out.append(YESNO(this->disabled_by_default));
+ out.append("\n");
+
+ out.append(" entity_category: ");
+ out.append(proto_enum_to_string(this->entity_category));
+ out.append("\n");
out.append("}");
}
+#endif
bool TextSensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 3: {
@@ -1809,8 +2404,9 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(2, this->state);
buffer.encode_bool(3, this->missing_state);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void TextSensorStateResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("TextSensorStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -1826,6 +2422,7 @@ void TextSensorStateResponse::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
+#endif
bool SubscribeLogsRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
@@ -1844,8 +2441,9 @@ void SubscribeLogsRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_enum(1, this->level);
buffer.encode_bool(2, this->dump_config);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeLogsRequest::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("SubscribeLogsRequest {\n");
out.append(" level: ");
out.append(proto_enum_to_string(this->level));
@@ -1856,6 +2454,7 @@ void SubscribeLogsRequest::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
+#endif
bool SubscribeLogsResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
@@ -1872,10 +2471,6 @@ bool SubscribeLogsResponse::decode_varint(uint32_t field_id, ProtoVarInt value)
}
bool SubscribeLogsResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
- case 2: {
- this->tag = value.as_string();
- return true;
- }
case 3: {
this->message = value.as_string();
return true;
@@ -1886,21 +2481,17 @@ bool SubscribeLogsResponse::decode_length(uint32_t field_id, ProtoLengthDelimite
}
void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_enum(1, this->level);
- buffer.encode_string(2, this->tag);
buffer.encode_string(3, this->message);
buffer.encode_bool(4, this->send_failed);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeLogsResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("SubscribeLogsResponse {\n");
out.append(" level: ");
out.append(proto_enum_to_string(this->level));
out.append("\n");
- out.append(" tag: ");
- out.append("'").append(this->tag).append("'");
- out.append("\n");
-
out.append(" message: ");
out.append("'").append(this->message).append("'");
out.append("\n");
@@ -1910,10 +2501,13 @@ void SubscribeLogsResponse::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
+#endif
void SubscribeHomeassistantServicesRequest::encode(ProtoWriteBuffer buffer) const {}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const {
out.append("SubscribeHomeassistantServicesRequest {}");
}
+#endif
bool HomeassistantServiceMap::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
@@ -1932,8 +2526,9 @@ void HomeassistantServiceMap::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->key);
buffer.encode_string(2, this->value);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void HomeassistantServiceMap::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("HomeassistantServiceMap {\n");
out.append(" key: ");
out.append("'").append(this->key).append("'");
@@ -1944,6 +2539,7 @@ void HomeassistantServiceMap::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
+#endif
bool HomeassistantServiceResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 5: {
@@ -1989,8 +2585,9 @@ void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const {
}
buffer.encode_bool(5, this->is_event);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void HomeassistantServiceResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("HomeassistantServiceResponse {\n");
out.append(" service: ");
out.append("'").append(this->service).append("'");
@@ -2019,31 +2616,45 @@ void HomeassistantServiceResponse::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
+#endif
void SubscribeHomeAssistantStatesRequest::encode(ProtoWriteBuffer buffer) const {}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeHomeAssistantStatesRequest::dump_to(std::string &out) const {
out.append("SubscribeHomeAssistantStatesRequest {}");
}
+#endif
bool SubscribeHomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->entity_id = value.as_string();
return true;
}
+ case 2: {
+ this->attribute = value.as_string();
+ return true;
+ }
default:
return false;
}
}
void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->entity_id);
+ buffer.encode_string(2, this->attribute);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("SubscribeHomeAssistantStateResponse {\n");
out.append(" entity_id: ");
out.append("'").append(this->entity_id).append("'");
out.append("\n");
+
+ out.append(" attribute: ");
+ out.append("'").append(this->attribute).append("'");
+ out.append("\n");
out.append("}");
}
+#endif
bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
@@ -2054,6 +2665,10 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel
this->state = value.as_string();
return true;
}
+ case 3: {
+ this->attribute = value.as_string();
+ return true;
+ }
default:
return false;
}
@@ -2061,9 +2676,11 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel
void HomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->entity_id);
buffer.encode_string(2, this->state);
+ buffer.encode_string(3, this->attribute);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void HomeAssistantStateResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("HomeAssistantStateResponse {\n");
out.append(" entity_id: ");
out.append("'").append(this->entity_id).append("'");
@@ -2072,10 +2689,17 @@ void HomeAssistantStateResponse::dump_to(std::string &out) const {
out.append(" state: ");
out.append("'").append(this->state).append("'");
out.append("\n");
+
+ out.append(" attribute: ");
+ out.append("'").append(this->attribute).append("'");
+ out.append("\n");
out.append("}");
}
+#endif
void GetTimeRequest::encode(ProtoWriteBuffer buffer) const {}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); }
+#endif
bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
@@ -2087,8 +2711,9 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
}
}
void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); }
+#ifdef HAS_PROTO_MESSAGE_DUMP
void GetTimeResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("GetTimeResponse {\n");
out.append(" epoch_seconds: ");
sprintf(buffer, "%u", this->epoch_seconds);
@@ -2096,6 +2721,7 @@ void GetTimeResponse::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
+#endif
bool ListEntitiesServicesArgument::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@@ -2120,8 +2746,9 @@ void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->name);
buffer.encode_enum(2, this->type);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesServicesArgument::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("ListEntitiesServicesArgument {\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
@@ -2132,6 +2759,7 @@ void ListEntitiesServicesArgument::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
+#endif
bool ListEntitiesServicesResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
@@ -2163,8 +2791,9 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_message(3, it, true);
}
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesServicesResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("ListEntitiesServicesResponse {\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
@@ -2182,6 +2811,7 @@ void ListEntitiesServicesResponse::dump_to(std::string &out) const {
}
out.append("}");
}
+#endif
bool ExecuteServiceArgument::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
@@ -2255,8 +2885,9 @@ void ExecuteServiceArgument::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(9, it, true);
}
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void ExecuteServiceArgument::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("ExecuteServiceArgument {\n");
out.append(" bool_: ");
out.append(YESNO(this->bool_));
@@ -2308,6 +2939,7 @@ void ExecuteServiceArgument::dump_to(std::string &out) const {
}
out.append("}");
}
+#endif
bool ExecuteServiceRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
@@ -2334,8 +2966,9 @@ void ExecuteServiceRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_message(2, it, true);
}
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void ExecuteServiceRequest::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("ExecuteServiceRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -2349,6 +2982,21 @@ void ExecuteServiceRequest::dump_to(std::string &out) const {
}
out.append("}");
}
+#endif
+bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
+ switch (field_id) {
+ case 5: {
+ this->disabled_by_default = value.as_bool();
+ return true;
+ }
+ case 7: {
+ this->entity_category = value.as_enum();
+ return true;
+ }
+ default:
+ return false;
+ }
+}
bool ListEntitiesCameraResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
@@ -2363,6 +3011,10 @@ bool ListEntitiesCameraResponse::decode_length(uint32_t field_id, ProtoLengthDel
this->unique_id = value.as_string();
return true;
}
+ case 6: {
+ this->icon = value.as_string();
+ return true;
+ }
default:
return false;
}
@@ -2382,9 +3034,13 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
+ buffer.encode_bool(5, this->disabled_by_default);
+ buffer.encode_string(6, this->icon);
+ buffer.encode_enum(7, this->entity_category);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesCameraResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("ListEntitiesCameraResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -2402,8 +3058,21 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const {
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
+
+ out.append(" disabled_by_default: ");
+ out.append(YESNO(this->disabled_by_default));
+ out.append("\n");
+
+ out.append(" icon: ");
+ out.append("'").append(this->icon).append("'");
+ out.append("\n");
+
+ out.append(" entity_category: ");
+ out.append(proto_enum_to_string(this->entity_category));
+ out.append("\n");
out.append("}");
}
+#endif
bool CameraImageResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 3: {
@@ -2439,8 +3108,9 @@ void CameraImageResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(2, this->data);
buffer.encode_bool(3, this->done);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void CameraImageResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("CameraImageResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -2456,6 +3126,7 @@ void CameraImageResponse::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
+#endif
bool CameraImageRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
@@ -2474,8 +3145,9 @@ void CameraImageRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(1, this->single);
buffer.encode_bool(2, this->stream);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void CameraImageRequest::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("CameraImageRequest {\n");
out.append(" single: ");
out.append(YESNO(this->single));
@@ -2486,6 +3158,7 @@ void CameraImageRequest::dump_to(std::string &out) const {
out.append("\n");
out.append("}");
}
+#endif
bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 5: {
@@ -2501,7 +3174,7 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
return true;
}
case 11: {
- this->supports_away = value.as_bool();
+ this->legacy_supports_away = value.as_bool();
return true;
}
case 12: {
@@ -2516,6 +3189,18 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
this->supported_swing_modes.push_back(value.as_enum());
return true;
}
+ case 16: {
+ this->supported_presets.push_back(value.as_enum());
+ return true;
+ }
+ case 18: {
+ this->disabled_by_default = value.as_bool();
+ return true;
+ }
+ case 20: {
+ this->entity_category = value.as_enum();
+ return true;
+ }
default:
return false;
}
@@ -2534,6 +3219,18 @@ bool ListEntitiesClimateResponse::decode_length(uint32_t field_id, ProtoLengthDe
this->unique_id = value.as_string();
return true;
}
+ case 15: {
+ this->supported_custom_fan_modes.push_back(value.as_string());
+ return true;
+ }
+ case 17: {
+ this->supported_custom_presets.push_back(value.as_string());
+ return true;
+ }
+ case 19: {
+ this->icon = value.as_string();
+ return true;
+ }
default:
return false;
}
@@ -2573,7 +3270,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(8, this->visual_min_temperature);
buffer.encode_float(9, this->visual_max_temperature);
buffer.encode_float(10, this->visual_temperature_step);
- buffer.encode_bool(11, this->supports_away);
+ buffer.encode_bool(11, this->legacy_supports_away);
buffer.encode_bool(12, this->supports_action);
for (auto &it : this->supported_fan_modes) {
buffer.encode_enum(13, it, true);
@@ -2581,9 +3278,22 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
for (auto &it : this->supported_swing_modes) {
buffer.encode_enum(14, it, true);
}
+ for (auto &it : this->supported_custom_fan_modes) {
+ buffer.encode_string(15, it, true);
+ }
+ for (auto &it : this->supported_presets) {
+ buffer.encode_enum(16, it, true);
+ }
+ for (auto &it : this->supported_custom_presets) {
+ buffer.encode_string(17, it, true);
+ }
+ buffer.encode_bool(18, this->disabled_by_default);
+ buffer.encode_string(19, this->icon);
+ buffer.encode_enum(20, this->entity_category);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("ListEntitiesClimateResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -2631,8 +3341,8 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
out.append(buffer);
out.append("\n");
- out.append(" supports_away: ");
- out.append(YESNO(this->supports_away));
+ out.append(" legacy_supports_away: ");
+ out.append(YESNO(this->legacy_supports_away));
out.append("\n");
out.append(" supports_action: ");
@@ -2650,8 +3360,39 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
out.append(proto_enum_to_string(it));
out.append("\n");
}
+
+ for (const auto &it : this->supported_custom_fan_modes) {
+ out.append(" supported_custom_fan_modes: ");
+ out.append("'").append(it).append("'");
+ out.append("\n");
+ }
+
+ for (const auto &it : this->supported_presets) {
+ out.append(" supported_presets: ");
+ out.append(proto_enum_to_string(it));
+ out.append("\n");
+ }
+
+ for (const auto &it : this->supported_custom_presets) {
+ out.append(" supported_custom_presets: ");
+ out.append("'").append(it).append("'");
+ out.append("\n");
+ }
+
+ out.append(" disabled_by_default: ");
+ out.append(YESNO(this->disabled_by_default));
+ out.append("\n");
+
+ out.append(" icon: ");
+ out.append("'").append(this->icon).append("'");
+ out.append("\n");
+
+ out.append(" entity_category: ");
+ out.append(proto_enum_to_string(this->entity_category));
+ out.append("\n");
out.append("}");
}
+#endif
bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@@ -2659,7 +3400,7 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
return true;
}
case 7: {
- this->away = value.as_bool();
+ this->legacy_away = value.as_bool();
return true;
}
case 8: {
@@ -2674,6 +3415,24 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
this->swing_mode = value.as_enum();
return true;
}
+ case 12: {
+ this->preset = value.as_enum();
+ return true;
+ }
+ default:
+ return false;
+ }
+}
+bool ClimateStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
+ switch (field_id) {
+ case 11: {
+ this->custom_fan_mode = value.as_string();
+ return true;
+ }
+ case 13: {
+ this->custom_preset = value.as_string();
+ return true;
+ }
default:
return false;
}
@@ -2711,13 +3470,17 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(4, this->target_temperature);
buffer.encode_float(5, this->target_temperature_low);
buffer.encode_float(6, this->target_temperature_high);
- buffer.encode_bool(7, this->away);
+ buffer.encode_bool(7, this->legacy_away);
buffer.encode_enum(8, this->action);
buffer.encode_enum(9, this->fan_mode);
buffer.encode_enum(10, this->swing_mode);
+ buffer.encode_string(11, this->custom_fan_mode);
+ buffer.encode_enum(12, this->preset);
+ buffer.encode_string(13, this->custom_preset);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void ClimateStateResponse::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("ClimateStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -2748,8 +3511,8 @@ void ClimateStateResponse::dump_to(std::string &out) const {
out.append(buffer);
out.append("\n");
- out.append(" away: ");
- out.append(YESNO(this->away));
+ out.append(" legacy_away: ");
+ out.append(YESNO(this->legacy_away));
out.append("\n");
out.append(" action: ");
@@ -2763,8 +3526,21 @@ void ClimateStateResponse::dump_to(std::string &out) const {
out.append(" swing_mode: ");
out.append(proto_enum_to_string(this->swing_mode));
out.append("\n");
+
+ out.append(" custom_fan_mode: ");
+ out.append("'").append(this->custom_fan_mode).append("'");
+ out.append("\n");
+
+ out.append(" preset: ");
+ out.append(proto_enum_to_string(this->preset));
+ out.append("\n");
+
+ out.append(" custom_preset: ");
+ out.append("'").append(this->custom_preset).append("'");
+ out.append("\n");
out.append("}");
}
+#endif
bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@@ -2788,11 +3564,11 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
return true;
}
case 10: {
- this->has_away = value.as_bool();
+ this->has_legacy_away = value.as_bool();
return true;
}
case 11: {
- this->away = value.as_bool();
+ this->legacy_away = value.as_bool();
return true;
}
case 12: {
@@ -2811,6 +3587,36 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
this->swing_mode = value.as_enum();
return true;
}
+ case 16: {
+ this->has_custom_fan_mode = value.as_bool();
+ return true;
+ }
+ case 18: {
+ this->has_preset = value.as_bool();
+ return true;
+ }
+ case 19: {
+ this->preset = value.as_enum();
+ return true;
+ }
+ case 20: {
+ this->has_custom_preset = value.as_bool();
+ return true;
+ }
+ default:
+ return false;
+ }
+}
+bool ClimateCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
+ switch (field_id) {
+ case 17: {
+ this->custom_fan_mode = value.as_string();
+ return true;
+ }
+ case 21: {
+ this->custom_preset = value.as_string();
+ return true;
+ }
default:
return false;
}
@@ -2847,15 +3653,22 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(7, this->target_temperature_low);
buffer.encode_bool(8, this->has_target_temperature_high);
buffer.encode_float(9, this->target_temperature_high);
- buffer.encode_bool(10, this->has_away);
- buffer.encode_bool(11, this->away);
+ buffer.encode_bool(10, this->has_legacy_away);
+ buffer.encode_bool(11, this->legacy_away);
buffer.encode_bool(12, this->has_fan_mode);
buffer.encode_enum(13, this->fan_mode);
buffer.encode_bool(14, this->has_swing_mode);
buffer.encode_enum(15, this->swing_mode);
+ buffer.encode_bool(16, this->has_custom_fan_mode);
+ buffer.encode_string(17, this->custom_fan_mode);
+ buffer.encode_bool(18, this->has_preset);
+ buffer.encode_enum(19, this->preset);
+ buffer.encode_bool(20, this->has_custom_preset);
+ buffer.encode_string(21, this->custom_preset);
}
+#ifdef HAS_PROTO_MESSAGE_DUMP
void ClimateCommandRequest::dump_to(std::string &out) const {
- char buffer[64];
+ __attribute__((unused)) char buffer[64];
out.append("ClimateCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -2897,12 +3710,12 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
out.append(buffer);
out.append("\n");
- out.append(" has_away: ");
- out.append(YESNO(this->has_away));
+ out.append(" has_legacy_away: ");
+ out.append(YESNO(this->has_legacy_away));
out.append("\n");
- out.append(" away: ");
- out.append(YESNO(this->away));
+ out.append(" legacy_away: ");
+ out.append(YESNO(this->legacy_away));
out.append("\n");
out.append(" has_fan_mode: ");
@@ -2920,8 +3733,571 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
out.append(" swing_mode: ");
out.append(proto_enum_to_string(this->swing_mode));
out.append("\n");
+
+ out.append(" has_custom_fan_mode: ");
+ out.append(YESNO(this->has_custom_fan_mode));
+ out.append("\n");
+
+ out.append(" custom_fan_mode: ");
+ out.append("'").append(this->custom_fan_mode).append("'");
+ out.append("\n");
+
+ out.append(" has_preset: ");
+ out.append(YESNO(this->has_preset));
+ out.append("\n");
+
+ out.append(" preset: ");
+ out.append(proto_enum_to_string(this->preset));
+ out.append("\n");
+
+ out.append(" has_custom_preset: ");
+ out.append(YESNO(this->has_custom_preset));
+ out.append("\n");
+
+ out.append(" custom_preset: ");
+ out.append("'").append(this->custom_preset).append("'");
+ out.append("\n");
out.append("}");
}
+#endif
+bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
+ switch (field_id) {
+ case 9: {
+ this->disabled_by_default = value.as_bool();
+ return true;
+ }
+ case 10: {
+ this->entity_category = value.as_enum();
+ return true;
+ }
+ case 12: {
+ this->mode = value.as_enum