Merge remote-tracking branch 'origin/dev' into ltr390

This commit is contained in:
Otto winter 2021-08-03 19:07:39 +02:00
commit 157e2731cc
No known key found for this signature in database
GPG key ID: 48ED2DDB96D7682C
1139 changed files with 38801 additions and 13965 deletions

View file

@ -49,7 +49,7 @@ ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true

View file

@ -4,14 +4,24 @@ Checks: >-
-abseil-*,
-android-*,
-boost-*,
-bugprone-macro-parentheses,
-bugprone-branch-clone,
-bugprone-narrowing-conversions,
-bugprone-signed-char-misuse,
-bugprone-too-small-loop-variable,
-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.*,
-clang-diagnostic-shadow-field,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-avoid-goto,
-cppcoreguidelines-c-copy-assignment-signature,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-init-variables,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-constant-array-index,
@ -24,40 +34,51 @@ 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-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-return-braced-init-list,
-modernize-use-auto,
-modernize-use-default-member-init,
-modernize-use-equals-default,
-modernize-use-trailing-return-type,
-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-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,
-warnings-as-errors
WarningsAsErrors: '*'
HeaderFilterRegex: '^.*/src/esphome/.*'
AnalyzeTemporaryDtors: false

View file

@ -2,16 +2,29 @@
"name": "ESPHome Dev",
"context": "..",
"dockerFile": "../docker/Dockerfile.dev",
"postCreateCommand": "mkdir -p config && pip3 install -e .",
"runArgs": ["--privileged", "-e", "ESPHOME_DASHBOARD_USE_PING=1"],
"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,7 +32,7 @@
"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",
@ -27,6 +40,18 @@
"!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",
}
}

View file

@ -103,6 +103,10 @@ venv.bak/
# mypy
.mypy_cache/
# PlatformIO
.pio/
# ESPHome
config/
examples/
Dockerfile

View file

@ -7,7 +7,7 @@ insert_final_newline = true
charset = utf-8
# python
[*.{py}]
[*.py]
indent_style = space
indent_size = 4
@ -25,4 +25,10 @@ indent_size = 2
[*.{yaml,yml}]
indent_style = space
indent_size = 2
quote_type = single
quote_type = single
# JSON
[*.json]
indent_style = space
indent_size = 2

7
.github/FUNDING.yml vendored
View file

@ -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

View file

@ -1,25 +1,22 @@
# What does this implement/fix?
Quick description
Quick description and explanation of changes
## 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)
- [ ] Configuration change (this will require users to update their yaml configuration files to keep working)
- [ ] Other
**Related issue or feature (if applicable):** fixes <link to issue>
**Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs#<esphome-docs PR number goes here>
# Test Environment
## Test Environment
- [ ] ESP32
- [ ] ESP8266
- [ ] Windows
- [ ] Mac OS
- [ ] Linux
## Example entry for `config.yaml`:
<!--
@ -34,11 +31,6 @@ Quick description
```
# Explain your changes
Describe your changes here to communicate to the maintainers **why we should accept this pull request**.
Very important to fill if no issue linked
## Checklist:
- [ ] The code change is tested and works locally.
- [ ] Tests have been added to verify that the new code works (under `tests/` folder).

View file

@ -3,7 +3,7 @@ name: CI for docker images
# Only run when docker paths change
on:
push:
branches: [dev, beta, master]
branches: [dev, beta, release]
paths:
- 'docker/**'
- '.github/workflows/**'
@ -18,38 +18,23 @@ jobs:
name: Build docker containers
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
arch: [amd64, armv7, aarch64]
build_type: ["hassio", "docker"]
build_type: ["ha-addon", "docker", "lint"]
steps:
- uses: actions/checkout@v2
- name: Set up env variables
run: |
base_version="2.6.0"
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Set TAG
run: |
echo "TAG=check" >> $GITHUB_ENV
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
build_to="esphome/esphome-hassio-${{ matrix.arch }}"
dockerfile="docker/Dockerfile.hassio"
else
build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
build_to="esphome/esphome-${{ matrix.arch }}"
dockerfile="docker/Dockerfile"
fi
echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
- name: Pull for cache
run: |
docker pull "${BUILD_TO}:dev" || true
- name: Register QEMU binfmt
run: docker run --rm --privileged multiarch/qemu-user-static:5.0.0-2 --reset -p yes
- run: |
docker build \
--build-arg "BUILD_FROM=${BUILD_FROM}" \
--build-arg "BUILD_VERSION=ci" \
--cache-from "${BUILD_TO}:dev" \
--file "${DOCKERFILE}" \
.
- name: Run build
run: |
docker/build.py \
--tag "${TAG}" \
--arch "${{ matrix.arch }}" \
--build-type "${{ matrix.build_type }}" \
build

View file

@ -4,85 +4,102 @@ name: CI
on:
push:
# On dev branch release-dev already performs CI checks
# On other branches the `pull_request` trigger will be used
branches: [beta, master]
branches: [dev, beta, release]
pull_request:
jobs:
lint-clang-format:
ci-with-container:
name: ${{ matrix.name }}
runs-on: ubuntu-latest
# cpp lint job runs with esphome-lint docker image so that clang-format-*
# doesn't have to be installed
container: esphome/esphome-lint:latest
steps:
- uses: actions/checkout@v2
# Cache platformio intermediary files (like libraries etc)
# Note: platformio platform versions should be cached via the esphome-lint image
- name: Cache Platformio
uses: actions/cache@v1
with:
path: .pio
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
restore-keys: |
lint-cpp-pio-
# Set up the pio project so that the cpp checks know how files are compiled
# (build flags, libraries etc)
- name: Set up platformio environment
run: pio init --ide atom
- name: Run clang-format
run: script/clang-format -i
- name: Suggest changes
run: script/ci-suggest-changes
lint-clang-tidy:
runs-on: ubuntu-latest
# cpp lint job runs with esphome-lint docker image so that clang-format-*
# doesn't have to be installed
container: esphome/esphome-lint:latest
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
strategy:
fail-fast: false
matrix:
split: [1, 2, 3, 4]
include:
- id: clang-format
name: Run script/clang-format
- id: clang-tidy
name: Run script/clang-tidy 1/4
split: 1
- id: clang-tidy
name: Run script/clang-tidy 2/4
split: 2
- id: clang-tidy
name: Run script/clang-tidy 3/4
split: 3
- id: clang-tidy
name: Run script/clang-tidy 4/4
split: 4
# cpp lint job runs with esphome-lint docker image so that clang-format-*
# doesn't have to be installed
container: ghcr.io/esphome/esphome-lint:1.1
steps:
- uses: actions/checkout@v2
# Cache platformio intermediary files (like libraries etc)
# Note: platformio platform versions should be cached via the esphome-lint image
- name: Cache Platformio
uses: actions/cache@v1
with:
path: .pio
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
restore-keys: |
lint-cpp-pio-
# Set up the pio project so that the cpp checks know how files are compiled
# (build flags, libraries etc)
- name: Set up platformio environment
run: pio init --ide atom
- name: Register problem matchers
run: |
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
echo "::add-matcher::.github/workflows/matchers/gcc.json"
# 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 --split-num 4 --split-at ${{ matrix.split }}
- name: Suggest changes
run: script/ci-suggest-changes
if: ${{ matrix.id == 'clang-tidy' }}
lint-python:
- name: Suggested changes
run: script/ci-suggest-changes
if: always()
ci:
# Don't use the esphome-lint docker image because it may contain outdated requirements.
# This way, all dependencies are cached via the cache action.
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
- id: test
file: tests/test2.yaml
name: Test tests/test2.yaml
- id: test
file: tests/test3.yaml
name: Test tests/test3.yaml
- id: test
file: tests/test4.yaml
name: Test tests/test4.yaml
- id: test
file: tests/test5.yaml
name: Test tests/test5.yaml
- id: pytest
name: Run pytest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.7'
- name: Cache pip modules
uses: actions/cache@v1
with:
@ -90,6 +107,17 @@ jobs:
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
restore-keys: |
esphome-pip-3.7-
# Use per test platformio cache because tests have different platform versions
- name: Cache ~/.platformio
uses: actions/cache@v1
with:
path: ~/.platformio
key: test-home-platformio-${{ matrix.file }}-${{ hashFiles('esphome/core/config.py') }}
restore-keys: |
test-home-platformio-${{ matrix.file }}-
if: ${{ matrix.id == 'test' }}
- name: Set up python environment
run: script/setup
@ -98,81 +126,22 @@ jobs:
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"
- name: Lint Custom
run: script/ci-custom.py
run: |
script/ci-custom.py
script/build_codeowners.py --check
if: ${{ matrix.id == 'ci-custom' }}
- name: Lint Python
run: script/lint-python
- name: Lint CODEOWNERS
run: script/build_codeowners.py --check
if: ${{ matrix.id == 'lint-python' }}
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
test:
- test1
- test2
- test3
- test4
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.7'
- name: Cache pip modules
uses: actions/cache@v1
with:
path: ~/.cache/pip
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
restore-keys: |
esphome-pip-3.7-
# Use per test platformio cache because tests have different platform versions
- name: Cache ~/.platformio
uses: actions/cache@v1
with:
path: ~/.platformio
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }}
restore-keys: |
test-home-platformio-${{ matrix.test }}-
- name: Set up environment
run: script/setup
- run: esphome compile ${{ matrix.file }}
if: ${{ matrix.id == 'test' }}
- name: Register problem matchers
run: |
echo "::add-matcher::.github/workflows/matchers/gcc.json"
echo "::add-matcher::.github/workflows/matchers/python.json"
- run: esphome tests/${{ matrix.test }}.yaml compile
pytest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.7'
- name: Cache pip modules
uses: actions/cache@v1
with:
path: ~/.cache/pip
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
restore-keys: |
esphome-pip-3.7-
- name: Set up environment
run: script/setup
- name: Install Github Actions annotator
run: pip install pytest-github-actions-annotate-failures
- name: Register problem matchers
run: |
echo "::add-matcher::.github/workflows/matchers/python.json"
- name: Run pytest
run: |
pytest \
-qq \
--durations=10 \
-o console_output_style=count \
tests
pytest -vv --tb=native tests
if: ${{ matrix.id == 'pytest' }}

100
.github/workflows/docker-lint-build.yml vendored Normal file
View file

@ -0,0 +1,100 @@
name: Build and publish lint docker image
# Only run when docker paths change
on:
push:
branches: [dev]
paths:
- 'docker/Dockerfile.lint'
- 'requirements.txt'
- 'requirements_optional.txt'
- 'requirements_test.txt'
- 'platformio.ini'
- '.github/workflows/docker-lint-build.yml'
jobs:
deploy-docker:
name: Build and publish docker containers
if: github.repository == 'esphome/esphome'
runs-on: ubuntu-latest
strategy:
matrix:
arch: [amd64, armv7, aarch64]
build_type: ["lint"]
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Set TAG
run: |
echo "TAG=1.1" >> $GITHUB_ENV
- name: Run build
run: |
docker/build.py \
--tag "${TAG}" \
--arch "${{ matrix.arch }}" \
--build-type "${{ matrix.build_type }}" \
build
- 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 push
run: |
docker/build.py \
--tag "${TAG}" \
--arch "${{ matrix.arch }}" \
--build-type "${{ matrix.build_type }}" \
push
deploy-docker-manifest:
if: github.repository == 'esphome/esphome'
runs-on: ubuntu-latest
needs: [deploy-docker]
strategy:
matrix:
build_type: ["lint"]
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Set TAG
run: |
echo "TAG=1.1" >> $GITHUB_ENV
- 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 "${TAG}" \
--build-type "${{ matrix.build_type }}" \
manifest

19
.github/workflows/matchers/pytest.json vendored Normal file
View file

@ -0,0 +1,19 @@
{
"problemMatcher": [
{
"owner": "pytest",
"fileLocation": "absolute",
"pattern": [
{
"regexp": "^\\s+File \"(.*)\", line (\\d+), in (.*)$",
"file": 1,
"line": 2
},
{
"regexp": "^\\s+(.*)$",
"message": 1
}
]
}
]
}

View file

@ -1,264 +0,0 @@
name: Publish dev releases to docker hub
on:
push:
branches:
- dev
jobs:
# THE LINT/TEST JOBS ARE COPIED FROM ci.yaml
lint-clang-format:
runs-on: ubuntu-latest
# cpp lint job runs with esphome-lint docker image so that clang-format-*
# doesn't have to be installed
container: esphome/esphome-lint:latest
steps:
- uses: actions/checkout@v2
# Cache platformio intermediary files (like libraries etc)
# Note: platformio platform versions should be cached via the esphome-lint image
- name: Cache Platformio
uses: actions/cache@v1
with:
path: .pio
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
restore-keys: |
lint-cpp-pio-
# Set up the pio project so that the cpp checks know how files are compiled
# (build flags, libraries etc)
- name: Set up platformio environment
run: pio init --ide atom
- name: Run clang-format
run: script/clang-format -i
- name: Suggest changes
run: script/ci-suggest-changes
lint-clang-tidy:
runs-on: ubuntu-latest
# cpp lint job runs with esphome-lint docker image so that clang-format-*
# doesn't have to be installed
container: esphome/esphome-lint:latest
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
strategy:
fail-fast: false
matrix:
split: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v2
# Cache platformio intermediary files (like libraries etc)
# Note: platformio platform versions should be cached via the esphome-lint image
- name: Cache Platformio
uses: actions/cache@v1
with:
path: .pio
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
restore-keys: |
lint-cpp-pio-
# Set up the pio project so that the cpp checks know how files are compiled
# (build flags, libraries etc)
- name: Set up platformio environment
run: pio init --ide atom
- name: Register problem matchers
run: |
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
echo "::add-matcher::.github/workflows/matchers/gcc.json"
- name: Run clang-tidy
run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
- name: Suggest changes
run: script/ci-suggest-changes
lint-python:
# Don't use the esphome-lint docker image because it may contain outdated requirements.
# This way, all dependencies are cached via the cache action.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.7'
- name: Cache pip modules
uses: actions/cache@v1
with:
path: ~/.cache/pip
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
restore-keys: |
esphome-pip-3.7-
- name: Set up python environment
run: script/setup
- 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"
- name: Lint Custom
run: script/ci-custom.py
- name: Lint Python
run: script/lint-python
- name: Lint CODEOWNERS
run: script/build_codeowners.py --check
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
test:
- test1
- test2
- test3
- test4
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.7'
- name: Cache pip modules
uses: actions/cache@v1
with:
path: ~/.cache/pip
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
restore-keys: |
esphome-pip-3.7-
# Use per test platformio cache because tests have different platform versions
- name: Cache ~/.platformio
uses: actions/cache@v1
with:
path: ~/.platformio
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }}
restore-keys: |
test-home-platformio-${{ matrix.test }}-
- name: Set up environment
run: script/setup
- name: Register problem matchers
run: |
echo "::add-matcher::.github/workflows/matchers/gcc.json"
echo "::add-matcher::.github/workflows/matchers/python.json"
- run: esphome tests/${{ matrix.test }}.yaml compile
pytest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.7'
- name: Cache pip modules
uses: actions/cache@v1
with:
path: ~/.cache/pip
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
restore-keys: |
esphome-pip-3.7-
- name: Set up environment
run: script/setup
- name: Install Github Actions annotator
run: pip install pytest-github-actions-annotate-failures
- name: Register problem matchers
run: |
echo "::add-matcher::.github/workflows/matchers/python.json"
- name: Run pytest
run: |
pytest \
-qq \
--durations=10 \
-o console_output_style=count \
tests
deploy-docker:
name: Build and publish docker containers
if: github.repository == 'esphome/esphome'
runs-on: ubuntu-latest
needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
strategy:
matrix:
arch: [amd64, armv7, aarch64]
# Hassio dev image doesn't use esphome/esphome-hassio-$arch and uses base directly
build_type: ["docker"]
steps:
- uses: actions/checkout@v2
- name: Set TAG
run: |
TAG="${GITHUB_SHA:0:7}"
echo "TAG=${TAG}" >> $GITHUB_ENV
- name: Set up env variables
run: |
base_version="2.6.0"
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
build_to="esphome/esphome-hassio-${{ matrix.arch }}"
dockerfile="docker/Dockerfile.hassio"
else
build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
build_to="esphome/esphome-${{ matrix.arch }}"
dockerfile="docker/Dockerfile"
fi
echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
- name: Pull for cache
run: |
docker pull "${BUILD_TO}:dev" || true
- name: Register QEMU binfmt
run: docker run --rm --privileged multiarch/qemu-user-static:5.0.0-2 --reset -p yes
- run: |
docker build \
--build-arg "BUILD_FROM=${BUILD_FROM}" \
--build-arg "BUILD_VERSION=${TAG}" \
--tag "${BUILD_TO}:${TAG}" \
--tag "${BUILD_TO}:dev" \
--cache-from "${BUILD_TO}:dev" \
--file "${DOCKERFILE}" \
.
- name: Log in to docker hub
env:
DOCKER_USER: ${{ secrets.DOCKER_USER }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
- run: |
docker push "${BUILD_TO}:${TAG}"
docker push "${BUILD_TO}:dev"
deploy-docker-manifest:
if: github.repository == 'esphome/esphome'
runs-on: ubuntu-latest
needs: [deploy-docker]
steps:
- name: Enable experimental manifest support
run: |
mkdir -p ~/.docker
echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
- name: Set TAG
run: |
TAG="${GITHUB_SHA:0:7}"
echo "TAG=${TAG}" >> $GITHUB_ENV
- name: Log in to docker hub
env:
DOCKER_USER: ${{ secrets.DOCKER_USER }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
- name: "Create the manifest"
run: |
docker manifest create esphome/esphome:${TAG} \
esphome/esphome-aarch64:${TAG} \
esphome/esphome-amd64:${TAG} \
esphome/esphome-armv7:${TAG}
docker manifest push esphome/esphome:${TAG}
docker manifest create esphome/esphome:dev \
esphome/esphome-aarch64:${TAG} \
esphome/esphome-amd64:${TAG} \
esphome/esphome-armv7:${TAG}
docker manifest push esphome/esphome:dev

View file

@ -1,181 +1,35 @@
name: Publish Release
on:
workflow_dispatch:
release:
types: [published]
schedule:
- cron: "0 2 * * *"
jobs:
# THE LINT/TEST JOBS ARE COPIED FROM ci.yaml
lint-clang-format:
init:
name: Initialize build
runs-on: ubuntu-latest
# cpp lint job runs with esphome-lint docker image so that clang-format-*
# doesn't have to be installed
container: esphome/esphome-lint:latest
outputs:
tag: ${{ steps.tag.outputs.tag }}
steps:
- uses: actions/checkout@v2
# Cache platformio intermediary files (like libraries etc)
# Note: platformio platform versions should be cached via the esphome-lint image
- name: Cache Platformio
uses: actions/cache@v1
with:
path: .pio
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
restore-keys: |
lint-cpp-pio-
# Set up the pio project so that the cpp checks know how files are compiled
# (build flags, libraries etc)
- name: Set up platformio environment
run: pio init --ide atom
- name: Run clang-format
run: script/clang-format -i
- name: Suggest changes
run: script/ci-suggest-changes
lint-clang-tidy:
runs-on: ubuntu-latest
# cpp lint job runs with esphome-lint docker image so that clang-format-*
# doesn't have to be installed
container: esphome/esphome-lint:latest
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
strategy:
fail-fast: false
matrix:
split: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v2
# Cache platformio intermediary files (like libraries etc)
# Note: platformio platform versions should be cached via the esphome-lint image
- name: Cache Platformio
uses: actions/cache@v1
with:
path: .pio
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
restore-keys: |
lint-cpp-pio-
# Set up the pio project so that the cpp checks know how files are compiled
# (build flags, libraries etc)
- name: Set up platformio environment
run: pio init --ide atom
- name: Register problem matchers
- name: Get tag
id: tag
run: |
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
echo "::add-matcher::.github/workflows/matchers/gcc.json"
- name: Run clang-tidy
run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
- name: Suggest changes
run: script/ci-suggest-changes
lint-python:
# Don't use the esphome-lint docker image because it may contain outdated requirements.
# This way, all dependencies are cached via the cache action.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.7'
- name: Cache pip modules
uses: actions/cache@v1
with:
path: ~/.cache/pip
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
restore-keys: |
esphome-pip-3.7-
- name: Set up python environment
run: script/setup
- 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"
- name: Lint Custom
run: script/ci-custom.py
- name: Lint Python
run: script/lint-python
- name: Lint CODEOWNERS
run: script/build_codeowners.py --check
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
test:
- test1
- test2
- test3
- test4
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.7'
- name: Cache pip modules
uses: actions/cache@v1
with:
path: ~/.cache/pip
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
restore-keys: |
esphome-pip-3.7-
# Use per test platformio cache because tests have different platform versions
- name: Cache ~/.platformio
uses: actions/cache@v1
with:
path: ~/.platformio
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }}
restore-keys: |
test-home-platformio-${{ matrix.test }}-
- name: Set up environment
run: script/setup
- name: Register problem matchers
run: |
echo "::add-matcher::.github/workflows/matchers/gcc.json"
echo "::add-matcher::.github/workflows/matchers/python.json"
- run: esphome tests/${{ matrix.test }}.yaml compile
pytest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.7'
- name: Cache pip modules
uses: actions/cache@v1
with:
path: ~/.cache/pip
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
restore-keys: |
esphome-pip-3.7-
- name: Set up environment
run: script/setup
- name: Install Github Actions annotator
run: pip install pytest-github-actions-annotate-failures
- name: Register problem matchers
run: |
echo "::add-matcher::.github/workflows/matchers/python.json"
- name: Run pytest
run: |
pytest \
-qq \
--durations=10 \
-o console_output_style=count \
tests
if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then
TAG="${GITHUB_REF#refs/tags/v}"
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'
needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
if: github.repository == 'esphome/esphome' && github.event_name == 'release'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@ -199,119 +53,85 @@ jobs:
name: Build and publish docker containers
if: github.repository == 'esphome/esphome'
runs-on: ubuntu-latest
needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
needs: [init]
strategy:
matrix:
arch: [amd64, armv7, aarch64]
build_type: ["hassio", "docker"]
build_type: ["ha-addon", "docker"]
steps:
- uses: actions/checkout@v2
- name: Set TAG
run: |
TAG="${GITHUB_REF#refs/tags/v}"
echo "TAG=${TAG}" >> $GITHUB_ENV
- name: Set up env variables
run: |
base_version="2.6.0"
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
build_to="esphome/esphome-hassio-${{ matrix.arch }}"
dockerfile="docker/Dockerfile.hassio"
else
build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
build_to="esphome/esphome-${{ matrix.arch }}"
dockerfile="docker/Dockerfile"
fi
- name: Run build
run: |
docker/build.py \
--tag "${{ needs.init.outputs.tag }}" \
--arch "${{ matrix.arch }}" \
--build-type "${{ matrix.build_type }}" \
build
if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then
cache_tag="beta"
else
cache_tag="latest"
fi
- 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 }}
# Set env variables so these values don't need to be calculated again
echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
echo "CACHE_TAG=${cache_tag}" >> $GITHUB_ENV
- name: Pull for cache
run: |
docker pull "${BUILD_TO}:${CACHE_TAG}" || true
- name: Register QEMU binfmt
run: docker run --rm --privileged multiarch/qemu-user-static:5.0.0-2 --reset -p yes
- run: |
docker build \
--build-arg "BUILD_FROM=${BUILD_FROM}" \
--build-arg "BUILD_VERSION=${TAG}" \
--tag "${BUILD_TO}:${TAG}" \
--cache-from "${BUILD_TO}:${CACHE_TAG}" \
--file "${DOCKERFILE}" \
.
- name: Log in to docker hub
env:
DOCKER_USER: ${{ secrets.DOCKER_USER }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
- run: docker push "${BUILD_TO}:${TAG}"
# Always publish to beta tag (also full releases)
- name: Publish docker beta tag
run: |
docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:beta"
docker push "${BUILD_TO}:beta"
- if: ${{ !github.event.release.prerelease }}
name: Publish docker latest tag
run: |
docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:latest"
docker push "${BUILD_TO}:latest"
- name: Run push
run: |
docker/build.py \
--tag "${{ needs.init.outputs.tag }}" \
--arch "${{ matrix.arch }}" \
--build-type "${{ matrix.build_type }}" \
push
deploy-docker-manifest:
if: github.repository == 'esphome/esphome'
runs-on: ubuntu-latest
needs: [deploy-docker]
needs: [init, deploy-docker]
strategy:
matrix:
build_type: ["ha-addon", "docker"]
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: Set TAG
run: |
TAG="${GITHUB_REF#refs/tags/v}"
echo "TAG=${TAG}" >> $GITHUB_ENV
- name: Log in to docker hub
env:
DOCKER_USER: ${{ secrets.DOCKER_USER }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
- name: "Create the manifest"
run: |
docker manifest create esphome/esphome:${TAG} \
esphome/esphome-aarch64:${TAG} \
esphome/esphome-amd64:${TAG} \
esphome/esphome-armv7:${TAG}
docker manifest push esphome/esphome:${TAG}
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: Publish docker beta tag
- name: Run manifest
run: |
docker manifest create esphome/esphome:beta \
esphome/esphome-aarch64:${TAG} \
esphome/esphome-amd64:${TAG} \
esphome/esphome-armv7:${TAG}
docker manifest push esphome/esphome:beta
- name: Publish docker latest tag
if: ${{ !github.event.release.prerelease }}
run: |
docker manifest create esphome/esphome:latest \
esphome/esphome-aarch64:${TAG} \
esphome/esphome-amd64:${TAG} \
esphome/esphome-armv7:${TAG}
docker manifest push esphome/esphome:latest
docker/build.py \
--tag "${{ needs.init.outputs.tag }}" \
--build-type "${{ matrix.build_type }}" \
manifest
deploy-hassio-repo:
if: github.repository == 'esphome/esphome'
if: github.repository == 'esphome/esphome' && github.event_name == 'release'
runs-on: ubuntu-latest
needs: [deploy-docker]
steps:
@ -324,4 +144,4 @@ jobs:
-X POST \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/esphome/hassio/actions/workflows/bump-version.yml/dispatches \
-d "{\"ref\":\"master\",\"inputs\":{\"version\":\"$TAG\"}}"
-d "{\"ref\":\"main\",\"inputs\":{\"version\":\"$TAG\"}}"

5
.gitignore vendored
View file

@ -13,6 +13,9 @@ __pycache__/
# Intellij Idea
.idea
# Vim
*.swp
# Hide some OS X stuff
.DS_Store
.AppleDouble
@ -100,6 +103,8 @@ CMakeLists.txt
# CMake
cmake-build-debug/
cmake-build-livingroom8266/
cmake-build-livingroom32/
cmake-build-release/
CMakeCache.txt

View file

@ -23,5 +23,5 @@ repos:
- id: no-commit-to-branch
args:
- --branch=dev
- --branch=master
- --branch=release
- --branch=beta

35
.vscode/tasks.json vendored
View file

@ -1,11 +1,32 @@
{
"version": "2.0.0",
"tasks": [
"version": "2.0.0",
"tasks": [
{
"label": "run",
"type": "shell",
"command": "python3 -m esphome dashboard config/",
"problemMatcher": []
},
{
"label": "clang-tidy",
"type": "shell",
"command": "test -f .gcc-flags.json || pio init --silent --ide atom; ./script/clang-tidy",
"problemMatcher": [
{
"label": "run",
"type": "shell",
"command": "python3 -m esphome config dashboard",
"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
}
]
}
]
]
}
]
}

View file

@ -13,30 +13,45 @@ esphome/core/* @esphome/core
# Integrations
esphome/components/ac_dimmer/* @glmnet
esphome/components/adc/* @esphome/core
esphome/components/addressable_light/* @justfalter
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/canbus/* @danielschramm @mvturnho
esphome/components/captive_portal/* @OttoWinter
esphome/components/climate/* @esphome/core
esphome/components/climate_ir/* @glmnet
esphome/components/coolix/* @glmnet
esphome/components/cover/* @esphome/core
esphome/components/cs5460a/* @balrog-kun
esphome/components/ct_clamp/* @jesserockz
esphome/components/debug/* @OttoWinter
esphome/components/dfplayer/* @glmnet
esphome/components/dht/* @OttoWinter
esphome/components/ds1307/* @badbadc0ffee
esphome/components/esp32_ble/* @jesserockz
esphome/components/esp32_ble_server/* @jesserockz
esphome/components/esp32_improv/* @jesserockz
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/havells_solar/* @sourabhjaiswal
esphome/components/homeassistant/* @OttoWinter
esphome/components/hrxl_maxsonar_wr/* @netmikey
esphome/components/i2c/* @esphome/core
esphome/components/improv/* @jesserockz
esphome/components/inkbird_ibsth1_mini/* @fkirill
esphome/components/inkplate6/* @jesserockz
esphome/components/integration/* @OttoWinter
@ -56,8 +71,17 @@ esphome/components/mcp23x17_base/* @jesserockz
esphome/components/mcp23xxx_base/* @jesserockz
esphome/components/mcp2515/* @danielschramm @mvturnho
esphome/components/mcp9808/* @k7hpn
esphome/components/midea_ac/* @dudanov
esphome/components/midea_dongle/* @dudanov
esphome/components/mitsubishi/* @RubyBailey
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
@ -65,6 +89,8 @@ 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/pulse_meter/* @stevebaxter
esphome/components/pvvx_mithermometer/* @pasiz
esphome/components/rc522/* @glmnet
esphome/components/rc522_i2c/* @glmnet
esphome/components/rc522_spi/* @glmnet
@ -72,9 +98,16 @@ esphome/components/restart/* @esphome/core
esphome/components/rf_bridge/* @jesserockz
esphome/components/rtttl/* @glmnet
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
esphome/components/sim800l/* @glmnet
esphome/components/sm2135/* @BoukeHaarsma23
esphome/components/spi/* @esphome/core
esphome/components/ssd1322_base/* @kbx81
esphome/components/ssd1322_spi/* @kbx81
@ -92,12 +125,17 @@ esphome/components/st7789v/* @kbx81
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/tuya/binary_sensor/* @jesserockz
esphome/components/tuya/climate/* @jesserockz
esphome/components/tuya/sensor/* @jesserockz
@ -109,3 +147,4 @@ esphome/components/web_server_base/* @OttoWinter
esphome/components/whirlpool/* @glmnet
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
esphome/components/xiaomi_mhoc401/* @vevsvevs
esphome/components/xpt2046/* @numo68

View file

@ -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.

View file

@ -1,4 +1,4 @@
# ESPHome [![Build Status](https://travis-ci.org/esphome/esphome.svg?branch=master)](https://travis-ci.org/esphome/esphome) [![Discord Chat](https://img.shields.io/discord/429907082951524364.svg)](https://discord.gg/KhAMKrd) [![GitHub release](https://img.shields.io/github/release/esphome/esphome.svg)](https://GitHub.com/esphome/esphome/releases/)
# ESPHome [![Discord Chat](https://img.shields.io/discord/429907082951524364.svg)](https://discord.gg/KhAMKrd) [![GitHub release](https://img.shields.io/github/release/esphome/esphome.svg)](https://GitHub.com/esphome/esphome/releases/)
[![ESPHome Logo](https://esphome.io/_images/logo-text.png)](https://esphome.io/)

View file

@ -1,9 +1,11 @@
ARG BUILD_FROM=esphome/esphome-base-amd64:2.6.0
ARG BUILD_FROM=esphome/esphome-base:latest
FROM ${BUILD_FROM}
# First install requirements to leverage caching when requirements don't change
COPY requirements.txt /
RUN pip3 install --no-cache-dir -r /requirements.txt
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
# Then copy esphome and install
COPY . .
@ -25,4 +27,4 @@ WORKDIR /config
# in every docker command twice
ENTRYPOINT ["esphome"]
# When no arguments given, start the dashboard in the workdir
CMD ["/config", "dashboard"]
CMD ["dashboard", "/config"]

View file

@ -1,13 +1 @@
FROM esphome/esphome-base-amd64:2.6.0
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
FROM esphome/esphome-lint:1.1

View file

@ -1,9 +1,11 @@
ARG BUILD_FROM
ARG BUILD_FROM=esphome/esphome-hassio-base:latest
FROM ${BUILD_FROM}
# First install requirements to leverage caching when requirements don't change
COPY requirements.txt /
RUN pip3 install --no-cache-dir -r /requirements.txt
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 root filesystem
COPY docker/rootfs/ /

View file

@ -1,7 +1,10 @@
FROM esphome/esphome-lint-base:2.6.0
ARG BUILD_FROM=esphome/esphome-lint-base:latest
FROM ${BUILD_FROM}
COPY requirements.txt requirements_test.txt /
RUN pip3 install --no-cache-dir -r /requirements.txt -r /requirements_test.txt
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

177
docker/build.py Executable file
View file

@ -0,0 +1,177 @@
#!/usr/bin/env python3
from dataclasses import dataclass
import subprocess
import argparse
import platform
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]
BASE_VERSION = "3.6.0"
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")
push_parser = subparsers.add_parser("push", help="Tag the already built image and push it to docker hub")
manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images")
# only lists some possibilities, doesn't have to be perfect
# https://stackoverflow.com/a/45125525
UNAME_TO_ARCH = {
"x86_64": ARCH_AMD64,
"aarch64": ARCH_AARCH64,
"aarch64_be": ARCH_AARCH64,
"arm": ARCH_ARMV7,
}
@dataclass(frozen=True)
class DockerParams:
build_from: str
build_to: str
manifest_to: str
dockerfile: 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_from = f"ghcr.io/{prefix}-base-{arch}:{BASE_VERSION}"
build_to = f"{prefix}-{arch}"
dockerfile = {
TYPE_DOCKER: "docker/Dockerfile",
TYPE_HA_ADDON: "docker/Dockerfile.hassio",
TYPE_LINT: "docker/Dockerfile.lint",
}[build_type]
return cls(
build_from=build_from,
build_to=build_to,
manifest_to=prefix,
dockerfile=dockerfile
)
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: "dev",
CHANNEL_BETA: "beta",
CHANNEL_RELEASE: "latest",
}[channel]
cache_img = f"ghcr.io/{params.build_to}:{cache_tag}"
run_command("docker", "pull", cache_img, ignore_error=True)
# 2. register QEMU binfmt (if not host arch)
is_native = UNAME_TO_ARCH.get(platform.machine()) == args.arch
if not is_native:
run_command(
"docker", "run", "--rm", "--privileged", "multiarch/qemu-user-static:5.2.0-2",
"--reset", "-p", "yes"
)
# 3. build
run_command(
"docker", "build",
"--build-arg", f"BUILD_FROM={params.build_from}",
"--build-arg", f"BUILD_VERSION={args.tag}",
"--tag", f"{params.build_to}:{args.tag}",
"--cache-from", cache_img,
"--file", params.dockerfile,
"."
)
elif args.command == "push":
params = DockerParams.for_type_arch(args.build_type, args.arch)
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]
src = imgs[0]
# 1. tag images
for img in imgs[1:]:
run_command(
"docker", "tag", src, img
)
# 2. push images
for img in imgs:
run_command(
"docker", "push", img
)
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()

View file

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# This script is used in the docker containers to preinstall
# all platformio libraries in the global storage
import configparser
import re
import subprocess
import sys
config = configparser.ConfigParser()
config.read(sys.argv[1])
libs = []
for line in config['common']['lib_deps'].splitlines():
# Format: '1655@1.0.2 ; TinyGPSPlus (has name conflict)' (includes comment)
m = re.search(r'([a-zA-Z0-9-_/]+@[0-9\.]+)', line)
if m is None:
continue
libs.append(m.group(1))
subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs])

View file

@ -23,4 +23,4 @@ if bashio::config.has_value 'relative_url'; then
fi
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

View file

@ -18,8 +18,8 @@ from esphome.const import (
CONF_ESPHOME,
CONF_PLATFORMIO_OPTIONS,
)
from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
from esphome.helpers import color, indent
from esphome.core import CORE, EsphomeError, coroutine
from esphome.helpers import indent
from esphome.util import (
run_external_command,
run_external_process,
@ -27,6 +27,7 @@ from esphome.util import (
list_yaml_files,
get_serial_ports,
)
from esphome.log import color, setup_log, Fore
_LOGGER = logging.getLogger(__name__)
@ -57,7 +58,7 @@ 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]
@ -126,15 +127,16 @@ 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("//", "")
cg.add(cg.LineComment(indent(conf_str)))
yield coro(conf)
await coro(conf)
if hasattr(coro, "priority"):
wrapped.priority = coro.priority
return wrapped
@ -263,50 +265,10 @@ def clean_mqtt(config, args):
)
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:
import colorama
colorama.init(strip=True)
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
def command_wizard(args):
from esphome import wizard
return wizard.wizard(args.configuration[0])
return wizard.wizard(args.configuration)
def command_config(args, config):
@ -322,7 +284,7 @@ def command_vscode(args):
logging.disable(logging.INFO)
logging.disable(logging.WARNING)
CORE.config_path = args.configuration[0]
CORE.config_path = args.configuration
vscode.read_config(args)
@ -342,7 +304,7 @@ def command_compile(args, config):
def command_upload(args, config):
port = choose_upload_log_host(
default=args.upload_port,
default=args.device,
check_default=None,
show_ota=True,
show_mqtt=False,
@ -357,7 +319,7 @@ def command_upload(args, config):
def command_logs(args, config):
port = choose_upload_log_host(
default=args.serial_port,
default=args.device,
check_default=None,
show_ota=False,
show_mqtt=True,
@ -375,7 +337,7 @@ def command_run(args, config):
return exit_code
_LOGGER.info("Successfully compiled program.")
port = choose_upload_log_host(
default=args.upload_port,
default=args.device,
check_default=None,
show_ota=True,
show_mqtt=False,
@ -388,7 +350,7 @@ def command_run(args, config):
if args.no_logs:
return 0
port = choose_upload_log_host(
default=args.upload_port,
default=args.device,
check_default=port,
show_ota=False,
show_mqtt=True,
@ -442,30 +404,30 @@ 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("Updating {}".format(color(Fore.CYAN, f)))
print("-" * twidth)
print()
rc = run_external_process(
"esphome", "--dashboard", f, "run", "--no-logs", "--upload-port", "OTA"
"esphome", "--dashboard", "run", "--no-logs", "--device", "OTA", f
)
if rc == 0:
print_bar("[{}] {}".format(color("bold_green", "SUCCESS"), f))
print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f))
success[f] = True
else:
print_bar("[{}] {}".format(color("bold_red", "ERROR"), f))
print_bar("[{}] {}".format(color(Fore.BOLD_RED, "ERROR"), f))
success[f] = False
print()
print()
print()
print_bar("[{}]".format(color("bold_white", "SUMMARY")))
print_bar("[{}]".format(color(Fore.BOLD_WHITE, "SUMMARY")))
failed = 0
for f in files:
if success[f]:
print(" - {}: {}".format(f, color("green", "SUCCESS")))
print(" - {}: {}".format(f, color(Fore.GREEN, "SUCCESS")))
else:
print(" - {}: {}".format(f, color("bold_red", "FAILED")))
print(" - {}: {}".format(f, color(Fore.BOLD_RED, "FAILED")))
failed += 1
return failed
@ -491,15 +453,17 @@ POST_CONFIG_ACTIONS = {
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"
options_parser = argparse.ArgumentParser(add_help=False)
options_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"
options_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(
options_parser.add_argument(
"--dashboard", help=argparse.SUPPRESS, action="store_true"
)
options_parser.add_argument(
"-s",
"--substitution",
nargs=2,
@ -507,17 +471,100 @@ def parse_args(argv):
help="Add a substitution",
metavar=("key", "value"),
)
parser.add_argument(
"configuration", help="Your YAML configuration file.", nargs="*"
# Keep backward compatibility with the old command line format of
# esphome <config> <command>.
#
# 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, construct an ad-hoc parser for the old format 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 continue on with the normal parser (after raising a deprecation warning).
#
# 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")
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",
],
)
subparsers = parser.add_subparsers(help="Commands", dest="command")
# on Python 3.9+ we can simply set exit_on_error=False in the constructor
def _raise(x):
raise argparse.ArgumentError(None, x)
compat_parser.error = _raise
deprecated_argv_suggestion = None
if ["dashboard", "config"] == argv[1:3] or ["version"] == argv[1:2]:
# this is most likely meant in new-style arg format. do not try compat parsing
pass
else:
try:
result, unparsed = compat_parser.parse_known_args(argv[1:])
last_option = len(argv) - len(unparsed) - 1 - len(result.configuration)
unparsed = [
"--device" if arg in ("--upload-port", "--serial-port") else arg
for arg in unparsed
]
argv = (
argv[0:last_option] + [result.command] + result.configuration + unparsed
)
deprecated_argv_suggestion = argv
except argparse.ArgumentError:
# This is not an old-style command line, so we don't have to do anything.
pass
# And continue on with regular parsing
parser = argparse.ArgumentParser(
description=f"ESPHome v{const.__version__}", parents=[options_parser]
)
parser.set_defaults(deprecated_argv_suggestion=deprecated_argv_suggestion)
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_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_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.",
@ -525,106 +572,124 @@ def parse_args(argv):
)
parser_upload = subparsers.add_parser(
"upload", help="Validate the configuration " "and upload the latest binary."
"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.",
"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_logs = subparsers.add_parser(
"logs", help="Validate the configuration " "and show all MQTT logs."
"logs",
help="Validate the configuration and show all logs.",
parents=[mqtt_options],
)
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.",
"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_run = subparsers.add_parser(
"run",
help="Validate the configuration, create a binary, "
"upload it, and start MQTT logs.",
help="Validate the configuration, create a binary, upload it, and start logs.",
parents=[mqtt_options],
)
parser_run.add_argument(
"--upload-port",
help="Manually specify the upload port/ip to use. "
"For example /dev/cu.SLAB_USBtoUART.",
"configuration", help="Your YAML configuration file(s).", nargs="+"
)
parser_run.add_argument(
"--no-logs", help="Disable starting MQTT logs.", action="store_true"
"--device",
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
)
parser_run.add_argument(
"--topic", help="Manually set the topic to subscribe to for logs."
"--no-logs", help="Disable starting logs.", action="store_true"
)
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_clean = subparsers.add_parser(
"clean-mqtt", help="Helper to clear an MQTT topic from " "retain messages."
"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="+"
)
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.")
subparsers.add_parser(
parser_wizard = subparsers.add_parser(
"wizard",
help="A helpful setup wizard that will guide "
"you through setting up esphome.",
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(
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("version", help="Print the esphome version and exit.")
subparsers.add_parser("version", help="Print the ESPHome version and exit.")
subparsers.add_parser("clean", help="Delete all temporary build files.")
parser_clean = subparsers.add_parser(
"clean", help="Delete all temporary build files."
)
parser_clean.add_argument(
"configuration", help="Your YAML configuration file(s).", nargs="+"
)
dashboard = subparsers.add_parser(
parser_dashboard = subparsers.add_parser(
"dashboard", help="Create a simple web server for a dashboard."
)
dashboard.add_argument(
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,
)
dashboard.add_argument(
parser_dashboard.add_argument(
"--username",
help="The optional username to require " "for authentication.",
help="The optional username to require for authentication.",
type=str,
default="",
)
dashboard.add_argument(
parser_dashboard.add_argument(
"--password",
help="The optional password to require " "for authentication.",
help="The optional password to require for authentication.",
type=str,
default="",
)
dashboard.add_argument(
parser_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(
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
)
vscode = subparsers.add_parser("vscode", help=argparse.SUPPRESS)
vscode.add_argument("--ace", action="store_true")
parser_vscode = subparsers.add_parser("vscode")
parser_vscode.add_argument(
"configuration", help="Your YAML configuration file.", nargs=1
)
parser_vscode.add_argument("--ace", action="store_true")
subparsers.add_parser("update-all", help=argparse.SUPPRESS)
parser_update = subparsers.add_parser("update-all")
parser_update.add_argument(
"configuration", help="Your YAML configuration file directory.", nargs=1
)
return parser.parse_args(argv[1:])
@ -634,14 +699,18 @@ def run_esphome(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
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[1:]))
if sys.version_info < (3, 6, 0):
if sys.version_info < (3, 7, 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+"
"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
@ -649,7 +718,7 @@ def run_esphome(argv):
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:
@ -667,7 +736,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

View file

@ -13,7 +13,8 @@ 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.helpers import resolve_ip_address, indent
from esphome.log import color, Fore
from esphome.util import safe_print
_LOGGER = logging.getLogger(__name__)
@ -488,7 +489,7 @@ def run_logs(config, address):
text = msg.message
if msg.send_failed:
text = color(
"white",
Fore.WHITE,
"(Message skipped because it was too big to fit in "
"TCP buffer - This is only cosmetic)",
)

View file

@ -10,7 +10,6 @@ from esphome.const import (
CONF_TYPE_ID,
CONF_TIME,
)
from esphome.core import coroutine
from esphome.jsonschema import jschema_extractor
from esphome.util import Registry
@ -142,27 +141,27 @@ 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)
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)
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)
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(
@ -177,26 +176,26 @@ def lambda_condition_to_code(config, condition_id, template_arg, args):
}
).extend(cv.COMPONENT_SCHEMA),
)
def for_condition_to_code(config, condition_id, template_arg, args):
condition = yield build_condition(
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):
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(
@ -211,16 +210,16 @@ def delay_action_to_code(config, action_id, template_arg, args):
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)
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(
@ -233,12 +232,12 @@ def if_action_to_code(config, action_id, template_arg, args):
}
),
)
def while_action_to_code(config, action_id, template_arg, args):
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
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
def validate_wait_until(value):
@ -253,17 +252,17 @@ def validate_wait_until(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)
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
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_)
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(
@ -275,54 +274,51 @@ def lambda_action_to_code(config, action_id, template_arg, args):
}
),
)
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)
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):
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):
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

875
esphome/boards.py Normal file
View file

@ -0,0 +1,875 @@
ESP8266_BASE_PINS = {
"A0": 17,
"SS": 15,
"MOSI": 13,
"MISO": 12,
"SCK": 14,
"SDA": 4,
"SCL": 5,
"RX": 3,
"TX": 1,
}
ESP8266_BOARD_PINS = {
"d1": {
"D0": 3,
"D1": 1,
"D2": 16,
"D3": 5,
"D4": 4,
"D5": 14,
"D6": 12,
"D7": 13,
"D8": 0,
"D9": 2,
"D10": 15,
"D11": 13,
"D12": 14,
"D13": 14,
"D14": 4,
"D15": 5,
"LED": 2,
},
"d1_mini": {
"D0": 16,
"D1": 5,
"D2": 4,
"D3": 0,
"D4": 2,
"D5": 14,
"D6": 12,
"D7": 13,
"D8": 15,
"LED": 2,
},
"d1_mini_lite": "d1_mini",
"d1_mini_pro": "d1_mini",
"esp01": {},
"esp01_1m": {},
"esp07": {},
"esp12e": {},
"esp210": {},
"esp8285": {},
"esp_wroom_02": {},
"espduino": {"LED": 16},
"espectro": {"LED": 15, "BUTTON": 2},
"espino": {"LED": 2, "LED_RED": 2, "LED_GREEN": 4, "LED_BLUE": 5, "BUTTON": 0},
"espinotee": {"LED": 16},
"espresso_lite_v1": {"LED": 16},
"espresso_lite_v2": {"LED": 2},
"gen4iod": {},
"heltec_wifi_kit_8": "d1_mini",
"huzzah": {
"LED": 0,
"LED_RED": 0,
"LED_BLUE": 2,
"D4": 4,
"D5": 5,
"D12": 12,
"D13": 13,
"D14": 14,
"D15": 15,
"D16": 16,
},
"inventone": {},
"modwifi": {},
"nodemcu": {
"D0": 16,
"D1": 5,
"D2": 4,
"D3": 0,
"D4": 2,
"D5": 14,
"D6": 12,
"D7": 13,
"D8": 15,
"D9": 3,
"D10": 1,
"LED": 16,
},
"nodemcuv2": "nodemcu",
"oak": {
"P0": 2,
"P1": 5,
"P2": 0,
"P3": 3,
"P4": 1,
"P5": 4,
"P6": 15,
"P7": 13,
"P8": 12,
"P9": 14,
"P10": 16,
"P11": 17,
"LED": 5,
},
"phoenix_v1": {"LED": 16},
"phoenix_v2": {"LED": 2},
"sparkfunBlynk": "thing",
"thing": {"LED": 5, "SDA": 2, "SCL": 14},
"thingdev": "thing",
"wifi_slot": {"LED": 2},
"wifiduino": {
"D0": 3,
"D1": 1,
"D2": 2,
"D3": 0,
"D4": 4,
"D5": 5,
"D6": 16,
"D7": 14,
"D8": 12,
"D9": 13,
"D10": 15,
"D11": 13,
"D12": 12,
"D13": 14,
},
"wifinfo": {
"LED": 12,
"D0": 16,
"D1": 5,
"D2": 4,
"D3": 0,
"D4": 2,
"D5": 14,
"D6": 12,
"D7": 13,
"D8": 15,
"D9": 3,
"D10": 1,
},
"wio_link": {"LED": 2, "GROVE": 15, "D0": 14, "D1": 12, "D2": 13, "BUTTON": 0},
"wio_node": {"LED": 2, "GROVE": 15, "D0": 3, "D1": 5, "BUTTON": 0},
"xinabox_cw01": {"SDA": 2, "SCL": 14, "LED": 5, "LED_RED": 12, "LED_GREEN": 13},
}
FLASH_SIZE_1_MB = 2 ** 20
FLASH_SIZE_512_KB = FLASH_SIZE_1_MB // 2
FLASH_SIZE_2_MB = 2 * FLASH_SIZE_1_MB
FLASH_SIZE_4_MB = 4 * FLASH_SIZE_1_MB
FLASH_SIZE_16_MB = 16 * FLASH_SIZE_1_MB
ESP8266_FLASH_SIZES = {
"d1": FLASH_SIZE_4_MB,
"d1_mini": FLASH_SIZE_4_MB,
"d1_mini_lite": FLASH_SIZE_1_MB,
"d1_mini_pro": FLASH_SIZE_16_MB,
"esp01": FLASH_SIZE_512_KB,
"esp01_1m": FLASH_SIZE_1_MB,
"esp07": FLASH_SIZE_4_MB,
"esp12e": FLASH_SIZE_4_MB,
"esp210": FLASH_SIZE_4_MB,
"esp8285": FLASH_SIZE_1_MB,
"esp_wroom_02": FLASH_SIZE_2_MB,
"espduino": FLASH_SIZE_4_MB,
"espectro": FLASH_SIZE_4_MB,
"espino": FLASH_SIZE_4_MB,
"espinotee": FLASH_SIZE_4_MB,
"espresso_lite_v1": FLASH_SIZE_4_MB,
"espresso_lite_v2": FLASH_SIZE_4_MB,
"gen4iod": FLASH_SIZE_512_KB,
"heltec_wifi_kit_8": FLASH_SIZE_4_MB,
"huzzah": FLASH_SIZE_4_MB,
"inventone": FLASH_SIZE_4_MB,
"modwifi": FLASH_SIZE_2_MB,
"nodemcu": FLASH_SIZE_4_MB,
"nodemcuv2": FLASH_SIZE_4_MB,
"oak": FLASH_SIZE_4_MB,
"phoenix_v1": FLASH_SIZE_4_MB,
"phoenix_v2": FLASH_SIZE_4_MB,
"sparkfunBlynk": FLASH_SIZE_4_MB,
"thing": FLASH_SIZE_512_KB,
"thingdev": FLASH_SIZE_512_KB,
"wifi_slot": FLASH_SIZE_1_MB,
"wifiduino": FLASH_SIZE_4_MB,
"wifinfo": FLASH_SIZE_1_MB,
"wio_link": FLASH_SIZE_4_MB,
"wio_node": FLASH_SIZE_4_MB,
"xinabox_cw01": FLASH_SIZE_4_MB,
}
ESP8266_LD_SCRIPTS = {
FLASH_SIZE_512_KB: ("eagle.flash.512k0.ld", "eagle.flash.512k.ld"),
FLASH_SIZE_1_MB: ("eagle.flash.1m0.ld", "eagle.flash.1m.ld"),
FLASH_SIZE_2_MB: ("eagle.flash.2m.ld", "eagle.flash.2m.ld"),
FLASH_SIZE_4_MB: ("eagle.flash.4m.ld", "eagle.flash.4m.ld"),
FLASH_SIZE_16_MB: ("eagle.flash.16m.ld", "eagle.flash.16m14m.ld"),
}
ESP32_BASE_PINS = {
"TX": 1,
"RX": 3,
"SDA": 21,
"SCL": 22,
"SS": 5,
"MOSI": 23,
"MISO": 19,
"SCK": 18,
"A0": 36,
"A3": 39,
"A4": 32,
"A5": 33,
"A6": 34,
"A7": 35,
"A10": 4,
"A11": 0,
"A12": 2,
"A13": 15,
"A14": 13,
"A15": 12,
"A16": 14,
"A17": 27,
"A18": 25,
"A19": 26,
"T0": 4,
"T1": 0,
"T2": 2,
"T3": 15,
"T4": 13,
"T5": 12,
"T6": 14,
"T7": 27,
"T8": 33,
"T9": 32,
"DAC1": 25,
"DAC2": 26,
"SVP": 36,
"SVN": 39,
}
ESP32_BOARD_PINS = {
"alksesp32": {
"A0": 32,
"A1": 33,
"A2": 25,
"A3": 26,
"A4": 27,
"A5": 14,
"A6": 12,
"A7": 15,
"D0": 40,
"D1": 41,
"D10": 19,
"D11": 21,
"D12": 22,
"D13": 23,
"D2": 15,
"D3": 2,
"D4": 0,
"D5": 4,
"D6": 16,
"D7": 17,
"D8": 5,
"D9": 18,
"DHT_PIN": 26,
"LED": 23,
"L_B": 5,
"L_G": 17,
"L_R": 22,
"L_RGB_B": 16,
"L_RGB_G": 21,
"L_RGB_R": 4,
"L_Y": 23,
"MISO": 22,
"MOSI": 21,
"PHOTO": 25,
"PIEZO1": 19,
"PIEZO2": 18,
"POT1": 32,
"POT2": 33,
"S1": 4,
"S2": 16,
"S3": 18,
"S4": 19,
"S5": 21,
"SCK": 23,
"SCL": 14,
"SDA": 27,
"SS": 19,
"SW1": 15,
"SW2": 2,
"SW3": 0,
},
"bpi-bit": {
"BUTTON_A": 35,
"BUTTON_B": 27,
"BUZZER": 25,
"LIGHT_SENSOR1": 36,
"LIGHT_SENSOR2": 39,
"MPU9250_INT": 0,
"P0": 25,
"P1": 32,
"P10": 26,
"P11": 27,
"P12": 2,
"P13": 18,
"P14": 19,
"P15": 23,
"P16": 5,
"P19": 22,
"P2": 33,
"P20": 21,
"P3": 13,
"P4": 15,
"P5": 35,
"P6": 12,
"P7": 14,
"P8": 16,
"P9": 17,
"RGB_LED": 4,
"TEMPERATURE_SENSOR": 34,
},
"d-duino-32": {
"D1": 5,
"D10": 1,
"D2": 4,
"D3": 0,
"D4": 2,
"D5": 14,
"D6": 12,
"D7": 13,
"D8": 15,
"D9": 3,
"MISO": 12,
"MOSI": 13,
"SCK": 14,
"SCL": 4,
"SDA": 5,
"SS": 15,
},
"esp-wrover-kit": {},
"esp32-devkitlipo": {},
"esp32-evb": {
"BUTTON": 34,
"MISO": 15,
"MOSI": 2,
"SCK": 14,
"SCL": 16,
"SDA": 13,
"SS": 17,
},
"esp32-gateway": {"BUTTON": 34, "LED": 33, "SCL": 16, "SDA": 32},
"esp32-poe-iso": {
"BUTTON": 34,
"MISO": 15,
"MOSI": 2,
"SCK": 14,
"SCL": 16,
"SDA": 13,
},
"esp32-poe": {"BUTTON": 34, "MISO": 15, "MOSI": 2, "SCK": 14, "SCL": 16, "SDA": 13},
"esp32-pro": {
"BUTTON": 34,
"MISO": 15,
"MOSI": 2,
"SCK": 14,
"SCL": 16,
"SDA": 13,
"SS": 17,
},
"esp320": {
"LED": 5,
"MISO": 12,
"MOSI": 13,
"SCK": 14,
"SCL": 14,
"SDA": 2,
"SS": 15,
},
"esp32cam": {},
"esp32dev": {},
"esp32doit-devkit-v1": {"LED": 2},
"esp32thing": {"BUTTON": 0, "LED": 5, "SS": 2},
"esp32vn-iot-uno": {},
"espea32": {"BUTTON": 0, "LED": 5},
"espectro32": {"LED": 15, "SD_SS": 33},
"espino32": {"BUTTON": 0, "LED": 16},
"featheresp32": {
"A0": 26,
"A1": 25,
"A10": 27,
"A11": 12,
"A12": 13,
"A13": 35,
"A2": 34,
"A4": 36,
"A5": 4,
"A6": 14,
"A7": 32,
"A8": 15,
"A9": 33,
"Ax": 2,
"LED": 13,
"MOSI": 18,
"RX": 16,
"SCK": 5,
"SDA": 23,
"SS": 33,
"TX": 17,
},
"firebeetle32": {"LED": 2},
"fm-devkit": {
"D0": 34,
"D1": 35,
"D10": 0,
"D2": 32,
"D3": 33,
"D4": 27,
"D5": 14,
"D6": 12,
"D7": 13,
"D8": 15,
"D9": 23,
"I2S_DOUT": 22,
"I2S_LRCLK": 25,
"I2S_MCLK": 2,
"I2S_SCLK": 26,
"LED": 5,
"SCL": 17,
"SDA": 16,
"SW1": 4,
"SW2": 18,
"SW3": 19,
"SW4": 21,
},
"frogboard": {},
"heltec_wifi_kit_32": {
"A1": 37,
"A2": 38,
"BUTTON": 0,
"LED": 25,
"RST_OLED": 16,
"SCL_OLED": 15,
"SDA_OLED": 4,
"Vext": 21,
},
"heltec_wifi_lora_32": {
"BUTTON": 0,
"DIO0": 26,
"DIO1": 33,
"DIO2": 32,
"LED": 25,
"MOSI": 27,
"RST_LoRa": 14,
"RST_OLED": 16,
"SCK": 5,
"SCL_OLED": 15,
"SDA_OLED": 4,
"SS": 18,
"Vext": 21,
},
"heltec_wifi_lora_32_V2": {
"BUTTON": 0,
"DIO0": 26,
"DIO1": 35,
"DIO2": 34,
"LED": 25,
"MOSI": 27,
"RST_LoRa": 14,
"RST_OLED": 16,
"SCK": 5,
"SCL_OLED": 15,
"SDA_OLED": 4,
"SS": 18,
"Vext": 21,
},
"heltec_wireless_stick": {
"BUTTON": 0,
"DIO0": 26,
"DIO1": 35,
"DIO2": 34,
"LED": 25,
"MOSI": 27,
"RST_LoRa": 14,
"RST_OLED": 16,
"SCK": 5,
"SCL_OLED": 15,
"SDA_OLED": 4,
"SS": 18,
"Vext": 21,
},
"hornbill32dev": {"BUTTON": 0, "LED": 13},
"hornbill32minima": {"SS": 2},
"intorobot": {
"A1": 39,
"A2": 35,
"A3": 25,
"A4": 26,
"A5": 14,
"A6": 12,
"A7": 15,
"A8": 13,
"A9": 2,
"BUTTON": 0,
"D0": 19,
"D1": 23,
"D2": 18,
"D3": 17,
"D4": 16,
"D5": 5,
"D6": 4,
"LED": 4,
"MISO": 17,
"MOSI": 16,
"RGB_B_BUILTIN": 22,
"RGB_G_BUILTIN": 21,
"RGB_R_BUILTIN": 27,
"SCL": 19,
"SDA": 23,
"T0": 19,
"T1": 23,
"T2": 18,
"T3": 17,
"T4": 16,
"T5": 5,
"T6": 4,
},
"iotaap_magnolia": {},
"iotbusio": {},
"iotbusproteus": {},
"lolin32": {"LED": 5},
"lolin32_lite": {"LED": 22},
"lolin_d32": {"LED": 5, "_VBAT": 35},
"lolin_d32_pro": {"LED": 5, "_VBAT": 35},
"lopy": {
"A1": 37,
"A2": 38,
"LED": 0,
"MISO": 37,
"MOSI": 22,
"SCK": 13,
"SCL": 13,
"SDA": 12,
"SS": 17,
},
"lopy4": {
"A1": 37,
"A2": 38,
"LED": 0,
"MISO": 37,
"MOSI": 22,
"SCK": 13,
"SCL": 13,
"SDA": 12,
"SS": 18,
},
"m5stack-core-esp32": {
"ADC1": 35,
"ADC2": 36,
"G0": 0,
"G1": 1,
"G12": 12,
"G13": 13,
"G15": 15,
"G16": 16,
"G17": 17,
"G18": 18,
"G19": 19,
"G2": 2,
"G21": 21,
"G22": 22,
"G23": 23,
"G25": 25,
"G26": 26,
"G3": 3,
"G34": 34,
"G35": 35,
"G36": 36,
"G5": 5,
"RXD2": 16,
"TXD2": 17,
},
"m5stack-fire": {
"ADC1": 35,
"ADC2": 36,
"G0": 0,
"G1": 1,
"G12": 12,
"G13": 13,
"G15": 15,
"G16": 16,
"G17": 17,
"G18": 18,
"G19": 19,
"G2": 2,
"G21": 21,
"G22": 22,
"G23": 23,
"G25": 25,
"G26": 26,
"G3": 3,
"G34": 34,
"G35": 35,
"G36": 36,
"G5": 5,
},
"m5stack-grey": {
"ADC1": 35,
"ADC2": 36,
"G0": 0,
"G1": 1,
"G12": 12,
"G13": 13,
"G15": 15,
"G16": 16,
"G17": 17,
"G18": 18,
"G19": 19,
"G2": 2,
"G21": 21,
"G22": 22,
"G23": 23,
"G25": 25,
"G26": 26,
"G3": 3,
"G34": 34,
"G35": 35,
"G36": 36,
"G5": 5,
"RXD2": 16,
"TXD2": 17,
},
"m5stick-c": {
"ADC1": 35,
"ADC2": 36,
"G0": 0,
"G10": 10,
"G26": 26,
"G32": 32,
"G33": 33,
"G36": 36,
"G37": 37,
"G39": 39,
"G9": 9,
"MISO": 36,
"MOSI": 15,
"SCK": 13,
"SCL": 33,
"SDA": 32,
},
"magicbit": {
"BLUE_LED": 17,
"BUZZER": 25,
"GREEN_LED": 16,
"LDR": 36,
"LED": 16,
"LEFT_BUTTON": 35,
"MOTOR1A": 27,
"MOTOR1B": 18,
"MOTOR2A": 16,
"MOTOR2B": 17,
"POT": 39,
"RED_LED": 27,
"RIGHT_PUTTON": 34,
"YELLOW_LED": 18,
},
"mhetesp32devkit": {"LED": 2},
"mhetesp32minikit": {"LED": 2},
"microduino-core-esp32": {
"A0": 12,
"A1": 13,
"A10": 25,
"A11": 26,
"A12": 27,
"A13": 14,
"A2": 15,
"A3": 4,
"A6": 38,
"A7": 37,
"A8": 32,
"A9": 33,
"D0": 3,
"D1": 1,
"D10": 5,
"D11": 23,
"D12": 19,
"D13": 18,
"D14": 12,
"D15": 13,
"D16": 15,
"D17": 4,
"D18": 22,
"D19": 21,
"D2": 16,
"D20": 38,
"D21": 37,
"D3": 17,
"D4": 32,
"D5": 33,
"D6": 25,
"D7": 26,
"D8": 27,
"D9": 14,
"SCL": 21,
"SCL1": 13,
"SDA": 22,
"SDA1": 12,
},
"nano32": {"BUTTON": 0, "LED": 16},
"nina_w10": {
"D0": 3,
"D1": 1,
"D10": 5,
"D11": 19,
"D12": 23,
"D13": 18,
"D14": 13,
"D15": 12,
"D16": 32,
"D17": 33,
"D18": 21,
"D19": 34,
"D2": 26,
"D20": 36,
"D21": 39,
"D3": 25,
"D4": 35,
"D5": 27,
"D6": 22,
"D7": 0,
"D8": 15,
"D9": 14,
"LED_BLUE": 21,
"LED_GREEN": 33,
"LED_RED": 23,
"SCL": 13,
"SDA": 12,
"SW1": 33,
"SW2": 27,
},
"node32s": {},
"nodemcu-32s": {"BUTTON": 0, "LED": 2},
"odroid_esp32": {"ADC1": 35, "ADC2": 36, "LED": 2, "SCL": 4, "SDA": 15, "SS": 22},
"onehorse32dev": {"A1": 37, "A2": 38, "BUTTON": 0, "LED": 5},
"oroca_edubot": {
"A0": 34,
"A1": 39,
"A2": 36,
"A3": 33,
"D0": 4,
"D1": 16,
"D2": 17,
"D3": 22,
"D4": 23,
"D5": 5,
"D6": 18,
"D7": 19,
"D8": 33,
"LED": 13,
"MOSI": 18,
"RX": 16,
"SCK": 5,
"SDA": 23,
"SS": 2,
"TX": 17,
"VBAT": 35,
},
"pico32": {},
"pocket_32": {"LED": 16},
"pycom_gpy": {
"A1": 37,
"A2": 38,
"LED": 0,
"MISO": 37,
"MOSI": 22,
"SCK": 13,
"SCL": 13,
"SDA": 12,
"SS": 17,
},
"quantum": {},
"sparkfun_lora_gateway_1-channel": {"MISO": 12, "MOSI": 13, "SCK": 14, "SS": 16},
"tinypico": {},
"ttgo-lora32-v1": {
"A1": 37,
"A2": 38,
"BUTTON": 0,
"LED": 2,
"MOSI": 27,
"SCK": 5,
"SS": 18,
},
"ttgo-t-beam": {"BUTTON": 39, "LED": 14, "MOSI": 27, "SCK": 5, "SS": 18},
"ttgo-t-watch": {"BUTTON": 36, "MISO": 2, "MOSI": 15, "SCK": 14, "SS": 13},
"ttgo-t1": {"LED": 22, "MISO": 2, "MOSI": 15, "SCK": 14, "SCL": 23, "SS": 13},
"ttgo-t7-v13-mini32": {"LED": 22},
"ttgo-t7-v14-mini32": {"LED": 19},
"turta_iot_node": {},
"vintlabs-devkit-v1": {
"LED": 2,
"PWM0": 12,
"PWM1": 13,
"PWM2": 14,
"PWM3": 15,
"PWM4": 16,
"PWM5": 17,
"PWM6": 18,
"PWM7": 19,
},
"wemos_d1_mini32": {
"D0": 26,
"D1": 22,
"D2": 21,
"D3": 17,
"D4": 16,
"D5": 18,
"D6": 19,
"D7": 23,
"D8": 5,
"LED": 2,
"RXD": 3,
"TXD": 1,
"_VBAT": 35,
},
"wemosbat": {"LED": 16},
"wesp32": {"MISO": 32, "SCL": 4, "SDA": 15},
"widora-air": {
"A1": 39,
"A2": 35,
"A3": 25,
"A4": 26,
"A5": 14,
"A6": 12,
"A7": 15,
"A8": 13,
"A9": 2,
"BUTTON": 0,
"D0": 19,
"D1": 23,
"D2": 18,
"D3": 17,
"D4": 16,
"D5": 5,
"D6": 4,
"LED": 25,
"MISO": 17,
"MOSI": 16,
"SCL": 19,
"SDA": 23,
"T0": 19,
"T1": 23,
"T2": 18,
"T3": 17,
"T4": 16,
"T5": 5,
"T6": 4,
},
"xinabox_cw02": {"LED": 27},
}
ESP32_C3_BASE_PINS = {
"TX": 21,
"RX": 20,
"ADC1_0": 0,
"ADC1_1": 1,
"ADC1_2": 2,
"ADC1_3": 3,
"ADC1_4": 4,
"ADC2_0": 5,
}
ESP32_C3_BOARD_PINS = {
"esp32-c3-devkitm-1": {"LED": 8},
"esp32-c3-devkitc-02": "esp32-c3-devkitm-1",
}

View file

@ -19,8 +19,10 @@ from esphome.cpp_generator import ( # noqa
Statement,
LineComment,
progmem_array,
static_const_array,
statement,
variable,
new_variable,
Pvariable,
new_Pvariable,
add,
@ -58,6 +60,7 @@ from esphome.cpp_types import ( # noqa
uint8,
uint16,
uint32,
uint64,
int32,
const_char_ptr,
NAN,

View file

@ -4,7 +4,7 @@
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...");

View file

@ -18,16 +18,16 @@ CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend(
).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))

View file

@ -9,15 +9,15 @@
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;
static const uint32_t GATE_ENABLE_TIME = 10;
/// Function called from timer interrupt
/// Input is current time in microseconds (micros())
@ -125,7 +125,7 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
}
void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) {
// Attaching pin interrupts on the same pin will override the previous interupt
// 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*.

View file

@ -32,18 +32,18 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
).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)
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]))

View file

@ -21,8 +21,7 @@ CONFIG_SCHEMA = cv.Schema({})
"Adalight",
{cv.GenerateID(CONF_UART_ID): cv.use_id(uart.UARTComponent)},
)
def adalight_light_effect_to_code(config, effect_id):
async def adalight_light_effect_to_code(config, effect_id):
effect = cg.new_Pvariable(effect_id, config[CONF_NAME])
yield uart.register_uart_device(effect, config)
yield effect
await uart.register_uart_device(effect, config)
return effect

View file

@ -4,7 +4,7 @@
namespace esphome {
namespace adalight {
static const char *TAG = "adalight_light_effect";
static const char *const TAG = "adalight_light_effect";
static const uint32_t ADALIGHT_ACK_INTERVAL = 1000;
static const uint32_t ADALIGHT_RECEIVE_TIMEOUT = 1000;

View file

@ -8,10 +8,50 @@ ADC_MODE(ADC_VCC)
namespace esphome {
namespace adc {
static const char *TAG = "adc";
static const char *const TAG = "adc";
#ifdef ARDUINO_ARCH_ESP32
void ADCSensor::set_attenuation(adc_attenuation_t attenuation) { this->attenuation_ = attenuation; }
void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; }
inline adc1_channel_t gpio_to_adc1(uint8_t pin) {
#if CONFIG_IDF_TARGET_ESP32
switch (pin) {
case 36:
return ADC1_CHANNEL_0;
case 37:
return ADC1_CHANNEL_1;
case 38:
return ADC1_CHANNEL_2;
case 39:
return ADC1_CHANNEL_3;
case 32:
return ADC1_CHANNEL_4;
case 33:
return ADC1_CHANNEL_5;
case 34:
return ADC1_CHANNEL_6;
case 35:
return ADC1_CHANNEL_7;
default:
return ADC1_CHANNEL_MAX;
}
#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2
switch (pin) {
case 0:
return ADC1_CHANNEL_0;
case 1:
return ADC1_CHANNEL_1;
case 2:
return ADC1_CHANNEL_2;
case 3:
return ADC1_CHANNEL_3;
case 4:
return ADC1_CHANNEL_4;
default:
return ADC1_CHANNEL_MAX;
}
#endif
}
#endif
void ADCSensor::setup() {
@ -21,7 +61,11 @@ void ADCSensor::setup() {
#endif
#ifdef ARDUINO_ARCH_ESP32
analogSetPinAttenuation(this->pin_, this->attenuation_);
adc1_config_channel_atten(gpio_to_adc1(pin_), attenuation_);
adc1_config_width(ADC_WIDTH_BIT_12);
#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2
adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_));
#endif
#endif
}
void ADCSensor::dump_config() {
@ -36,18 +80,20 @@ void ADCSensor::dump_config() {
#ifdef ARDUINO_ARCH_ESP32
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
switch (this->attenuation_) {
case ADC_0db:
case ADC_ATTEN_DB_0:
ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
break;
case ADC_2_5db:
case ADC_ATTEN_DB_2_5:
ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)");
break;
case ADC_6db:
case ADC_ATTEN_DB_6:
ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)");
break;
case ADC_11db:
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
LOG_UPDATE_INTERVAL(this);
@ -60,21 +106,43 @@ void ADCSensor::update() {
}
float ADCSensor::sample() {
#ifdef ARDUINO_ARCH_ESP32
float value_v = analogRead(this->pin_) / 4095.0f; // NOLINT
int raw = adc1_get_raw(gpio_to_adc1(pin_));
float value_v = raw / 4095.0f;
#if CONFIG_IDF_TARGET_ESP32
switch (this->attenuation_) {
case ADC_0db:
case ADC_ATTEN_DB_0:
value_v *= 1.1;
break;
case ADC_2_5db:
case ADC_ATTEN_DB_2_5:
value_v *= 1.5;
break;
case ADC_6db:
case ADC_ATTEN_DB_6:
value_v *= 2.2;
break;
case ADC_11db:
case ADC_ATTEN_DB_11:
value_v *= 3.9;
break;
default: // This is to satisfy the unused ADC_ATTEN_MAX
break;
}
#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2
switch (this->attenuation_) {
case ADC_ATTEN_DB_0:
value_v *= 0.84;
break;
case ADC_ATTEN_DB_2_5:
value_v *= 1.13;
break;
case ADC_ATTEN_DB_6:
value_v *= 1.56;
break;
case ADC_ATTEN_DB_11:
value_v *= 3.0;
break;
default: // This is to satisfy the unused ADC_ATTEN_MAX
break;
}
#endif
return value_v;
#endif

View file

@ -6,6 +6,10 @@
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/voltage_sampler/voltage_sampler.h"
#ifdef ARDUINO_ARCH_ESP32
#include "driver/adc.h"
#endif
namespace esphome {
namespace adc {
@ -13,7 +17,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
public:
#ifdef ARDUINO_ARCH_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);
#endif
/// Update adc values.
@ -34,7 +38,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
uint8_t pin_;
#ifdef ARDUINO_ARCH_ESP32
adc_attenuation_t attenuation_{ADC_0db};
adc_atten_t attenuation_{ADC_ATTEN_DB_0};
#endif
};

View file

@ -7,7 +7,7 @@ from esphome.const import (
CONF_ID,
CONF_PIN,
DEVICE_CLASS_VOLTAGE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
)
@ -15,10 +15,10 @@ from esphome.const import (
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,
}
@ -35,7 +35,12 @@ ADCSensor = adc_ns.class_(
)
CONFIG_SCHEMA = (
sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE)
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),
@ -49,10 +54,10 @@ CONFIG_SCHEMA = (
)
def to_code(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")

View file

@ -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

View file

@ -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<light::AddressableLight *>(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<int(int, int)> &&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<Color> addressable_light_buffer_;
optional<std::string> last_effect_;
optional<std::function<int(int, int)>> pixel_mapper_f_;
};
} // namespace addressable_light
} // namespace esphome

View file

@ -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_))

View file

@ -4,7 +4,7 @@
namespace esphome {
namespace ade7953 {
static const char *TAG = "ade7953";
static const char *const TAG = "ade7953";
void ADE7953::dump_config() {
ESP_LOGCONFIG(TAG, "ADE7953:");
@ -21,8 +21,8 @@ void ADE7953::dump_config() {
}
#define ADE_PUBLISH_(name, factor) \
if (name && this->name##_sensor_) { \
float value = *name / factor; \
if ((name) && this->name##_sensor_) { \
float value = *(name) / (factor); \
this->name##_sensor_->publish_state(value); \
}
#define ADE_PUBLISH(name, factor) ADE_PUBLISH_(name, factor)

View file

@ -8,7 +8,7 @@ from esphome.const import (
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_POWER,
DEVICE_CLASS_VOLTAGE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
@ -31,19 +31,34 @@ CONFIG_SCHEMA = (
cv.GenerateID(): cv.declare_id(ADE7953),
cv.Optional(CONF_IRQ_PIN): pins.input_pin,
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
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_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT
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_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT
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_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER
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_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER
unit_of_measurement=UNIT_WATT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
@ -52,10 +67,10 @@ CONFIG_SCHEMA = (
)
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_IRQ_PIN in config:
cg.add(var.set_irq_pin(config[CONF_IRQ_PIN]))
@ -70,5 +85,5 @@ def to_code(config):
if key not in config:
continue
conf = config[key]
sens = yield sensor.new_sensor(conf)
sens = await sensor.new_sensor(conf)
cg.add(getattr(var, f"set_{key}_sensor")(sens))

View file

@ -23,9 +23,9 @@ CONFIG_SCHEMA = (
)
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]))

View file

@ -4,7 +4,7 @@
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 +64,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 +102,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();
}
}

View file

@ -5,7 +5,7 @@ from esphome.const import (
CONF_GAIN,
CONF_MULTIPLEXER,
DEVICE_CLASS_VOLTAGE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
CONF_ID,
)
@ -51,7 +51,12 @@ ADS1115Sensor = ads1115_ns.class_(
CONF_ADS1115_ID = "ads1115_id"
CONFIG_SCHEMA = (
sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE)
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),
@ -64,11 +69,11 @@ CONFIG_SCHEMA = (
)
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]))

View file

@ -10,7 +10,7 @@
//
// 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"
@ -18,12 +18,12 @@
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...");
@ -58,8 +58,9 @@ void AHT10Component::update() {
uint8_t delay = 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());
for (int i = 0; i < AHT10_ATTEMPTS; ++i) {
ESP_LOGVV(TAG, "Attempt %u at %6ld", i, millis());
delay_microseconds_accurate(4);
if (!this->read_bytes(0, data, 6, delay)) {
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
} else if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy

View file

@ -7,7 +7,7 @@ from esphome.const import (
CONF_TEMPERATURE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
)
@ -22,10 +22,16 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(): cv.declare_id(AHT10Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS, ICON_EMPTY, 2, DEVICE_CLASS_TEMPERATURE
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_PERCENT, ICON_EMPTY, 2, DEVICE_CLASS_HUMIDITY
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
@ -34,15 +40,15 @@ CONFIG_SCHEMA = (
)
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))

View file

@ -9,7 +9,7 @@
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) {

View file

@ -7,8 +7,8 @@ from esphome.const import (
CONF_TEMPERATURE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
ICON_EMPTY,
UNIT_PERCENT,
)
@ -24,10 +24,16 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(): cv.declare_id(AM2320Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
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_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
@ -36,15 +42,15 @@ CONFIG_SCHEMA = (
)
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))

View file

@ -34,7 +34,7 @@ CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA)
CODEOWNERS = ["@syndlex"]
def to_code(config):
async def to_code(config):
from PIL import Image
path = CORE.relative_config_path(config[CONF_FILE])

View file

@ -0,0 +1,141 @@
#include "anova.h"
#include "esphome/core/log.h"
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace anova {
static const char *TAG = "anova";
using namespace esphome::climate;
void Anova::dump_config() { LOG_CLIMATE("", "Anova BLE Cooker", this); }
void Anova::setup() {
this->codec_ = new AnovaCodec();
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;
}
this->publish_state();
if (this->current_request_ > 0) {
AnovaPacket *pkt = nullptr;
switch (this->current_request_++) {
case 1:
pkt = this->codec_->get_read_target_temp_request();
break;
case 2:
pkt = this->codec_->get_read_current_temp_request();
break;
default:
this->current_request_ = 0;
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::update() {
if (this->node_state != espbt::ClientState::Established)
return;
if (this->current_request_ == 0) {
auto pkt = this->codec_->get_read_device_status_request();
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

View file

@ -0,0 +1,50 @@
#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 ARDUINO_ARCH_ESP32
#include <esp_gattc_api.h>
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() {
auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(true);
traits.set_supports_heat_mode(true);
traits.set_visual_min_temperature(25.0);
traits.set_visual_max_temperature(100.0);
traits.set_visual_temperature_step(0.1);
return traits;
}
protected:
AnovaCodec *codec_;
void control(const climate::ClimateCall &call) override;
uint16_t char_handle_;
uint8_t current_request_;
};
} // namespace anova
} // namespace esphome
#endif

View file

@ -0,0 +1,119 @@
#include "anova_base.h"
namespace esphome {
namespace anova {
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;
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) {
memset(this->buf_, 0, 32);
strncpy(this->buf_, (char *) data, length);
ESP_LOGV("anova", "Received: %s\n", this->buf_);
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(this->buf_, "stopped", 7)) {
this->has_running_ = true;
this->running_ = false;
}
if (!strncmp(this->buf_, "running", 7)) {
this->has_running_ = true;
this->running_ = true;
}
break;
}
case START: {
if (!strncmp(this->buf_, "start", 5)) {
this->has_running_ = true;
this->running_ = true;
}
break;
}
case STOP: {
if (!strncmp(this->buf_, "stop", 4)) {
this->has_running_ = true;
this->running_ = false;
}
break;
}
case READ_TARGET_TEMPERATURE: {
this->target_temp_ = strtof(this->buf_, nullptr);
this->has_target_temp_ = true;
break;
}
case SET_TARGET_TEMPERATURE: {
this->target_temp_ = strtof(this->buf_, nullptr);
this->has_target_temp_ = true;
break;
}
case READ_CURRENT_TEMPERATURE: {
this->current_temp_ = strtof(this->buf_, nullptr);
this->has_current_temp_ = true;
break;
}
default:
break;
}
}
} // namespace anova
} // namespace esphome

View file

@ -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_;
char buf_[32];
CurrentQuery current_query_;
};
} // namespace anova
} // namespace esphome

View file

@ -0,0 +1,25 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import climate, ble_client
from esphome.const import CONF_ID
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)})
.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)

View file

@ -23,7 +23,7 @@ CONFIG_SCHEMA = (
)
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)

View file

@ -4,10 +4,10 @@
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; \
}

View file

@ -24,8 +24,8 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
)
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))

View file

@ -1,7 +1,12 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import CONF_TYPE, DEVICE_CLASS_EMPTY, 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"]
@ -15,7 +20,10 @@ TYPES = {
}
CONFIG_SCHEMA = sensor.sensor_schema(
UNIT_PERCENT, ICON_LIGHTBULB, 1, DEVICE_CLASS_EMPTY
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),
@ -24,8 +32,8 @@ CONFIG_SCHEMA = sensor.sensor_schema(
)
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))

View file

@ -68,9 +68,9 @@ CONFIG_SCHEMA = cv.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]))
@ -90,7 +90,7 @@ def to_code(config):
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")
cg.add_global(api_ns.using)
@ -116,21 +116,21 @@ HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema(
HomeAssistantServiceCallAction,
HOMEASSISTANT_SERVICE_ACTION_SCHEMA,
)
def homeassistant_service_to_code(config, action_id, template_arg, args):
serv = yield cg.get_variable(config[CONF_ID])
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):
@ -159,21 +159,21 @@ HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema(
HomeAssistantServiceCallAction,
HOMEASSISTANT_EVENT_ACTION_SCHEMA,
)
def homeassistant_event_to_code(config, action_id, template_arg, args):
serv = yield cg.get_variable(config[CONF_ID])
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
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value(
@ -190,15 +190,15 @@ HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value(
HomeAssistantServiceCallAction,
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
)
def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
serv = yield cg.get_variable(config[CONF_ID])
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 = yield cg.templatable(config[CONF_TAG], args, cg.std_string)
templ = await cg.templatable(config[CONF_TAG], args, cg.std_string)
cg.add(var.add_data("tag_id", 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)
async def api_connected_to_code(config, condition_id, template_arg, args):
return cg.new_Pvariable(condition_id, template_arg)

View file

@ -38,6 +38,8 @@ 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) {}
}
@ -176,6 +178,10 @@ message DeviceInfoResponse {
string model = 6;
bool has_deep_sleep = 7;
// The esphome project details if set
string project_name = 8;
string project_version = 9;
}
message ListEntitiesRequest {
@ -303,6 +309,7 @@ message ListEntitiesFanResponse {
bool supports_oscillation = 5;
bool supports_speed = 6;
bool supports_direction = 7;
int32 supported_speed_count = 8;
}
enum FanSpeed {
FAN_SPEED_LOW = 0;
@ -322,8 +329,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;
@ -334,15 +342,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;
@ -353,10 +375,12 @@ 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;
@ -370,11 +394,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 {
@ -388,6 +416,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;
@ -396,6 +428,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;
@ -405,6 +441,17 @@ message LightCommandRequest {
}
// ==================== SENSOR ====================
enum SensorStateClass {
STATE_CLASS_NONE = 0;
STATE_CLASS_MEASUREMENT = 1;
}
enum SensorLastResetType {
LAST_RESET_NONE = 0;
LAST_RESET_NEVER = 1;
LAST_RESET_AUTO = 2;
}
message ListEntitiesSensorResponse {
option (id) = 16;
option (source) = SOURCE_SERVER;
@ -420,6 +467,8 @@ message ListEntitiesSensorResponse {
int32 accuracy_decimals = 7;
bool force_update = 8;
string device_class = 9;
SensorStateClass state_class = 10;
SensorLastResetType last_reset_type = 11;
}
message SensorStateResponse {
option (id) = 25;
@ -557,6 +606,7 @@ message SubscribeHomeAssistantStateResponse {
option (id) = 39;
option (source) = SOURCE_SERVER;
string entity_id = 1;
string attribute = 2;
}
message HomeAssistantStateResponse {
@ -566,6 +616,7 @@ message HomeAssistantStateResponse {
string entity_id = 1;
string state = 2;
string attribute = 3;
}
// ==================== IMPORT TIME ====================
@ -660,11 +711,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;
@ -692,6 +744,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;
@ -708,10 +770,15 @@ 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;
}
message ClimateStateResponse {
option (id) = 47;
@ -725,10 +792,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;
@ -745,10 +816,91 @@ 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 ====================
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;
}
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;
}
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;
}

View file

@ -9,11 +9,14 @@
#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) {
@ -246,8 +249,10 @@ 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<enums::FanSpeed>(fan->speed);
if (traits.supports_speed()) {
resp.speed_level = fan->speed;
resp.speed = static_cast<enums::FanSpeed>(fan::speed_level_to_enum(fan->speed, traits.supported_speed_count()));
}
if (traits.supports_direction())
resp.direction = static_cast<enums::FanDirection>(fan->direction);
return this->send_fan_state_response(resp);
@ -262,6 +267,7 @@ bool APIConnection::send_fan_info(fan::FanState *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();
return this->send_list_entities_fan_response(msg);
}
void APIConnection::fan_command(const FanCommandRequest &msg) {
@ -269,13 +275,19 @@ 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<fan::FanSpeed>(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<fan::FanSpeed>(msg.speed), traits.supported_speed_count()));
}
if (msg.has_direction)
call.set_direction(static_cast<fan::FanDirection>(msg.direction));
call.perform();
@ -289,21 +301,28 @@ 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.color_mode = static_cast<enums::ColorMode>(color_mode);
if (color_mode & light::ColorCapability::BRIGHTNESS)
resp.brightness = values.get_brightness();
if (traits.get_supports_rgb()) {
if (color_mode & light::ColorCapability::RGB) {
resp.color_brightness = values.get_color_brightness();
resp.red = values.get_red();
resp.green = values.get_green();
resp.blue = values.get_blue();
}
if (traits.get_supports_rgb_white_value())
if (color_mode & light::ColorCapability::WHITE)
resp.white = values.get_white();
if (traits.get_supports_color_temperature())
if (color_mode & light::ColorCapability::COLOR_TEMPERATURE)
resp.color_temperature = values.get_color_temperature();
if (color_mode & light::ColorCapability::COLD_WARM_WHITE) {
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);
@ -315,11 +334,18 @@ 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) {
for (auto mode : traits.get_supported_color_modes())
msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(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();
}
@ -340,6 +366,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<light::ColorMode>(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);
@ -349,6 +379,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)
@ -383,6 +417,9 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
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<enums::SensorStateClass>(sensor->state_class);
msg.last_reset_type = static_cast<enums::SensorLastResetType>(sensor->last_reset_type);
return this->send_list_entities_sensor_response(msg);
}
#endif
@ -461,10 +498,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<enums::ClimateFanMode>(climate->fan_mode);
if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
resp.fan_mode = static_cast<enums::ClimateFanMode>(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<enums::ClimatePreset>(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<enums::ClimateSwingMode>(climate->swing_mode);
return this->send_climate_state_response(resp);
@ -478,27 +521,26 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
msg.unique_id = get_default_unique_id("climate", climate);
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<enums::ClimateMode>(mode));
}
for (auto mode : traits.get_supported_modes())
msg.supported_modes.push_back(static_cast<enums::ClimateMode>(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<enums::ClimateFanMode>(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<enums::ClimateSwingMode>(swing_mode));
}
for (auto fan_mode : traits.get_supported_fan_modes())
msg.supported_fan_modes.push_back(static_cast<enums::ClimateFanMode>(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<enums::ClimatePreset>(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<enums::ClimateSwingMode>(swing_mode));
return this->send_list_entities_climate_response(msg);
}
void APIConnection::climate_command(const ClimateCommandRequest &msg) {
@ -515,16 +557,93 @@ 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<climate::ClimateFanMode>(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<climate::ClimatePreset>(msg.preset));
if (msg.has_custom_preset)
call.set_preset(msg.custom_preset);
if (msg.has_swing_mode)
call.set_swing_mode(static_cast<climate::ClimateSwingMode>(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->traits.get_icon();
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->traits.get_icon();
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_ESP32_CAMERA
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
if (!this->state_subscription_)
@ -590,7 +709,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
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;
@ -625,13 +744,18 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
#endif
#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
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;
@ -648,6 +772,7 @@ void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistant
for (auto &it : this->parent_->get_state_subs()) {
SubscribeHomeAssistantStateResponse resp;
resp.entity_id = it.entity_id;
resp.attribute = it.attribute.value();
if (!this->send_subscribe_home_assistant_state_response(resp)) {
this->on_fatal_error();
return;

View file

@ -62,6 +62,16 @@ 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
bool send_log_message(int level, const char *tag, const char *line);
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {

File diff suppressed because it is too large Load diff

View file

@ -32,6 +32,27 @@ enum FanDirection : uint32_t {
FAN_DIRECTION_FORWARD = 0,
FAN_DIRECTION_REVERSE = 1,
};
enum ColorMode : uint32_t {
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,
};
enum SensorStateClass : uint32_t {
STATE_CLASS_NONE = 0,
STATE_CLASS_MEASUREMENT = 1,
};
enum SensorLastResetType : uint32_t {
LAST_RESET_NONE = 0,
LAST_RESET_NEVER = 1,
LAST_RESET_AUTO = 2,
};
enum LogLevel : uint32_t {
LOG_LEVEL_NONE = 0,
LOG_LEVEL_ERROR = 1,
@ -53,11 +74,12 @@ enum ServiceArgType : uint32_t {
};
enum ClimateMode : uint32_t {
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 : uint32_t {
CLIMATE_FAN_ON = 0,
@ -84,12 +106,22 @@ enum ClimateAction : uint32_t {
CLIMATE_ACTION_DRYING = 5,
CLIMATE_ACTION_FAN = 6,
};
enum ClimatePreset : uint32_t {
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,
};
} // namespace enums
class HelloRequest : public ProtoMessage {
public:
std::string client_info{}; // NOLINT
std::string client_info{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -98,9 +130,9 @@ class HelloRequest : public ProtoMessage {
};
class HelloResponse : public ProtoMessage {
public:
uint32_t api_version_major{0}; // NOLINT
uint32_t api_version_minor{0}; // NOLINT
std::string server_info{}; // NOLINT
uint32_t api_version_major{0};
uint32_t api_version_minor{0};
std::string server_info{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -110,7 +142,7 @@ class HelloResponse : public ProtoMessage {
};
class ConnectRequest : public ProtoMessage {
public:
std::string password{}; // NOLINT
std::string password{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -119,7 +151,7 @@ class ConnectRequest : public ProtoMessage {
};
class ConnectResponse : public ProtoMessage {
public:
bool invalid_password{false}; // NOLINT
bool invalid_password{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -163,13 +195,15 @@ class DeviceInfoRequest : public ProtoMessage {
};
class DeviceInfoResponse : public ProtoMessage {
public:
bool uses_password{false}; // NOLINT
std::string name{}; // NOLINT
std::string mac_address{}; // NOLINT
std::string esphome_version{}; // NOLINT
std::string compilation_time{}; // NOLINT
std::string model{}; // NOLINT
bool has_deep_sleep{false}; // NOLINT
bool uses_password{false};
std::string name{};
std::string mac_address{};
std::string esphome_version{};
std::string compilation_time{};
std::string model{};
bool has_deep_sleep{false};
std::string project_name{};
std::string project_version{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -200,12 +234,12 @@ class SubscribeStatesRequest : public ProtoMessage {
};
class ListEntitiesBinarySensorResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
std::string device_class{}; // NOLINT
bool is_status_binary_sensor{false}; // NOLINT
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string device_class{};
bool is_status_binary_sensor{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -216,9 +250,9 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage {
};
class BinarySensorStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool state{false}; // NOLINT
bool missing_state{false}; // NOLINT
uint32_t key{0};
bool state{false};
bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -228,14 +262,14 @@ class BinarySensorStateResponse : public ProtoMessage {
};
class ListEntitiesCoverResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
bool assumed_state{false}; // NOLINT
bool supports_position{false}; // NOLINT
bool supports_tilt{false}; // NOLINT
std::string device_class{}; // NOLINT
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
bool assumed_state{false};
bool supports_position{false};
bool supports_tilt{false};
std::string device_class{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -246,11 +280,11 @@ class ListEntitiesCoverResponse : public ProtoMessage {
};
class CoverStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
enums::LegacyCoverState legacy_state{}; // NOLINT
float position{0.0f}; // NOLINT
float tilt{0.0f}; // NOLINT
enums::CoverOperation current_operation{}; // NOLINT
uint32_t key{0};
enums::LegacyCoverState legacy_state{};
float position{0.0f};
float tilt{0.0f};
enums::CoverOperation current_operation{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -260,14 +294,14 @@ class CoverStateResponse : public ProtoMessage {
};
class CoverCommandRequest : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool has_legacy_command{false}; // NOLINT
enums::LegacyCoverCommand legacy_command{}; // NOLINT
bool has_position{false}; // NOLINT
float position{0.0f}; // NOLINT
bool has_tilt{false}; // NOLINT
float tilt{0.0f}; // NOLINT
bool stop{false}; // NOLINT
uint32_t key{0};
bool has_legacy_command{false};
enums::LegacyCoverCommand legacy_command{};
bool has_position{false};
float position{0.0f};
bool has_tilt{false};
float tilt{0.0f};
bool stop{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -277,13 +311,14 @@ class CoverCommandRequest : public ProtoMessage {
};
class ListEntitiesFanResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
bool supports_oscillation{false}; // NOLINT
bool supports_speed{false}; // NOLINT
bool supports_direction{false}; // NOLINT
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
bool supports_oscillation{false};
bool supports_speed{false};
bool supports_direction{false};
int32_t supported_speed_count{0};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -294,11 +329,12 @@ class ListEntitiesFanResponse : public ProtoMessage {
};
class FanStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool state{false}; // NOLINT
bool oscillating{false}; // NOLINT
enums::FanSpeed speed{}; // NOLINT
enums::FanDirection direction{}; // NOLINT
uint32_t key{0};
bool state{false};
bool oscillating{false};
enums::FanSpeed speed{};
enums::FanDirection direction{};
int32_t speed_level{0};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -308,15 +344,17 @@ class FanStateResponse : public ProtoMessage {
};
class FanCommandRequest : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool has_state{false}; // NOLINT
bool state{false}; // NOLINT
bool has_speed{false}; // NOLINT
enums::FanSpeed speed{}; // NOLINT
bool has_oscillating{false}; // NOLINT
bool oscillating{false}; // NOLINT
bool has_direction{false}; // NOLINT
enums::FanDirection direction{}; // NOLINT
uint32_t key{0};
bool has_state{false};
bool state{false};
bool has_speed{false};
enums::FanSpeed speed{};
bool has_oscillating{false};
bool oscillating{false};
bool has_direction{false};
enums::FanDirection direction{};
bool has_speed_level{false};
int32_t speed_level{0};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -326,17 +364,18 @@ class FanCommandRequest : public ProtoMessage {
};
class ListEntitiesLightResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
bool supports_brightness{false}; // NOLINT
bool supports_rgb{false}; // NOLINT
bool supports_white_value{false}; // NOLINT
bool supports_color_temperature{false}; // NOLINT
float min_mireds{0.0f}; // NOLINT
float max_mireds{0.0f}; // NOLINT
std::vector<std::string> effects{}; // NOLINT
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::vector<enums::ColorMode> supported_color_modes{};
bool legacy_supports_brightness{false};
bool legacy_supports_rgb{false};
bool legacy_supports_white_value{false};
bool legacy_supports_color_temperature{false};
float min_mireds{0.0f};
float max_mireds{0.0f};
std::vector<std::string> effects{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -347,15 +386,19 @@ class ListEntitiesLightResponse : public ProtoMessage {
};
class LightStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool state{false}; // NOLINT
float brightness{0.0f}; // NOLINT
float red{0.0f}; // NOLINT
float green{0.0f}; // NOLINT
float blue{0.0f}; // NOLINT
float white{0.0f}; // NOLINT
float color_temperature{0.0f}; // NOLINT
std::string effect{}; // NOLINT
uint32_t key{0};
bool state{false};
float brightness{0.0f};
enums::ColorMode color_mode{};
float color_brightness{0.0f};
float red{0.0f};
float green{0.0f};
float blue{0.0f};
float white{0.0f};
float color_temperature{0.0f};
float cold_white{0.0f};
float warm_white{0.0f};
std::string effect{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -366,25 +409,33 @@ class LightStateResponse : public ProtoMessage {
};
class LightCommandRequest : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool has_state{false}; // NOLINT
bool state{false}; // NOLINT
bool has_brightness{false}; // NOLINT
float brightness{0.0f}; // NOLINT
bool has_rgb{false}; // NOLINT
float red{0.0f}; // NOLINT
float green{0.0f}; // NOLINT
float blue{0.0f}; // NOLINT
bool has_white{false}; // NOLINT
float white{0.0f}; // NOLINT
bool has_color_temperature{false}; // NOLINT
float color_temperature{0.0f}; // NOLINT
bool has_transition_length{false}; // NOLINT
uint32_t transition_length{0}; // NOLINT
bool has_flash_length{false}; // NOLINT
uint32_t flash_length{0}; // NOLINT
bool has_effect{false}; // NOLINT
std::string effect{}; // NOLINT
uint32_t key{0};
bool has_state{false};
bool state{false};
bool has_brightness{false};
float brightness{0.0f};
bool has_color_mode{false};
enums::ColorMode color_mode{};
bool has_color_brightness{false};
float color_brightness{0.0f};
bool has_rgb{false};
float red{0.0f};
float green{0.0f};
float blue{0.0f};
bool has_white{false};
float white{0.0f};
bool has_color_temperature{false};
float color_temperature{0.0f};
bool has_cold_white{false};
float cold_white{0.0f};
bool has_warm_white{false};
float warm_white{0.0f};
bool has_transition_length{false};
uint32_t transition_length{0};
bool has_flash_length{false};
uint32_t flash_length{0};
bool has_effect{false};
std::string effect{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -395,15 +446,17 @@ class LightCommandRequest : public ProtoMessage {
};
class ListEntitiesSensorResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
std::string icon{}; // NOLINT
std::string unit_of_measurement{}; // NOLINT
std::string device_class{}; // NOLINT
int32_t accuracy_decimals{0}; // NOLINT
bool force_update{false}; // NOLINT
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
std::string unit_of_measurement{};
int32_t accuracy_decimals{0};
bool force_update{false};
std::string device_class{};
enums::SensorStateClass state_class{};
enums::SensorLastResetType last_reset_type{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -414,9 +467,9 @@ class ListEntitiesSensorResponse : public ProtoMessage {
};
class SensorStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
float state{0.0f}; // NOLINT
bool missing_state{false}; // NOLINT
uint32_t key{0};
float state{0.0f};
bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -426,12 +479,12 @@ class SensorStateResponse : public ProtoMessage {
};
class ListEntitiesSwitchResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
std::string icon{}; // NOLINT
bool assumed_state{false}; // NOLINT
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
bool assumed_state{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -442,8 +495,8 @@ class ListEntitiesSwitchResponse : public ProtoMessage {
};
class SwitchStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool state{false}; // NOLINT
uint32_t key{0};
bool state{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -453,8 +506,8 @@ class SwitchStateResponse : public ProtoMessage {
};
class SwitchCommandRequest : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool state{false}; // NOLINT
uint32_t key{0};
bool state{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -464,11 +517,11 @@ class SwitchCommandRequest : public ProtoMessage {
};
class ListEntitiesTextSensorResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
std::string icon{}; // NOLINT
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -478,9 +531,9 @@ class ListEntitiesTextSensorResponse : public ProtoMessage {
};
class TextSensorStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
std::string state{}; // NOLINT
bool missing_state{false}; // NOLINT
uint32_t key{0};
std::string state{};
bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -491,8 +544,8 @@ class TextSensorStateResponse : public ProtoMessage {
};
class SubscribeLogsRequest : public ProtoMessage {
public:
enums::LogLevel level{}; // NOLINT
bool dump_config{false}; // NOLINT
enums::LogLevel level{};
bool dump_config{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -501,10 +554,10 @@ class SubscribeLogsRequest : public ProtoMessage {
};
class SubscribeLogsResponse : public ProtoMessage {
public:
enums::LogLevel level{}; // NOLINT
std::string tag{}; // NOLINT
std::string message{}; // NOLINT
bool send_failed{false}; // NOLINT
enums::LogLevel level{};
std::string tag{};
std::string message{};
bool send_failed{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -521,8 +574,8 @@ class SubscribeHomeassistantServicesRequest : public ProtoMessage {
};
class HomeassistantServiceMap : public ProtoMessage {
public:
std::string key{}; // NOLINT
std::string value{}; // NOLINT
std::string key{};
std::string value{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -531,11 +584,11 @@ class HomeassistantServiceMap : public ProtoMessage {
};
class HomeassistantServiceResponse : public ProtoMessage {
public:
std::string service{}; // NOLINT
std::vector<HomeassistantServiceMap> data{}; // NOLINT
std::vector<HomeassistantServiceMap> data_template{}; // NOLINT
std::vector<HomeassistantServiceMap> variables{}; // NOLINT
bool is_event{false}; // NOLINT
std::string service{};
std::vector<HomeassistantServiceMap> data{};
std::vector<HomeassistantServiceMap> data_template{};
std::vector<HomeassistantServiceMap> variables{};
bool is_event{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -552,7 +605,8 @@ class SubscribeHomeAssistantStatesRequest : public ProtoMessage {
};
class SubscribeHomeAssistantStateResponse : public ProtoMessage {
public:
std::string entity_id{}; // NOLINT
std::string entity_id{};
std::string attribute{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -561,8 +615,9 @@ class SubscribeHomeAssistantStateResponse : public ProtoMessage {
};
class HomeAssistantStateResponse : public ProtoMessage {
public:
std::string entity_id{}; // NOLINT
std::string state{}; // NOLINT
std::string entity_id{};
std::string state{};
std::string attribute{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -578,7 +633,7 @@ class GetTimeRequest : public ProtoMessage {
};
class GetTimeResponse : public ProtoMessage {
public:
uint32_t epoch_seconds{0}; // NOLINT
uint32_t epoch_seconds{0};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -587,8 +642,8 @@ class GetTimeResponse : public ProtoMessage {
};
class ListEntitiesServicesArgument : public ProtoMessage {
public:
std::string name{}; // NOLINT
enums::ServiceArgType type{}; // NOLINT
std::string name{};
enums::ServiceArgType type{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -598,9 +653,9 @@ class ListEntitiesServicesArgument : public ProtoMessage {
};
class ListEntitiesServicesResponse : public ProtoMessage {
public:
std::string name{}; // NOLINT
uint32_t key{0}; // NOLINT
std::vector<ListEntitiesServicesArgument> args{}; // NOLINT
std::string name{};
uint32_t key{0};
std::vector<ListEntitiesServicesArgument> args{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -610,15 +665,15 @@ class ListEntitiesServicesResponse : public ProtoMessage {
};
class ExecuteServiceArgument : public ProtoMessage {
public:
bool bool_{false}; // NOLINT
int32_t legacy_int{0}; // NOLINT
float float_{0.0f}; // NOLINT
std::string string_{}; // NOLINT
int32_t int_{0}; // NOLINT
std::vector<bool> bool_array{}; // NOLINT
std::vector<int32_t> int_array{}; // NOLINT
std::vector<float> float_array{}; // NOLINT
std::vector<std::string> string_array{}; // NOLINT
bool bool_{false};
int32_t legacy_int{0};
float float_{0.0f};
std::string string_{};
int32_t int_{0};
std::vector<bool> bool_array{};
std::vector<int32_t> int_array{};
std::vector<float> float_array{};
std::vector<std::string> string_array{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -629,8 +684,8 @@ class ExecuteServiceArgument : public ProtoMessage {
};
class ExecuteServiceRequest : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
std::vector<ExecuteServiceArgument> args{}; // NOLINT
uint32_t key{0};
std::vector<ExecuteServiceArgument> args{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -640,10 +695,10 @@ class ExecuteServiceRequest : public ProtoMessage {
};
class ListEntitiesCameraResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -653,9 +708,9 @@ class ListEntitiesCameraResponse : public ProtoMessage {
};
class CameraImageResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
std::string data{}; // NOLINT
bool done{false}; // NOLINT
uint32_t key{0};
std::string data{};
bool done{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -666,8 +721,8 @@ class CameraImageResponse : public ProtoMessage {
};
class CameraImageRequest : public ProtoMessage {
public:
bool single{false}; // NOLINT
bool stream{false}; // NOLINT
bool single{false};
bool stream{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -676,20 +731,23 @@ class CameraImageRequest : public ProtoMessage {
};
class ListEntitiesClimateResponse : public ProtoMessage {
public:
std::string object_id{}; // NOLINT
uint32_t key{0}; // NOLINT
std::string name{}; // NOLINT
std::string unique_id{}; // NOLINT
bool supports_current_temperature{false}; // NOLINT
bool supports_two_point_target_temperature{false}; // NOLINT
std::vector<enums::ClimateMode> supported_modes{}; // NOLINT
float visual_min_temperature{0.0f}; // NOLINT
float visual_max_temperature{0.0f}; // NOLINT
float visual_temperature_step{0.0f}; // NOLINT
bool supports_away{false}; // NOLINT
bool supports_action{false}; // NOLINT
std::vector<enums::ClimateFanMode> supported_fan_modes{}; // NOLINT
std::vector<enums::ClimateSwingMode> supported_swing_modes{}; // NOLINT
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
bool supports_current_temperature{false};
bool supports_two_point_target_temperature{false};
std::vector<enums::ClimateMode> supported_modes{};
float visual_min_temperature{0.0f};
float visual_max_temperature{0.0f};
float visual_temperature_step{0.0f};
bool legacy_supports_away{false};
bool supports_action{false};
std::vector<enums::ClimateFanMode> supported_fan_modes{};
std::vector<enums::ClimateSwingMode> supported_swing_modes{};
std::vector<std::string> supported_custom_fan_modes{};
std::vector<enums::ClimatePreset> supported_presets{};
std::vector<std::string> supported_custom_presets{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -700,40 +758,80 @@ class ListEntitiesClimateResponse : public ProtoMessage {
};
class ClimateStateResponse : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
enums::ClimateMode mode{}; // NOLINT
float current_temperature{0.0f}; // NOLINT
float target_temperature{0.0f}; // NOLINT
float target_temperature_low{0.0f}; // NOLINT
float target_temperature_high{0.0f}; // NOLINT
bool away{false}; // NOLINT
enums::ClimateAction action{}; // NOLINT
enums::ClimateFanMode fan_mode{}; // NOLINT
enums::ClimateSwingMode swing_mode{}; // NOLINT
uint32_t key{0};
enums::ClimateMode mode{};
float current_temperature{0.0f};
float target_temperature{0.0f};
float target_temperature_low{0.0f};
float target_temperature_high{0.0f};
bool legacy_away{false};
enums::ClimateAction action{};
enums::ClimateFanMode fan_mode{};
enums::ClimateSwingMode swing_mode{};
std::string custom_fan_mode{};
enums::ClimatePreset preset{};
std::string custom_preset{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ClimateCommandRequest : public ProtoMessage {
public:
uint32_t key{0}; // NOLINT
bool has_mode{false}; // NOLINT
enums::ClimateMode mode{}; // NOLINT
bool has_target_temperature{false}; // NOLINT
float target_temperature{0.0f}; // NOLINT
bool has_target_temperature_low{false}; // NOLINT
float target_temperature_low{0.0f}; // NOLINT
bool has_target_temperature_high{false}; // NOLINT
float target_temperature_high{0.0f}; // NOLINT
bool has_away{false}; // NOLINT
bool away{false}; // NOLINT
bool has_fan_mode{false}; // NOLINT
enums::ClimateFanMode fan_mode{}; // NOLINT
bool has_swing_mode{false}; // NOLINT
enums::ClimateSwingMode swing_mode{}; // NOLINT
uint32_t key{0};
bool has_mode{false};
enums::ClimateMode mode{};
bool has_target_temperature{false};
float target_temperature{0.0f};
bool has_target_temperature_low{false};
float target_temperature_low{0.0f};
bool has_target_temperature_high{false};
float target_temperature_high{0.0f};
bool has_legacy_away{false};
bool legacy_away{false};
bool has_fan_mode{false};
enums::ClimateFanMode fan_mode{};
bool has_swing_mode{false};
enums::ClimateSwingMode swing_mode{};
bool has_custom_fan_mode{false};
std::string custom_fan_mode{};
bool has_preset{false};
enums::ClimatePreset preset{};
bool has_custom_preset{false};
std::string custom_preset{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ListEntitiesNumberResponse : public ProtoMessage {
public:
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
float min_value{0.0f};
float max_value{0.0f};
float step{0.0f};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class NumberStateResponse : public ProtoMessage {
public:
uint32_t key{0};
float state{0.0f};
bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
@ -741,6 +839,55 @@ class ClimateCommandRequest : public ProtoMessage {
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class NumberCommandRequest : public ProtoMessage {
public:
uint32_t key{0};
float state{0.0f};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
};
class ListEntitiesSelectResponse : public ProtoMessage {
public:
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
std::vector<std::string> options{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class SelectStateResponse : public ProtoMessage {
public:
uint32_t key{0};
std::string state{};
bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class SelectCommandRequest : public ProtoMessage {
public:
uint32_t key{0};
std::string state{};
void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override;
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
} // namespace api
} // namespace esphome

View file

@ -6,7 +6,7 @@
namespace esphome {
namespace api {
static const char *TAG = "api.service";
static const char *const TAG = "api.service";
bool APIServerConnectionBase::send_hello_response(const HelloResponse &msg) {
ESP_LOGVV(TAG, "send_hello_response: %s", msg.dump().c_str());
@ -184,6 +184,34 @@ bool APIServerConnectionBase::send_climate_state_response(const ClimateStateResp
#endif
#ifdef USE_CLIMATE
#endif
#ifdef USE_NUMBER
bool APIServerConnectionBase::send_list_entities_number_response(const ListEntitiesNumberResponse &msg) {
ESP_LOGVV(TAG, "send_list_entities_number_response: %s", msg.dump().c_str());
return this->send_message_<ListEntitiesNumberResponse>(msg, 49);
}
#endif
#ifdef USE_NUMBER
bool APIServerConnectionBase::send_number_state_response(const NumberStateResponse &msg) {
ESP_LOGVV(TAG, "send_number_state_response: %s", msg.dump().c_str());
return this->send_message_<NumberStateResponse>(msg, 50);
}
#endif
#ifdef USE_NUMBER
#endif
#ifdef USE_SELECT
bool APIServerConnectionBase::send_list_entities_select_response(const ListEntitiesSelectResponse &msg) {
ESP_LOGVV(TAG, "send_list_entities_select_response: %s", msg.dump().c_str());
return this->send_message_<ListEntitiesSelectResponse>(msg, 52);
}
#endif
#ifdef USE_SELECT
bool APIServerConnectionBase::send_select_state_response(const SelectStateResponse &msg) {
ESP_LOGVV(TAG, "send_select_state_response: %s", msg.dump().c_str());
return this->send_message_<SelectStateResponse>(msg, 53);
}
#endif
#ifdef USE_SELECT
#endif
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
switch (msg_type) {
case 1: {
@ -349,6 +377,24 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_climate_command_request: %s", msg.dump().c_str());
this->on_climate_command_request(msg);
#endif
break;
}
case 51: {
#ifdef USE_NUMBER
NumberCommandRequest msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_number_command_request: %s", msg.dump().c_str());
this->on_number_command_request(msg);
#endif
break;
}
case 54: {
#ifdef USE_SELECT
SelectCommandRequest msg;
msg.decode(msg_data, msg_size);
ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str());
this->on_select_command_request(msg);
#endif
break;
}
@ -547,6 +593,32 @@ void APIServerConnection::on_climate_command_request(const ClimateCommandRequest
this->climate_command(msg);
}
#endif
#ifdef USE_NUMBER
void APIServerConnection::on_number_command_request(const NumberCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->number_command(msg);
}
#endif
#ifdef USE_SELECT
void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->select_command(msg);
}
#endif
} // namespace api
} // namespace esphome

View file

@ -111,6 +111,24 @@ class APIServerConnectionBase : public ProtoService {
#endif
#ifdef USE_CLIMATE
virtual void on_climate_command_request(const ClimateCommandRequest &value){};
#endif
#ifdef USE_NUMBER
bool send_list_entities_number_response(const ListEntitiesNumberResponse &msg);
#endif
#ifdef USE_NUMBER
bool send_number_state_response(const NumberStateResponse &msg);
#endif
#ifdef USE_NUMBER
virtual void on_number_command_request(const NumberCommandRequest &value){};
#endif
#ifdef USE_SELECT
bool send_list_entities_select_response(const ListEntitiesSelectResponse &msg);
#endif
#ifdef USE_SELECT
bool send_select_state_response(const SelectStateResponse &msg);
#endif
#ifdef USE_SELECT
virtual void on_select_command_request(const SelectCommandRequest &value){};
#endif
protected:
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
@ -147,6 +165,12 @@ class APIServerConnection : public APIServerConnectionBase {
#endif
#ifdef USE_CLIMATE
virtual void climate_command(const ClimateCommandRequest &msg) = 0;
#endif
#ifdef USE_NUMBER
virtual void number_command(const NumberCommandRequest &msg) = 0;
#endif
#ifdef USE_SELECT
virtual void select_command(const SelectCommandRequest &msg) = 0;
#endif
protected:
void on_hello_request(const HelloRequest &msg) override;
@ -179,6 +203,12 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_CLIMATE
void on_climate_command_request(const ClimateCommandRequest &msg) override;
#endif
#ifdef USE_NUMBER
void on_number_command_request(const NumberCommandRequest &msg) override;
#endif
#ifdef USE_SELECT
void on_select_command_request(const SelectCommandRequest &msg) override;
#endif
};
} // namespace api

View file

@ -15,7 +15,7 @@
namespace esphome {
namespace api {
static const char *TAG = "api";
static const char *const TAG = "api";
// APIServer
void APIServer::setup() {
@ -180,7 +180,7 @@ void APIServer::on_switch_update(switch_::Switch *obj, bool state) {
#endif
#ifdef USE_TEXT_SENSOR
void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, std::string state) {
void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) {
if (obj->is_internal())
return;
for (auto *c : this->clients_)
@ -197,9 +197,27 @@ void APIServer::on_climate_update(climate::Climate *obj) {
}
#endif
#ifdef USE_NUMBER
void APIServer::on_number_update(number::Number *obj, float state) {
if (obj->is_internal())
return;
for (auto *c : this->clients_)
c->send_number_state(obj, state);
}
#endif
#ifdef USE_SELECT
void APIServer::on_select_update(select::Select *obj, const std::string &state) {
if (obj->is_internal())
return;
for (auto *c : this->clients_)
c->send_select_state(obj, state);
}
#endif
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
void APIServer::set_port(uint16_t port) { this->port_ = port; }
APIServer *global_api_server = nullptr;
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void APIServer::set_password(const std::string &password) { this->password_ = password; }
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
@ -208,9 +226,11 @@ void APIServer::send_homeassistant_service_call(const HomeassistantServiceRespon
}
}
APIServer::APIServer() { global_api_server = this; }
void APIServer::subscribe_home_assistant_state(std::string entity_id, std::function<void(std::string)> f) {
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f) {
this->state_subs_.push_back(HomeAssistantStateSubscription{
.entity_id = std::move(entity_id),
.attribute = std::move(attribute),
.callback = std::move(f),
});
}

View file

@ -9,7 +9,6 @@
#include "util.h"
#include "list_entities.h"
#include "subscribe_state.h"
#include "homeassistant_service.h"
#include "user_services.h"
#ifdef ARDUINO_ARCH_ESP32
@ -56,10 +55,16 @@ class APIServer : public Component, public Controller {
void on_switch_update(switch_::Switch *obj, bool state) override;
#endif
#ifdef USE_TEXT_SENSOR
void on_text_sensor_update(text_sensor::TextSensor *obj, std::string state) override;
void on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) override;
#endif
#ifdef USE_CLIMATE
void on_climate_update(climate::Climate *obj) override;
#endif
#ifdef USE_NUMBER
void on_number_update(number::Number *obj, float state) override;
#endif
#ifdef USE_SELECT
void on_select_update(select::Select *obj, const std::string &state) override;
#endif
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
@ -71,10 +76,12 @@ class APIServer : public Component, public Controller {
struct HomeAssistantStateSubscription {
std::string entity_id;
optional<std::string> attribute;
std::function<void(std::string)> callback;
};
void subscribe_home_assistant_state(std::string entity_id, std::function<void(std::string)> f);
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f);
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }
@ -89,7 +96,7 @@ class APIServer : public Component, public Controller {
std::vector<UserServiceDescriptor *> user_services_;
};
extern APIServer *global_api_server;
extern APIServer *global_api_server; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> {
public:

View file

@ -76,13 +76,13 @@ class CustomAPIDevice {
global_api_server->register_user_service(service);
}
/** Subscribe to the state of an entity from Home Assistant.
/** Subscribe to the state (or attribute state) of an entity from Home Assistant.
*
* Usage:
*
* ```cpp
* void setup() override {
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "climate.kitchen", "current_temperature");
* }
*
* void on_state_changed(std::string state) {
@ -93,17 +93,19 @@ class CustomAPIDevice {
* @tparam T The class type creating the service, automatically deduced from the function pointer.
* @param callback The member function to call when the entity state changes.
* @param entity_id The entity_id to track.
* @param attribute The entity state attribute to track.
*/
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id) {
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id,
const std::string &attribute = "") {
auto f = std::bind(callback, (T *) this, std::placeholders::_1);
global_api_server->subscribe_home_assistant_state(entity_id, f);
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), f);
}
/** Subscribe to the state of an entity from Home Assistant.
/** Subscribe to the state (or attribute state) of an entity from Home Assistant.
*
* Usage:
*
*å
* ```cpp
* void setup() override {
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");
@ -117,11 +119,13 @@ class CustomAPIDevice {
* @tparam T The class type creating the service, automatically deduced from the function pointer.
* @param callback The member function to call when the entity state changes.
* @param entity_id The entity_id to track.
* @param attribute The entity state attribute to track.
*/
template<typename T>
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id) {
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id,
const std::string &attribute = "") {
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
global_api_server->subscribe_home_assistant_state(entity_id, f);
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), f);
}
/** Call a Home Assistant service from ESPHome.

View file

@ -51,5 +51,13 @@ bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) {
bool ListEntitiesIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_info(climate); }
#endif
#ifdef USE_NUMBER
bool ListEntitiesIterator::on_number(number::Number *number) { return this->client_->send_number_info(number); }
#endif
#ifdef USE_SELECT
bool ListEntitiesIterator::on_select(select::Select *select) { return this->client_->send_select_info(select); }
#endif
} // namespace api
} // namespace esphome

View file

@ -39,6 +39,12 @@ class ListEntitiesIterator : public ComponentIterator {
#endif
#ifdef USE_CLIMATE
bool on_climate(climate::Climate *climate) override;
#endif
#ifdef USE_NUMBER
bool on_number(number::Number *number) override;
#endif
#ifdef USE_SELECT
bool on_select(select::Select *select) override;
#endif
bool on_end() override;

View file

@ -5,7 +5,7 @@
namespace esphome {
namespace api {
static const char *TAG = "api.proto";
static const char *const TAG = "api.proto";
void ProtoMessage::decode(const uint8_t *buffer, size_t length) {
uint32_t i = 0;

View file

@ -37,6 +37,16 @@ bool InitialStateIterator::on_text_sensor(text_sensor::TextSensor *text_sensor)
#ifdef USE_CLIMATE
bool InitialStateIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_state(climate); }
#endif
#ifdef USE_NUMBER
bool InitialStateIterator::on_number(number::Number *number) {
return this->client_->send_number_state(number, number->state);
}
#endif
#ifdef USE_SELECT
bool InitialStateIterator::on_select(select::Select *select) {
return this->client_->send_select_state(select, select->state);
}
#endif
InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client)
: ComponentIterator(server), client_(client) {}

View file

@ -36,6 +36,12 @@ class InitialStateIterator : public ComponentIterator {
#endif
#ifdef USE_CLIMATE
bool on_climate(climate::Climate *climate) override;
#endif
#ifdef USE_NUMBER
bool on_number(number::Number *number) override;
#endif
#ifdef USE_SELECT
bool on_select(select::Select *select) override;
#endif
protected:
APIConnection *client_;

View file

@ -15,7 +15,7 @@ template<> std::string get_execute_arg_value<std::string>(const ExecuteServiceAr
template<> std::vector<bool> get_execute_arg_value<std::vector<bool>>(const ExecuteServiceArgument &arg) {
return arg.bool_array;
}
template<> std::vector<int> get_execute_arg_value<std::vector<int>>(const ExecuteServiceArgument &arg) {
template<> std::vector<int32_t> get_execute_arg_value<std::vector<int32_t>>(const ExecuteServiceArgument &arg) {
return arg.int_array;
}
template<> std::vector<float> get_execute_arg_value<std::vector<float>>(const ExecuteServiceArgument &arg) {

View file

@ -1,5 +1,7 @@
#pragma once
#include <utility>
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "api_pb2.h"
@ -20,8 +22,8 @@ template<typename T> enums::ServiceArgType to_service_arg_type();
template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
public:
UserServiceBase(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names)
: name_(name), arg_names_(arg_names) {
UserServiceBase(std::string name, const std::array<std::string, sizeof...(Ts)> &arg_names)
: name_(std::move(name)), arg_names_(arg_names) {
this->key_ = fnv1_hash(this->name_);
}

View file

@ -167,6 +167,36 @@ void ComponentIterator::advance() {
}
}
break;
#endif
#ifdef USE_NUMBER
case IteratorState::NUMBER:
if (this->at_ >= App.get_numbers().size()) {
advance_platform = true;
} else {
auto *number = App.get_numbers()[this->at_];
if (number->is_internal()) {
success = true;
break;
} else {
success = this->on_number(number);
}
}
break;
#endif
#ifdef USE_SELECT
case IteratorState::SELECT:
if (this->at_ >= App.get_selects().size()) {
advance_platform = true;
} else {
auto *select = App.get_selects()[this->at_];
if (select->is_internal()) {
success = true;
break;
} else {
success = this->on_select(select);
}
}
break;
#endif
case IteratorState::MAX:
if (this->on_end()) {

View file

@ -47,6 +47,12 @@ class ComponentIterator {
#endif
#ifdef USE_CLIMATE
virtual bool on_climate(climate::Climate *climate) = 0;
#endif
#ifdef USE_NUMBER
virtual bool on_number(number::Number *number) = 0;
#endif
#ifdef USE_SELECT
virtual bool on_select(select::Select *select) = 0;
#endif
virtual bool on_end();
@ -81,6 +87,12 @@ class ComponentIterator {
#endif
#ifdef USE_CLIMATE
CLIMATE,
#endif
#ifdef USE_NUMBER
NUMBER,
#endif
#ifdef USE_SELECT
SELECT,
#endif
MAX,
} state_{IteratorState::NONE};

View file

@ -11,7 +11,6 @@ from esphome.const import (
CONF_DIV_RATIO,
CONF_CAPACITANCE,
)
from esphome.core import coroutine
AUTO_LOAD = ["sensor", "binary_sensor"]
MULTI_CONF = True
@ -40,11 +39,10 @@ AS3935_SCHEMA = cv.Schema(
)
@coroutine
def setup_as3935(var, config):
yield cg.register_component(var, config)
async def setup_as3935(var, config):
await cg.register_component(var, config)
irq_pin = yield cg.gpio_pin_expression(config[CONF_IRQ_PIN])
irq_pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN])
cg.add(var.set_irq_pin(irq_pin))
cg.add(var.set_indoor(config[CONF_INDOOR]))
cg.add(var.set_noise_level(config[CONF_NOISE_LEVEL]))

View file

@ -4,7 +4,7 @@
namespace esphome {
namespace as3935 {
static const char *TAG = "as3935";
static const char *const TAG = "as3935";
void AS3935Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up AS3935...");

View file

@ -12,7 +12,7 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
)
def to_code(config):
hub = yield cg.get_variable(config[CONF_AS3935_ID])
var = yield binary_sensor.new_binary_sensor(config)
async def to_code(config):
hub = await cg.get_variable(config[CONF_AS3935_ID])
var = await binary_sensor.new_binary_sensor(config)
cg.add(hub.set_thunder_alert_binary_sensor(var))

View file

@ -4,9 +4,8 @@ from esphome.components import sensor
from esphome.const import (
CONF_DISTANCE,
CONF_LIGHTNING_ENERGY,
DEVICE_CLASS_EMPTY,
STATE_CLASS_NONE,
UNIT_KILOMETER,
UNIT_EMPTY,
ICON_SIGNAL_DISTANCE_VARIANT,
ICON_FLASH,
)
@ -18,24 +17,29 @@ CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
cv.Optional(CONF_DISTANCE): sensor.sensor_schema(
UNIT_KILOMETER, ICON_SIGNAL_DISTANCE_VARIANT, 1, DEVICE_CLASS_EMPTY
unit_of_measurement=UNIT_KILOMETER,
icon=ICON_SIGNAL_DISTANCE_VARIANT,
accuracy_decimals=1,
state_class=STATE_CLASS_NONE,
),
cv.Optional(CONF_LIGHTNING_ENERGY): sensor.sensor_schema(
UNIT_EMPTY, ICON_FLASH, 1, DEVICE_CLASS_EMPTY
icon=ICON_FLASH,
accuracy_decimals=1,
state_class=STATE_CLASS_NONE,
),
}
).extend(cv.COMPONENT_SCHEMA)
def to_code(config):
hub = yield cg.get_variable(config[CONF_AS3935_ID])
async def to_code(config):
hub = await cg.get_variable(config[CONF_AS3935_ID])
if CONF_DISTANCE in config:
conf = config[CONF_DISTANCE]
distance_sensor = yield sensor.new_sensor(conf)
distance_sensor = await sensor.new_sensor(conf)
cg.add(hub.set_distance_sensor(distance_sensor))
if CONF_LIGHTNING_ENERGY in config:
conf = config[CONF_LIGHTNING_ENERGY]
lightning_energy_sensor = yield sensor.new_sensor(conf)
lightning_energy_sensor = await sensor.new_sensor(conf)
cg.add(hub.set_energy_sensor(lightning_energy_sensor))

View file

@ -20,7 +20,7 @@ CONFIG_SCHEMA = cv.All(
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield as3935.setup_as3935(var, config)
yield i2c.register_i2c_device(var, config)
await as3935.setup_as3935(var, config)
await i2c.register_i2c_device(var, config)

View file

@ -4,7 +4,7 @@
namespace esphome {
namespace as3935_i2c {
static const char *TAG = "as3935_i2c";
static const char *const TAG = "as3935_i2c";
void I2CAS3935Component::write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_pos) {
uint8_t write_reg;

View file

@ -20,7 +20,7 @@ CONFIG_SCHEMA = cv.All(
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield as3935.setup_as3935(var, config)
yield spi.register_spi_device(var, config)
await as3935.setup_as3935(var, config)
await spi.register_spi_device(var, config)

View file

@ -4,7 +4,7 @@
namespace esphome {
namespace as3935_spi {
static const char *TAG = "as3935_spi";
static const char *const TAG = "as3935_spi";
void SPIAS3935Component::setup() {
ESP_LOGI(TAG, "SPIAS3935Component setup started!");
@ -33,7 +33,7 @@ void SPIAS3935Component::write_register(uint8_t reg, uint8_t mask, uint8_t bits,
uint8_t SPIAS3935Component::read_register(uint8_t reg) {
uint8_t value = 0;
this->enable();
this->write_byte(reg |= SPI_READ_M);
this->write_byte(reg | SPI_READ_M);
value = this->read_byte();
// According to datsheet, the chip select must be written HIGH, LOW, HIGH
// to correctly end the READ command.

View file

@ -6,10 +6,10 @@ CODEOWNERS = ["@OttoWinter"]
@coroutine_with_priority(200.0)
def to_code(config):
async def to_code(config):
if CORE.is_esp32:
# https://github.com/OttoWinter/AsyncTCP/blob/master/library.json
cg.add_library("AsyncTCP-esphome", "1.1.1")
# https://github.com/esphome/AsyncTCP/blob/master/library.json
cg.add_library("esphome/AsyncTCP-esphome", "1.2.2")
elif CORE.is_esp8266:
# https://github.com/OttoWinter/ESPAsyncTCP
cg.add_library("ESPAsyncTCP-esphome", "1.2.3")

View file

@ -6,7 +6,7 @@
namespace esphome {
namespace atc_mithermometer {
static const char *TAG = "atc_mithermometer";
static const char *const TAG = "atc_mithermometer";
void ATCMiThermometer::dump_config() {
ESP_LOGCONFIG(TAG, "ATC MiThermometer");

View file

@ -12,7 +12,7 @@ from esphome.const import (
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
UNIT_VOLT,
@ -33,16 +33,28 @@ CONFIG_SCHEMA = (
cv.GenerateID(): cv.declare_id(ATCMiThermometer),
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
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_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_HUMIDITY
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
@ -51,22 +63,22 @@ CONFIG_SCHEMA = (
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield esp32_ble_tracker.register_ble_device(var, config)
await cg.register_component(var, config)
await esp32_ble_tracker.register_ble_device(var, config)
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
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(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(sens))
if CONF_BATTERY_LEVEL in config:
sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL])
sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL])
cg.add(var.set_battery_level(sens))
if CONF_BATTERY_VOLTAGE in config:
sens = yield sensor.new_sensor(config[CONF_BATTERY_VOLTAGE])
sens = await sensor.new_sensor(config[CONF_BATTERY_VOLTAGE])
cg.add(var.set_battery_voltage(sens))

View file

@ -5,7 +5,7 @@
namespace esphome {
namespace atm90e32 {
static const char *TAG = "atm90e32";
static const char *const TAG = "atm90e32";
void ATM90E32Component::update() {
if (this->read16_(ATM90E32_REGISTER_METEREN) != 1) {
@ -58,6 +58,24 @@ void ATM90E32Component::update() {
if (this->phase_[2].power_factor_sensor_ != nullptr) {
this->phase_[2].power_factor_sensor_->publish_state(this->get_power_factor_c_());
}
if (this->phase_[0].forward_active_energy_sensor_ != nullptr) {
this->phase_[0].forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_a_());
}
if (this->phase_[1].forward_active_energy_sensor_ != nullptr) {
this->phase_[1].forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_b_());
}
if (this->phase_[2].forward_active_energy_sensor_ != nullptr) {
this->phase_[2].forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_c_());
}
if (this->phase_[0].reverse_active_energy_sensor_ != nullptr) {
this->phase_[0].reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_a_());
}
if (this->phase_[1].reverse_active_energy_sensor_ != nullptr) {
this->phase_[1].reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_b_());
}
if (this->phase_[2].reverse_active_energy_sensor_ != nullptr) {
this->phase_[2].reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_c_());
}
if (this->freq_sensor_ != nullptr) {
this->freq_sensor_->publish_state(this->get_frequency_());
}
@ -119,16 +137,22 @@ void ATM90E32Component::dump_config() {
LOG_SENSOR(" ", "Power A", this->phase_[0].power_sensor_);
LOG_SENSOR(" ", "Reactive Power A", this->phase_[0].reactive_power_sensor_);
LOG_SENSOR(" ", "PF A", this->phase_[0].power_factor_sensor_);
LOG_SENSOR(" ", "Active Forward Energy A", this->phase_[0].forward_active_energy_sensor_);
LOG_SENSOR(" ", "Active Reverse Energy A", this->phase_[0].reverse_active_energy_sensor_);
LOG_SENSOR(" ", "Voltage B", this->phase_[1].voltage_sensor_);
LOG_SENSOR(" ", "Current B", this->phase_[1].current_sensor_);
LOG_SENSOR(" ", "Power B", this->phase_[1].power_sensor_);
LOG_SENSOR(" ", "Reactive Power B", this->phase_[1].reactive_power_sensor_);
LOG_SENSOR(" ", "PF B", this->phase_[1].power_factor_sensor_);
LOG_SENSOR(" ", "Active Forward Energy B", this->phase_[1].forward_active_energy_sensor_);
LOG_SENSOR(" ", "Active Reverse Energy B", this->phase_[1].reverse_active_energy_sensor_);
LOG_SENSOR(" ", "Voltage C", this->phase_[2].voltage_sensor_);
LOG_SENSOR(" ", "Current C", this->phase_[2].current_sensor_);
LOG_SENSOR(" ", "Power C", this->phase_[2].power_sensor_);
LOG_SENSOR(" ", "Reactive Power C", this->phase_[2].reactive_power_sensor_);
LOG_SENSOR(" ", "PF C", this->phase_[2].power_factor_sensor_);
LOG_SENSOR(" ", "Active Forward Energy C", this->phase_[2].forward_active_energy_sensor_);
LOG_SENSOR(" ", "Active Reverse Energy C", this->phase_[2].reverse_active_energy_sensor_);
LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
LOG_SENSOR(" ", "Chip Temp", this->chip_temperature_sensor_);
}
@ -239,6 +263,30 @@ float ATM90E32Component::get_power_factor_c_() {
int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANC);
return (float) pf / 1000;
}
float ATM90E32Component::get_forward_active_energy_a_() {
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYA);
return (float) val * 10 / 3200; // convert register value to WattHours
}
float ATM90E32Component::get_forward_active_energy_b_() {
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYB);
return (float) val * 10 / 3200;
}
float ATM90E32Component::get_forward_active_energy_c_() {
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYC);
return (float) val * 10 / 3200;
}
float ATM90E32Component::get_reverse_active_energy_a_() {
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYA);
return (float) val * 10 / 3200;
}
float ATM90E32Component::get_reverse_active_energy_b_() {
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYB);
return (float) val * 10 / 3200;
}
float ATM90E32Component::get_reverse_active_energy_c_() {
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYC);
return (float) val * 10 / 3200;
}
float ATM90E32Component::get_frequency_() {
uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ);
return (float) freq / 100;

View file

@ -20,6 +20,12 @@ class ATM90E32Component : public PollingComponent,
void set_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].current_sensor_ = obj; }
void set_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_sensor_ = obj; }
void set_reactive_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].reactive_power_sensor_ = obj; }
void set_forward_active_energy_sensor(int phase, sensor::Sensor *obj) {
this->phase_[phase].forward_active_energy_sensor_ = obj;
}
void set_reverse_active_energy_sensor(int phase, sensor::Sensor *obj) {
this->phase_[phase].reverse_active_energy_sensor_ = obj;
}
void set_power_factor_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_factor_sensor_ = obj; }
void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].volt_gain_ = gain; }
void set_ct_gain(int phase, uint16_t gain) { this->phase_[phase].ct_gain_ = gain; }
@ -52,6 +58,12 @@ class ATM90E32Component : public PollingComponent,
float get_power_factor_a_();
float get_power_factor_b_();
float get_power_factor_c_();
float get_forward_active_energy_a_();
float get_forward_active_energy_b_();
float get_forward_active_energy_c_();
float get_reverse_active_energy_a_();
float get_reverse_active_energy_b_();
float get_reverse_active_energy_c_();
float get_frequency_();
float get_chip_temperature_();
@ -63,6 +75,8 @@ class ATM90E32Component : public PollingComponent,
sensor::Sensor *power_sensor_{nullptr};
sensor::Sensor *reactive_power_sensor_{nullptr};
sensor::Sensor *power_factor_sensor_{nullptr};
sensor::Sensor *forward_active_energy_sensor_{nullptr};
sensor::Sensor *reverse_active_energy_sensor_{nullptr};
} phase_[3];
sensor::Sensor *freq_sensor_{nullptr};
sensor::Sensor *chip_temperature_sensor_{nullptr};

View file

@ -39,7 +39,7 @@ static const uint16_t ATM90E32_STATUS_S0_OVPHASEBST = 1 << 11; // Over voltage
static const uint16_t ATM90E32_STATUS_S0_OVPHASECST = 1 << 10; // Over voltage on phase C
static const uint16_t ATM90E32_STATUS_S0_UREVWNST = 1 << 9; // Voltage Phase Sequence Error status
static const uint16_t ATM90E32_STATUS_S0_IREVWNST = 1 << 8; // Current Phase Sequence Error status
static const uint16_t ATM90E32_STATUS_S0_INOV0ST = 1 << 7; // Calculated N line current greater tha INWarnTh reg
static const uint16_t ATM90E32_STATUS_S0_INOV0ST = 1 << 7; // Calculated N line current greater than INWarnTh reg
static const uint16_t ATM90E32_STATUS_S0_TQNOLOADST = 1 << 6; // All phase sum reactive power no-load condition status
static const uint16_t ATM90E32_STATUS_S0_TPNOLOADST = 1 << 5; // All phase sum active power no-load condition status
static const uint16_t ATM90E32_STATUS_S0_TASNOLOADST = 1 << 4; // All phase sum apparent power no-load status

View file

@ -3,34 +3,37 @@ import esphome.config_validation as cv
from esphome.components import sensor, spi
from esphome.const import (
CONF_ID,
CONF_REACTIVE_POWER,
CONF_VOLTAGE,
CONF_CURRENT,
CONF_POWER,
CONF_POWER_FACTOR,
CONF_FREQUENCY,
CONF_FORWARD_ACTIVE_ENERGY,
CONF_REVERSE_ACTIVE_ENERGY,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ICON_EMPTY,
ICON_LIGHTBULB,
ICON_CURRENT_AC,
LAST_RESET_TYPE_AUTO,
STATE_CLASS_MEASUREMENT,
UNIT_HERTZ,
UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
UNIT_EMPTY,
UNIT_CELSIUS,
UNIT_VOLT_AMPS_REACTIVE,
UNIT_WATT_HOURS,
)
CONF_PHASE_A = "phase_a"
CONF_PHASE_B = "phase_b"
CONF_PHASE_C = "phase_c"
CONF_REACTIVE_POWER = "reactive_power"
CONF_LINE_FREQUENCY = "line_frequency"
CONF_CHIP_TEMPERATURE = "chip_temperature"
CONF_GAIN_PGA = "gain_pga"
@ -59,19 +62,47 @@ ATM90E32Component = atm90e32_ns.class_(
ATM90E32_PHASE_SCHEMA = cv.Schema(
{
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER): sensor.sensor_schema(
UNIT_WATT, ICON_EMPTY, 2, DEVICE_CLASS_POWER
unit_of_measurement=UNIT_WATT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(
UNIT_VOLT_AMPS_REACTIVE, ICON_LIGHTBULB, 2, DEVICE_CLASS_EMPTY
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
icon=ICON_LIGHTBULB,
accuracy_decimals=2,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 2, DEVICE_CLASS_POWER_FACTOR
accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER_FACTOR,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_MEASUREMENT,
last_reset_type=LAST_RESET_TYPE_AUTO,
),
cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_MEASUREMENT,
last_reset_type=LAST_RESET_TYPE_AUTO,
),
cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t,
cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
@ -86,10 +117,16 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_PHASE_B): ATM90E32_PHASE_SCHEMA,
cv.Optional(CONF_PHASE_C): ATM90E32_PHASE_SCHEMA,
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY
unit_of_measurement=UNIT_HERTZ,
icon=ICON_CURRENT_AC,
accuracy_decimals=1,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CHIP_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum(
@ -103,10 +140,10 @@ CONFIG_SCHEMA = (
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield spi.register_spi_device(var, config)
await cg.register_component(var, config)
await spi.register_spi_device(var, config)
for i, phase in enumerate([CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]):
if phase not in config:
@ -115,25 +152,31 @@ def to_code(config):
cg.add(var.set_volt_gain(i, conf[CONF_GAIN_VOLTAGE]))
cg.add(var.set_ct_gain(i, conf[CONF_GAIN_CT]))
if CONF_VOLTAGE in conf:
sens = yield sensor.new_sensor(conf[CONF_VOLTAGE])
sens = await sensor.new_sensor(conf[CONF_VOLTAGE])
cg.add(var.set_voltage_sensor(i, sens))
if CONF_CURRENT in conf:
sens = yield sensor.new_sensor(conf[CONF_CURRENT])
sens = await sensor.new_sensor(conf[CONF_CURRENT])
cg.add(var.set_current_sensor(i, sens))
if CONF_POWER in conf:
sens = yield sensor.new_sensor(conf[CONF_POWER])
sens = await sensor.new_sensor(conf[CONF_POWER])
cg.add(var.set_power_sensor(i, sens))
if CONF_REACTIVE_POWER in conf:
sens = yield sensor.new_sensor(conf[CONF_REACTIVE_POWER])
sens = await sensor.new_sensor(conf[CONF_REACTIVE_POWER])
cg.add(var.set_reactive_power_sensor(i, sens))
if CONF_POWER_FACTOR in conf:
sens = yield sensor.new_sensor(conf[CONF_POWER_FACTOR])
sens = await sensor.new_sensor(conf[CONF_POWER_FACTOR])
cg.add(var.set_power_factor_sensor(i, sens))
if CONF_FORWARD_ACTIVE_ENERGY in conf:
sens = await sensor.new_sensor(conf[CONF_FORWARD_ACTIVE_ENERGY])
cg.add(var.set_forward_active_energy_sensor(i, sens))
if CONF_REVERSE_ACTIVE_ENERGY in conf:
sens = await sensor.new_sensor(conf[CONF_REVERSE_ACTIVE_ENERGY])
cg.add(var.set_reverse_active_energy_sensor(i, sens))
if CONF_FREQUENCY in config:
sens = yield sensor.new_sensor(config[CONF_FREQUENCY])
sens = await sensor.new_sensor(config[CONF_FREQUENCY])
cg.add(var.set_freq_sensor(sens))
if CONF_CHIP_TEMPERATURE in config:
sens = yield sensor.new_sensor(config[CONF_CHIP_TEMPERATURE])
sens = await sensor.new_sensor(config[CONF_CHIP_TEMPERATURE])
cg.add(var.set_chip_temperature_sensor(sens))
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES]))

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