Compare commits

..

7 Commits

Author SHA1 Message Date
Otto winter
88632b22e2 Complete 2021-09-06 12:44:53 +02:00
Otto winter
44041d2526 Updates 2021-08-23 20:23:39 +02:00
Otto winter
7cfc36cb70 Add noise API transport support 2021-08-16 20:18:01 +02:00
Otto winter
08dd72e716 Echo component and noise 2021-08-12 13:45:51 +02:00
Otto winter
7b7e5f7db5 Fixes 2021-08-10 19:40:38 +02:00
Otto winter
c9b170eab9 Merge remote-tracking branch 'origin/dev' into socket-refactor-and-ssl 2021-08-10 19:06:46 +02:00
Otto winter
40dd9c5dce Socket refactor and SSL 2021-08-09 20:54:50 +02:00
1093 changed files with 19240 additions and 38807 deletions

View File

@@ -2,11 +2,12 @@
Checks: >-
*,
-abseil-*,
-altera-*,
-android-*,
-boost-*,
-bugprone-branch-clone,
-bugprone-narrowing-conversions,
-bugprone-signed-char-misuse,
-bugprone-too-small-loop-variable,
-cert-dcl50-cpp,
-cert-err58-cpp,
-cert-oop57-cpp,
@@ -16,15 +17,17 @@ Checks: >-
-clang-diagnostic-delete-abstract-non-virtual-dtor,
-clang-diagnostic-delete-non-abstract-non-virtual-dtor,
-clang-diagnostic-shadow-field,
-clang-diagnostic-sign-compare,
-clang-diagnostic-unused-variable,
-clang-diagnostic-unused-const-variable,
-clang-diagnostic-unused-parameter,
-concurrency-*,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-avoid-goto,
-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,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
@@ -36,6 +39,7 @@ Checks: >-
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-special-member-functions,
-fuchsia-default-arguments,
-fuchsia-multiple-inheritance,
-fuchsia-overloaded-operator,
-fuchsia-statically-constructed-objects,
@@ -45,7 +49,6 @@ Checks: >-
-google-explicit-constructor,
-google-readability-braces-around-statements,
-google-readability-casting,
-google-readability-namespace-comments,
-google-readability-todo,
-google-runtime-references,
-hicpp-*,
@@ -58,21 +61,17 @@ Checks: >-
-misc-no-recursion,
-misc-unused-parameters,
-modernize-avoid-c-arrays,
-modernize-avoid-bind,
-modernize-concat-nested-namespaces,
-modernize-return-braced-init-list,
-modernize-use-auto,
-modernize-use-default-member-init,
-modernize-use-equals-default,
-modernize-use-trailing-return-type,
-modernize-use-nodiscard,
-mpi-*,
-objc-*,
-readability-braces-around-statements,
-readability-const-return-type,
-readability-convert-member-functions-to-static,
-readability-else-after-return,
-readability-function-cognitive-complexity,
-readability-implicit-bool-conversion,
-readability-isolate-declaration,
-readability-magic-numbers,
@@ -84,6 +83,7 @@ Checks: >-
-readability-redundant-string-init,
-readability-uppercase-literal-suffix,
-readability-use-anyofallof,
-warnings-as-errors
WarningsAsErrors: '*'
AnalyzeTemporaryDtors: false
FormatStyle: google
@@ -92,11 +92,9 @@ CheckOptions:
value: '1'
- key: google-readability-function-size.StatementThreshold
value: '800'
- key: google-runtime-int.TypeSuffix
value: '_t'
- key: llvm-namespace-comment.ShortNamespaceLines
- key: google-readability-namespace-comments.ShortNamespaceLines
value: '10'
- key: llvm-namespace-comment.SpacesBeforeComments
- key: google-readability-namespace-comments.SpacesBeforeComments
value: '2'
- key: modernize-loop-convert.MaxCopySize
value: '16'
@@ -110,10 +108,6 @@ CheckOptions:
value: llvm
- key: modernize-use-nullptr.NullMacros
value: 'NULL'
- key: modernize-make-unique.MakeSmartPtrFunction
value: 'make_unique'
- key: modernize-make-unique.MakeSmartPtrFunctionHeader
value: 'esphome/core/helpers.h'
- key: readability-identifier-naming.LocalVariableCase
value: 'lower_case'
- key: readability-identifier-naming.ClassCase
@@ -127,19 +121,15 @@ CheckOptions:
- key: readability-identifier-naming.StaticConstantCase
value: 'UPPER_CASE'
- key: readability-identifier-naming.StaticVariableCase
value: 'lower_case'
value: 'UPPER_CASE'
- key: readability-identifier-naming.GlobalConstantCase
value: 'UPPER_CASE'
- key: readability-identifier-naming.ParameterCase
value: 'lower_case'
- key: readability-identifier-naming.PrivateMemberCase
value: 'lower_case'
- key: readability-identifier-naming.PrivateMemberSuffix
value: '_'
- key: readability-identifier-naming.PrivateMethodCase
value: 'lower_case'
- key: readability-identifier-naming.PrivateMethodSuffix
value: '_'
- key: readability-identifier-naming.PrivateMemberPrefix
value: 'NO_PRIVATE_MEMBERS_ALWAYS_USE_PROTECTED'
- key: readability-identifier-naming.PrivateMethodPrefix
value: 'NO_PRIVATE_METHODS_ALWAYS_USE_PROTECTED'
- key: readability-identifier-naming.ClassMemberCase
value: 'lower_case'
- key: readability-identifier-naming.ClassMemberCase

View File

@@ -1,6 +1,7 @@
{
"name": "ESPHome Dev",
"image": "esphome/esphome-lint:dev",
"context": "..",
"dockerFile": "../docker/Dockerfile.dev",
"postCreateCommand": [
"script/devcontainer-post-create"
],

2
.gitattributes vendored
View File

@@ -1,2 +0,0 @@
# Normalize line endings to LF in the repository
* text eol=lf

View File

@@ -16,7 +16,6 @@ Quick description and explanation of changes
## Test Environment
- [ ] ESP32
- [ ] ESP32 IDF
- [ ] ESP8266
## Example entry for `config.yaml`:

7
.github/issue-close-app.yml vendored Normal file
View File

@@ -0,0 +1,7 @@
comment: >-
https://github.com/esphome/esphome/issues/430
issueConfigs:
- content:
- "OTHERWISE THE ISSUE WILL BE CLOSED AUTOMATICALLY"
caseInsensitive: false

36
.github/lock.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
# Configuration for Lock Threads - https://github.com/dessant/lock-threads
# Number of days of inactivity before a closed issue or pull request is locked
daysUntilLock: 7
# Skip issues and pull requests created before a given timestamp. Timestamp must
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
skipCreatedBefore: false
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
exemptLabels:
- keep-open
# Label to add before locking, such as `outdated`. Set to `false` to disable
lockLabel: false
# Comment to post before locking. Set to `false` to disable
lockComment: false
# Assign `resolved` as the reason for locking. Set to `false` to disable
setLockReason: false
# Limit to only `issues` or `pulls`
# only: issues
# Optionally, specify configuration settings just for `issues` or `pulls`
# issues:
# exemptLabels:
# - help-wanted
# lockLabel: outdated
# pulls:
# daysUntilLock: 30
# Repository to extend settings from
# _extends: repo

59
.github/stale.yml vendored Normal file
View File

@@ -0,0 +1,59 @@
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 60
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 7
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
onlyLabels: []
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
- not-stale
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: false
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: true
# Set to true to ignore issues with an assignee (defaults to false)
exemptAssignees: false
# Label to use when marking as stale
staleLabel: stale
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when removing the stale label.
# unmarkComment: >
# Your comment here.
# Comment to post when closing a stale Issue or Pull Request.
# closeComment: >
# Your comment here.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 10
# Limit to only `issues` or `pulls`
only: pulls
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
# pulls:
# daysUntilStale: 30
# markComment: >
# This pull request has been automatically marked as stale because it has not had
# recent activity. It will be closed if no further activity occurs. Thank you
# for your contributions.
# issues:
# exemptLabels:
# - confirmed

View File

@@ -7,19 +7,11 @@ on:
paths:
- 'docker/**'
- '.github/workflows/**'
- 'requirements*.txt'
- 'platformio.ini'
pull_request:
paths:
- 'docker/**'
- '.github/workflows/**'
- 'requirements*.txt'
- 'platformio.ini'
permissions:
contents: read
packages: read
jobs:
check-docker:
@@ -35,11 +27,6 @@ jobs:
uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set TAG
run: |
echo "TAG=check" >> $GITHUB_ENV

View File

@@ -1,3 +1,5 @@
# THESE JOBS ARE COPIED IN release.yml and release-dev.yml
# PLEASE ALSO UPDATE THOSE FILES WHEN CHANGING LINES HERE
name: CI
on:
@@ -6,15 +8,59 @@ on:
pull_request:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
ci-with-container:
name: ${{ matrix.name }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
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
- 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 }}
if: ${{ matrix.id == 'clang-tidy' }}
- 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:
@@ -28,92 +74,48 @@ jobs:
- id: test
file: tests/test1.yaml
name: Test tests/test1.yaml
pio_cache_key: test1
- id: test
file: tests/test2.yaml
name: Test tests/test2.yaml
pio_cache_key: test2
- id: test
file: tests/test3.yaml
name: Test tests/test3.yaml
pio_cache_key: test3
- id: test
file: tests/test4.yaml
name: Test tests/test4.yaml
pio_cache_key: test4
- id: test
file: tests/test5.yaml
name: Test tests/test5.yaml
pio_cache_key: test5
- id: pytest
name: Run pytest
- id: clang-format
name: Run script/clang-format
- id: clang-tidy
name: Run script/clang-tidy for ESP8266
options: --environment esp8266-tidy --grep USE_ESP8266
pio_cache_key: tidyesp8266
- id: clang-tidy
name: Run script/clang-tidy for ESP32 1/4
options: --environment esp32-tidy --split-num 4 --split-at 1
pio_cache_key: tidyesp32
- id: clang-tidy
name: Run script/clang-tidy for ESP32 2/4
options: --environment esp32-tidy --split-num 4 --split-at 2
pio_cache_key: tidyesp32
- id: clang-tidy
name: Run script/clang-tidy for ESP32 3/4
options: --environment esp32-tidy --split-num 4 --split-at 3
pio_cache_key: tidyesp32
- id: clang-tidy
name: Run script/clang-tidy for ESP32 4/4
options: --environment esp32-tidy --split-num 4 --split-at 4
pio_cache_key: tidyesp32
- id: clang-tidy
name: Run script/clang-tidy for ESP32 esp-idf
options: --environment esp32-idf-tidy --grep USE_ESP_IDF
pio_cache_key: tidyesp32-idf
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
id: python
with:
python-version: '3.7'
- name: Cache virtualenv
uses: actions/cache@v2
- name: Cache pip modules
uses: actions/cache@v1
with:
path: .venv
key: venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }}
path: ~/.cache/pip
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
restore-keys: |
venv-${{ steps.python.outputs.python-version }}-
esphome-pip-3.7-
- name: Set up virtualenv
run: |
python -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
pip install -e .
echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH
echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> $GITHUB_ENV
# Use per check platformio cache because checks use different parts
- name: Cache platformio
uses: actions/cache@v2
# Use per test platformio cache because tests have different platform versions
- name: Cache ~/.platformio
uses: actions/cache@v1
with:
path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
if: matrix.id == 'test' || matrix.id == 'clang-tidy'
key: test-home-platformio-${{ matrix.file }}-${{ hashFiles('esphome/core/config.py') }}
restore-keys: |
test-home-platformio-${{ matrix.file }}-
if: ${{ matrix.id == 'test' }}
- name: Install clang tools
run: |
sudo apt-get install \
clang-format-11 \
clang-tidy-11
if: matrix.id == 'clang-tidy' || matrix.id == 'clang-format'
- name: Set up python environment
run: script/setup
- name: Register problem matchers
run: |
@@ -122,45 +124,20 @@ jobs:
echo "::add-matcher::.github/workflows/matchers/python.json"
echo "::add-matcher::.github/workflows/matchers/pytest.json"
echo "::add-matcher::.github/workflows/matchers/gcc.json"
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
- name: Lint Custom
run: |
script/ci-custom.py
script/build_codeowners.py --check
if: matrix.id == 'ci-custom'
if: ${{ matrix.id == 'ci-custom' }}
- name: Lint Python
run: script/lint-python
if: matrix.id == 'lint-python'
if: ${{ matrix.id == 'lint-python' }}
- run: esphome compile ${{ matrix.file }}
if: matrix.id == 'test'
env:
# Also cache libdeps, store them in a ~/.platformio subfolder
PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
if: ${{ matrix.id == 'test' }}
- name: Run pytest
run: |
pytest -vv --tb=native tests
if: matrix.id == 'pytest'
# Also run git-diff-index so that the step is marked as failed on formatting errors,
# since clang-format doesn't do anything but change files if -i is passed.
- name: Run clang-format
run: |
script/clang-format -i
git diff-index --quiet HEAD --
if: matrix.id == 'clang-format'
- name: Run clang-tidy
run: |
script/clang-tidy --all-headers --fix ${{ matrix.options }}
if: matrix.id == 'clang-tidy'
env:
# Also cache libdeps, store them in a ~/.platformio subfolder
PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
- name: Suggested changes
run: script/ci-suggest-changes
if: always() && (matrix.id == 'clang-tidy' || matrix.id == 'clang-format')
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

View File

@@ -1,27 +0,0 @@
name: Lock
on:
schedule:
- cron: '30 0 * * *'
workflow_dispatch:
permissions:
issues: write
pull-requests: write
concurrency:
group: lock
jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v3
with:
pr-inactive-days: "1"
pr-lock-reason: ""
exclude-any-pr-labels: keep-open
issue-inactive-days: "7"
issue-lock-reason: ""
exclude-any-issue-labels: keep-open

View File

@@ -4,7 +4,7 @@
"owner": "ci-custom",
"pattern": [
{
"regexp": "^(.*):(\\d+):(\\d+):\\s+lint:\\s+(.*)$",
"regexp": "^ERROR (.*):(\\d+):(\\d+) - (.*)$",
"file": 1,
"line": 2,
"column": 3,

View File

@@ -5,7 +5,7 @@
"severity": "error",
"pattern": [
{
"regexp": "^src/(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
"regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
"file": 1,
"line": 2,
"column": 3,

View File

@@ -1,22 +1,11 @@
{
"problemMatcher": [
{
"owner": "black",
"severity": "error",
"pattern": [
{
"regexp": "^(.*): (Please format this file with the black formatter)",
"file": 1,
"message": 2
}
]
},
{
"owner": "flake8",
"severity": "error",
"pattern": [
{
"regexp": "^(.*):(\\d+): ([EFCDNW]\\d{3}.*)$",
"regexp": "^(.*):(\\d+) - ([EFCDNW]\\d{3}.*)$",
"file": 1,
"line": 2,
"message": 3
@@ -28,7 +17,7 @@
"severity": "error",
"pattern": [
{
"regexp": "^(.*):(\\d+): (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$",
"regexp": "^(.*):(\\d+) - (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$",
"file": 1,
"line": 2,
"message": 3

View File

@@ -7,9 +7,6 @@ on:
schedule:
- cron: "0 2 * * *"
permissions:
contents: read
jobs:
init:
name: Initialize build
@@ -22,7 +19,7 @@ jobs:
id: tag
run: |
if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then
TAG="${GITHUB_REF#refs/tags/}"
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')"
@@ -55,15 +52,12 @@ jobs:
deploy-docker:
name: Build and publish docker containers
if: github.repository == 'esphome/esphome'
permissions:
contents: read
packages: write
runs-on: ubuntu-latest
needs: [init]
strategy:
matrix:
arch: [amd64, armv7, aarch64]
build_type: ["ha-addon", "docker", "lint"]
build_type: ["ha-addon", "docker"]
steps:
- uses: actions/checkout@v2
- name: Set up Python
@@ -71,10 +65,13 @@ jobs:
with:
python-version: '3.9'
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Run build
run: |
docker/build.py \
--tag "${{ needs.init.outputs.tag }}" \
--arch "${{ matrix.arch }}" \
--build-type "${{ matrix.build_type }}" \
build
- name: Log in to docker hub
uses: docker/login-action@v1
@@ -88,25 +85,21 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
- name: Run push
run: |
docker/build.py \
--tag "${{ needs.init.outputs.tag }}" \
--arch "${{ matrix.arch }}" \
--build-type "${{ matrix.build_type }}" \
build \
--push
push
deploy-docker-manifest:
if: github.repository == 'esphome/esphome'
permissions:
contents: read
packages: write
runs-on: ubuntu-latest
needs: [init, deploy-docker]
strategy:
matrix:
build_type: ["ha-addon", "docker", "lint"]
build_type: ["ha-addon", "docker"]
steps:
- uses: actions/checkout@v2
- name: Set up Python
@@ -145,7 +138,7 @@ jobs:
- env:
TOKEN: ${{ secrets.DEPLOY_HASSIO_TOKEN }}
run: |
TAG="${GITHUB_REF#refs/tags/}"
TAG="${GITHUB_REF#refs/tags/v}"
curl \
-u ":$TOKEN" \
-X POST \

View File

@@ -1,48 +0,0 @@
name: Stale
on:
schedule:
- cron: '30 0 * * *'
workflow_dispatch:
permissions:
issues: write
pull-requests: write
concurrency:
group: lock
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v4
with:
days-before-pr-stale: 90
days-before-pr-close: 7
days-before-issue-stale: -1
days-before-issue-close: -1
remove-stale-when-updated: true
stale-pr-label: "stale"
exempt-pr-labels: "no-stale"
stale-pr-message: >
There hasn't been any activity on this pull request recently. This
pull request has been automatically marked as stale because of that
and will be closed if no further activity occurs within 7 days.
Thank you for your contributions.
# Use stale to automatically close issues with a reference to the issue tracker
close-issues:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v4
with:
days-before-pr-stale: -1
days-before-pr-close: -1
days-before-issue-stale: 1
days-before-issue-close: 1
remove-stale-when-updated: true
stale-issue-label: "stale"
exempt-issue-labels: "not-stale"
stale-issue-message: >
https://github.com/esphome/esphome/issues/430

8
.gitignore vendored
View File

@@ -102,7 +102,10 @@ CMakeLists.txt
.idea/**/dynamic.xml
# CMake
cmake-build-*/
cmake-build-debug/
cmake-build-livingroom8266/
cmake-build-livingroom32/
cmake-build-release/
CMakeCache.txt
CMakeFiles
@@ -124,6 +127,3 @@ tests/.esphome/
/.temp-clang-tidy.cpp
/.temp/
.pio/
sdkconfig.*
!sdkconfig.defaults

View File

@@ -14,11 +14,6 @@ esphome/core/* @esphome/core
esphome/components/ac_dimmer/* @glmnet
esphome/components/adc/* @esphome/core
esphome/components/addressable_light/* @justfalter
esphome/components/airthings_ble/* @jeromelaban
esphome/components/airthings_wave_mini/* @ncareau
esphome/components/airthings_wave_plus/* @jeromelaban
esphome/components/am43/* @buxtronix
esphome/components/am43/cover/* @buxtronix
esphome/components/animation/* @syndlex
esphome/components/anova/* @buxtronix
esphome/components/api/* @OttoWinter
@@ -30,33 +25,23 @@ esphome/components/bang_bang/* @OttoWinter
esphome/components/binary_sensor/* @esphome/core
esphome/components/ble_client/* @buxtronix
esphome/components/bme680_bsec/* @trvrnrth
esphome/components/button/* @esphome/core
esphome/components/canbus/* @danielschramm @mvturnho
esphome/components/cap1188/* @MrEditor97
esphome/components/captive_portal/* @OttoWinter
esphome/components/ccs811/* @habbie
esphome/components/climate/* @esphome/core
esphome/components/climate_ir/* @glmnet
esphome/components/color_temperature/* @jesserockz
esphome/components/coolix/* @glmnet
esphome/components/cover/* @esphome/core
esphome/components/cs5460a/* @balrog-kun
esphome/components/cse7761/* @berfenger
esphome/components/ct_clamp/* @jesserockz
esphome/components/current_based/* @djwmarcx
esphome/components/daly_bms/* @s1lvi0
esphome/components/dashboard_import/* @esphome/core
esphome/components/debug/* @OttoWinter
esphome/components/dfplayer/* @glmnet
esphome/components/dht/* @OttoWinter
esphome/components/ds1307/* @badbadc0ffee
esphome/components/dsmr/* @glmnet @zuidwijk
esphome/components/esp32/* @esphome/core
esphome/components/esp32_ble/* @jesserockz
esphome/components/esp32_ble_server/* @jesserockz
esphome/components/esp32_camera_web_server/* @ayufan
esphome/components/esp32_improv/* @jesserockz
esphome/components/esp8266/* @esphome/core
esphome/components/exposure_notifications/* @OttoWinter
esphome/components/ezo/* @ssieb
esphome/components/fastled_base/* @OttoWinter
@@ -64,16 +49,12 @@ esphome/components/fingerprint_grow/* @OnFreund @loongyh
esphome/components/globals/* @esphome/core
esphome/components/gpio/* @esphome/core
esphome/components/gps/* @coogle
esphome/components/graph/* @synco
esphome/components/havells_solar/* @sourabhjaiswal
esphome/components/hbridge/fan/* @WeekendWarrior
esphome/components/hbridge/light/* @DotNetDann
esphome/components/heatpumpir/* @rob-deutsch
esphome/components/hitachi_ac424/* @sourabhjaiswal
esphome/components/homeassistant/* @OttoWinter
esphome/components/hrxl_maxsonar_wr/* @netmikey
esphome/components/i2c/* @esphome/core
esphome/components/improv_serial/* @esphome/core
esphome/components/improv/* @jesserockz
esphome/components/inkbird_ibsth1_mini/* @fkirill
esphome/components/inkplate6/* @jesserockz
esphome/components/integration/* @OttoWinter
@@ -82,7 +63,6 @@ esphome/components/json/* @OttoWinter
esphome/components/ledc/* @OttoWinter
esphome/components/light/* @esphome/core
esphome/components/logger/* @esphome/core
esphome/components/ltr390/* @sjtrny
esphome/components/max7219digit/* @rspaargaren
esphome/components/mcp23008/* @jesserockz
esphome/components/mcp23017/* @jesserockz
@@ -93,17 +73,9 @@ esphome/components/mcp23x17_base/* @jesserockz
esphome/components/mcp23xxx_base/* @jesserockz
esphome/components/mcp2515/* @danielschramm @mvturnho
esphome/components/mcp9808/* @k7hpn
esphome/components/md5/* @esphome/core
esphome/components/mdns/* @esphome/core
esphome/components/midea/* @dudanov
esphome/components/midea_ac/* @dudanov
esphome/components/midea_dongle/* @dudanov
esphome/components/mitsubishi/* @RubyBailey
esphome/components/modbus_controller/* @martgras
esphome/components/modbus_controller/binary_sensor/* @martgras
esphome/components/modbus_controller/number/* @martgras
esphome/components/modbus_controller/output/* @martgras
esphome/components/modbus_controller/sensor/* @martgras
esphome/components/modbus_controller/switch/* @martgras
esphome/components/modbus_controller/text_sensor/* @martgras
esphome/components/network/* @esphome/core
esphome/components/nextion/* @senexcrenshaw
esphome/components/nextion/binary_sensor/* @senexcrenshaw
@@ -115,14 +87,11 @@ esphome/components/number/* @esphome/core
esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core
esphome/components/pid/* @OttoWinter
esphome/components/pipsolar/* @andreashergert1984
esphome/components/pm1006/* @habbie
esphome/components/pmsa003i/* @sjtrny
esphome/components/pn532/* @OttoWinter @jesserockz
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
esphome/components/pn532_spi/* @OttoWinter @jesserockz
esphome/components/power_supply/* @esphome/core
esphome/components/preferences/* @esphome/core
esphome/components/pulse_meter/* @stevebaxter
esphome/components/pvvx_mithermometer/* @pasiz
esphome/components/rc522/* @glmnet
@@ -132,8 +101,6 @@ esphome/components/restart/* @esphome/core
esphome/components/rf_bridge/* @jesserockz
esphome/components/rgbct/* @jesserockz
esphome/components/rtttl/* @glmnet
esphome/components/safe_mode/* @paulmonigatti
esphome/components/scd4x/* @sjtrny
esphome/components/script/* @esphome/core
esphome/components/sdm_meter/* @jesserockz @polyfaces
esphome/components/sdp3x/* @Azimath
@@ -145,7 +112,6 @@ esphome/components/sht4x/* @sjtrny
esphome/components/shutdown/* @esphome/core
esphome/components/sim800l/* @glmnet
esphome/components/sm2135/* @BoukeHaarsma23
esphome/components/socket/* @esphome/core
esphome/components/spi/* @esphome/core
esphome/components/ssd1322_base/* @kbx81
esphome/components/ssd1322_spi/* @kbx81
@@ -160,7 +126,6 @@ esphome/components/ssd1351_base/* @kbx81
esphome/components/ssd1351_spi/* @kbx81
esphome/components/st7735/* @SenexCrenshaw
esphome/components/st7789v/* @kbx81
esphome/components/st7920/* @marsjan155
esphome/components/substitutions/* @esphome/core
esphome/components/sun/* @OttoWinter
esphome/components/switch/* @esphome/core
@@ -181,7 +146,6 @@ esphome/components/tuya/binary_sensor/* @jesserockz
esphome/components/tuya/climate/* @jesserockz
esphome/components/tuya/sensor/* @jesserockz
esphome/components/tuya/switch/* @jesserockz
esphome/components/tuya/text_sensor/* @dentra
esphome/components/uart/* @esphome/core
esphome/components/ultrasonic/* @OttoWinter
esphome/components/version/* @esphome/core

View File

@@ -1,6 +1,10 @@
# Contributing to ESPHome
For a detailed guide, please see https://esphome.io/guides/contributing.html#contributing-to-esphome
This python project is responsible for reading in YAML configuration files,
converting them to C++ code. This code is then converted to a platformio project and compiled
with [esphome-core](https://github.com/esphome/esphome-core), the C++ framework behind the project.
For a detailed guide, please see https://esphome.io/guides/contributing.html#contributing-to-esphomeyaml
Things to note when contributing:

View File

@@ -1,60 +1,5 @@
# Build these with the build.py script
# Example:
# python3 docker/build.py --tag dev --arch amd64 --build-type docker build
# One of "docker", "hassio"
ARG BASEIMGTYPE=docker
FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.1 AS base-hassio-amd64
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.1 AS base-hassio-arm64
FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.1 AS base-hassio-armv7
FROM debian:bullseye-20211011-slim AS base-docker-amd64
FROM debian:bullseye-20211011-slim AS base-docker-arm64
FROM debian:bullseye-20211011-slim AS base-docker-armv7
# Use TARGETARCH/TARGETVARIANT defined by docker
# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
FROM base-${BASEIMGTYPE}-${TARGETARCH}${TARGETVARIANT} AS base
RUN \
apt-get update \
# Use pinned versions so that we get updates with build caching
&& apt-get install -y --no-install-recommends \
python3=3.9.2-3 \
python3-pip=20.3.4-4 \
python3-setuptools=52.0.0-4 \
python3-pil=8.1.2+dfsg-0.3 \
python3-cryptography=3.3.2-1 \
iputils-ping=3:20210202-1 \
git=1:2.30.2-1 \
curl=7.74.0-1.3+b1 \
&& rm -rf \
/tmp/* \
/var/{cache,log}/* \
/var/lib/apt/lists/*
ENV \
# Fix click python3 lang warning https://click.palletsprojects.com/en/7.x/python3/
LANG=C.UTF-8 LC_ALL=C.UTF-8 \
# Store globally installed pio libs in /piolibs
PLATFORMIO_GLOBALLIB_DIR=/piolibs
RUN \
# Ubuntu python3-pip is missing wheel
pip3 install --no-cache-dir \
wheel==0.36.2 \
platformio==5.2.2 \
# Change some platformio settings
&& platformio settings set enable_telemetry No \
&& platformio settings set check_libraries_interval 1000000 \
&& platformio settings set check_platformio_interval 1000000 \
&& platformio settings set check_platforms_interval 1000000 \
&& mkdir -p /piolibs
# ======================= docker-type image =======================
FROM base AS docker
ARG BUILD_FROM=esphome/esphome-base:latest
FROM ${BUILD_FROM}
# First install requirements to leverage caching when requirements don't change
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
@@ -62,9 +7,9 @@ RUN \
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
&& /platformio_install_deps.py /platformio.ini
# Copy esphome and install
COPY . /esphome
RUN pip3 install --no-cache-dir -e /esphome
# Then copy esphome and install
COPY . .
RUN pip3 install --no-cache-dir -e .
# Settings for dashboard
ENV USERNAME="" PASSWORD=""
@@ -72,85 +17,14 @@ ENV USERNAME="" PASSWORD=""
# Expose the dashboard to Docker
EXPOSE 6052
COPY docker/docker_entrypoint.sh /entrypoint.sh
# Run healthcheck (heartbeat)
HEALTHCHECK --interval=30s --timeout=30s \
CMD curl --fail http://localhost:6052 || exit 1
# The directory the user should mount their configuration files to
VOLUME /config
WORKDIR /config
# Set entrypoint to esphome (via a script) so that the user doesn't have to type 'esphome'
# Set entrypoint to esphome so that the user doesn't have to type 'esphome'
# in every docker command twice
ENTRYPOINT ["/entrypoint.sh"]
ENTRYPOINT ["esphome"]
# When no arguments given, start the dashboard in the workdir
CMD ["dashboard", "/config"]
# ======================= hassio-type image =======================
FROM base AS hassio
RUN \
apt-get update \
# Use pinned versions so that we get updates with build caching
&& apt-get install -y --no-install-recommends \
nginx=1.18.0-6.1 \
&& rm -rf \
/tmp/* \
/var/{cache,log}/* \
/var/lib/apt/lists/*
ARG BUILD_VERSION=dev
# Copy root filesystem
COPY docker/hassio-rootfs/ /
# First install requirements to leverage caching when requirements don't change
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
RUN \
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
&& /platformio_install_deps.py /platformio.ini
# Copy esphome and install
COPY . /esphome
RUN pip3 install --no-cache-dir -e /esphome
# Labels
LABEL \
io.hass.name="ESPHome" \
io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \
io.hass.type="addon" \
io.hass.version="${BUILD_VERSION}"
# io.hass.arch is inherited from addon-debian-base
# ======================= lint-type image =======================
FROM base AS lint
ENV \
PLATFORMIO_CORE_DIR=/esphome/.temp/platformio
RUN \
apt-get update \
# Use pinned versions so that we get updates with build caching
&& apt-get install -y --no-install-recommends \
clang-format-11=1:11.0.1-2 \
clang-tidy-11=1:11.0.1-2 \
patch=2.7.6-7 \
software-properties-common=0.96.20.2-2.1 \
nano=5.4-2 \
build-essential=12.9 \
python3-dev=3.9.2-3 \
&& rm -rf \
/tmp/* \
/var/{cache,log}/* \
/var/lib/apt/lists/*
COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini /
RUN \
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt -r /requirements_test.txt \
&& /platformio_install_deps.py /platformio.ini
VOLUME ["/esphome"]
WORKDIR /esphome

1
docker/Dockerfile.dev Normal file
View File

@@ -0,0 +1 @@
FROM esphome/esphome-lint:1.1

25
docker/Dockerfile.hassio Normal file
View File

@@ -0,0 +1,25 @@
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 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/ /
# Then copy esphome and install
COPY . /opt/esphome/
RUN pip3 install --no-cache-dir -e /opt/esphome
# Build arguments
ARG BUILD_VERSION=dev
# Labels
LABEL \
io.hass.name="ESPHome" \
io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \
io.hass.type="addon" \
io.hass.version=${BUILD_VERSION}

10
docker/Dockerfile.lint Normal file
View File

@@ -0,0 +1,10 @@
ARG BUILD_FROM=esphome/esphome-lint-base:latest
FROM ${BUILD_FROM}
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

View File

@@ -2,7 +2,7 @@
from dataclasses import dataclass
import subprocess
import argparse
from platform import machine
import platform
import shlex
import re
import sys
@@ -24,6 +24,9 @@ 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")
@@ -31,17 +34,27 @@ parser.add_argument("--build-type", choices=TYPES, required=True, help="The type
parser.add_argument("--dry-run", action="store_true", help="Don't run any commands, just print them")
subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True)
build_parser = subparsers.add_parser("build", help="Build the image")
build_parser.add_argument("--push", help="Also push the images", action="store_true")
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
baseimgtype: str
platform: str
target: str
dockerfile: str
@classmethod
def for_type_arch(cls, build_type, arch):
@@ -50,28 +63,18 @@ class DockerParams:
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}"
baseimgtype = {
TYPE_DOCKER: "docker",
TYPE_HA_ADDON: "hassio",
TYPE_LINT: "docker",
}[build_type]
platform = {
ARCH_AMD64: "linux/amd64",
ARCH_ARMV7: "linux/arm/v7",
ARCH_AARCH64: "linux/arm64",
}[arch]
target = {
TYPE_DOCKER: "docker",
TYPE_HA_ADDON: "hassio",
TYPE_LINT: "lint",
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,
baseimgtype=baseimgtype,
platform=platform,
target=target,
dockerfile=dockerfile
)
@@ -109,31 +112,46 @@ def main():
# 1. pull cache image
params = DockerParams.for_type_arch(args.build_type, args.arch)
cache_tag = {
CHANNEL_DEV: "cache-dev",
CHANNEL_BETA: "cache-beta",
CHANNEL_RELEASE: "cache-latest",
CHANNEL_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)
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]
# 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
cmd = [
"docker", "buildx", "build",
"--build-arg", f"BASEIMGTYPE={params.baseimgtype}",
run_command(
"docker", "build",
"--build-arg", f"BUILD_FROM={params.build_from}",
"--build-arg", f"BUILD_VERSION={args.tag}",
"--cache-from", f"type=registry,ref={cache_img}",
"--file", "docker/Dockerfile",
"--platform", params.platform,
"--target", params.target,
]
"--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:
cmd += ["--tag", img]
if args.push:
cmd += ["--push", "--cache-to", f"type=registry,ref={cache_img},mode=max"]
run_command(*cmd, ".")
run_command(
"docker", "push", img
)
elif args.command == "manifest":
manifest = DockerParams.for_type_arch(args.build_type, ARCH_AMD64).manifest_to

View File

@@ -1,24 +0,0 @@
#!/bin/bash
# If /cache is mounted, use that as PIO's coredir
# otherwise use path in /config (so that PIO packages aren't downloaded on each compile)
if [[ -d /cache ]]; then
pio_cache_base=/cache/platformio
else
pio_cache_base=/config/.esphome/platformio
fi
if [[ ! -d "${pio_cache_base}" ]]; then
echo "Creating cache directory ${pio_cache_base}"
echo "You can change this behavior by mounting a directory to the container's /cache directory."
mkdir -p "${pio_cache_base}"
fi
# we can't set core_dir, because the settings file is stored in `core_dir/appstate.json`
# setting `core_dir` would therefore prevent pio from accessing
export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
exec esphome "$@"

View File

@@ -1,9 +0,0 @@
#!/usr/bin/with-contenv bashio
# ==============================================================================
# Community Hass.io Add-ons: ESPHome
# This files creates all directories used by esphome
# ==============================================================================
pio_cache_base=/data/cache/platformio
mkdir -p "${pio_cache_base}"

View File

@@ -3,28 +3,18 @@
# all platformio libraries in the global storage
import configparser
import re
import subprocess
import sys
config = configparser.ConfigParser(inline_comment_prefixes=(';', ))
config = configparser.ConfigParser()
config.read(sys.argv[1])
libs = []
# Extract from every lib_deps key in all sections
for section in config.sections():
conf = config[section]
if "lib_deps" not in conf:
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
for lib_dep in conf["lib_deps"].splitlines():
if not lib_dep:
# Empty line or comment
continue
if lib_dep.startswith("${"):
# Extending from another section
continue
if "@" not in lib_dep:
# No version pinned, this is an internal lib
continue
libs.append(lib_dep)
libs.append(m.group(1))
subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs])

View File

@@ -22,14 +22,5 @@ if bashio::config.has_value 'relative_url'; then
export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url')
fi
pio_cache_base=/data/cache/platformio
# we can't set core_dir, because the settings file is stored in `core_dir/appstate.json`
# setting `core_dir` would therefore prevent pio from accessing
export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
export PLATFORMIO_GLOBALLIB_DIR=/piolibs
bashio::log.info "Starting ESPHome dashboard..."
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --hassio

View File

@@ -11,14 +11,12 @@ from esphome.config import iter_components, read_config, strip_default_ids
from esphome.const import (
CONF_BAUD_RATE,
CONF_BROKER,
CONF_DEASSERT_RTS_DTR,
CONF_LOGGER,
CONF_OTA,
CONF_PASSWORD,
CONF_PORT,
CONF_ESPHOME,
CONF_PLATFORMIO_OPTIONS,
SECRETS_FILES,
)
from esphome.core import CORE, EsphomeError, coroutine
from esphome.helpers import indent
@@ -73,7 +71,7 @@ def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api
if default == "OTA":
return CORE.address
if show_mqtt and "mqtt" in CORE.config:
options.append((f"MQTT ({CORE.config['mqtt'][CONF_BROKER]})", "MQTT"))
options.append(("MQTT ({})".format(CORE.config["mqtt"][CONF_BROKER]), "MQTT"))
if default == "OTA":
return "MQTT"
if default is not None:
@@ -101,21 +99,10 @@ def run_miniterm(config, port):
baud_rate = config["logger"][CONF_BAUD_RATE]
if baud_rate == 0:
_LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.")
return
_LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
backtrace_state = False
ser = serial.Serial()
ser.baudrate = baud_rate
ser.port = port
# We can't set to False by default since it leads to toggling and hence
# ESP32 resets on some platforms.
if config["logger"][CONF_DEASSERT_RTS_DTR]:
ser.dtr = False
ser.rts = False
with ser:
with serial.Serial(port, baudrate=baud_rate) as ser:
while True:
try:
raw = ser.readline()
@@ -181,37 +168,16 @@ def compile_program(args, config):
from esphome import platformio_api
_LOGGER.info("Compiling app...")
rc = platformio_api.run_compile(config, CORE.verbose)
if rc != 0:
return rc
idedata = platformio_api.get_idedata(config)
return 0 if idedata is not None else 1
return platformio_api.run_compile(config, CORE.verbose)
def upload_using_esptool(config, port):
from esphome import platformio_api
path = CORE.firmware_bin
first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
"upload_speed", 460800
)
def run_esptool(baud_rate):
idedata = platformio_api.get_idedata(config)
firmware_offset = "0x10000" if CORE.is_esp32 else "0x0"
flash_images = [
platformio_api.FlashImage(
path=idedata.firmware_bin_path, offset=firmware_offset
),
*idedata.extra_flash_images,
]
mcu = "esp8266"
if CORE.is_esp32:
from esphome.components.esp32 import get_esp32_variant
mcu = get_esp32_variant().lower()
cmd = [
"esptool.py",
"--before",
@@ -220,17 +186,14 @@ def upload_using_esptool(config, port):
"hard_reset",
"--baud",
str(baud_rate),
"--chip",
"esp8266",
"--port",
port,
"--chip",
mcu,
"write_flash",
"-z",
"--flash_size",
"detect",
"0x0",
path,
]
for img in flash_images:
cmd += [img.offset, img.path]
if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
import esptool
@@ -254,7 +217,11 @@ def upload_using_esptool(config, port):
def upload_program(config, args, host):
# if upload is to a serial port use platformio, otherwise assume ota
if get_port_type(host) == "SERIAL":
return upload_using_esptool(config, host)
from esphome import platformio_api
if CORE.is_esp8266:
return upload_using_esptool(config, host)
return platformio_api.run_upload(config, CORE.verbose, host)
from esphome import espota2
@@ -266,7 +233,7 @@ def upload_program(config, args, host):
ota_conf = config[CONF_OTA]
remote_port = ota_conf[CONF_PORT]
password = ota_conf.get(CONF_PASSWORD, "")
password = ota_conf[CONF_PASSWORD]
return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
@@ -277,7 +244,7 @@ def show_logs(config, args, port):
run_miniterm(config, port)
return 0
if get_port_type(port) == "NETWORK" and "api" in config:
from esphome.components.api.client import run_logs
from esphome.api.client import run_logs
return run_logs(config, port)
if get_port_type(port) == "MQTT" and "mqtt" in config:
@@ -317,6 +284,7 @@ def command_vscode(args):
logging.disable(logging.INFO)
logging.disable(logging.WARNING)
CORE.config_path = args.configuration
vscode.read_config(args)
@@ -426,7 +394,7 @@ def command_update_all(args):
import click
success = {}
files = list_yaml_files(args.configuration)
files = list_yaml_files(args.configuration[0])
twidth = 60
def print_bar(middle_text):
@@ -436,49 +404,34 @@ def command_update_all(args):
click.echo(f"{half_line}{middle_text}{half_line}")
for f in files:
print(f"Updating {color(Fore.CYAN, f)}")
print("Updating {}".format(color(Fore.CYAN, f)))
print("-" * twidth)
print()
rc = run_external_process(
"esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
)
if rc == 0:
print_bar(f"[{color(Fore.BOLD_GREEN, 'SUCCESS')}] {f}")
print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f))
success[f] = True
else:
print_bar(f"[{color(Fore.BOLD_RED, 'ERROR')}] {f}")
print_bar("[{}] {}".format(color(Fore.BOLD_RED, "ERROR"), f))
success[f] = False
print()
print()
print()
print_bar(f"[{color(Fore.BOLD_WHITE, 'SUMMARY')}]")
print_bar("[{}]".format(color(Fore.BOLD_WHITE, "SUMMARY")))
failed = 0
for f in files:
if success[f]:
print(f" - {f}: {color(Fore.GREEN, 'SUCCESS')}")
print(" - {}: {}".format(f, color(Fore.GREEN, "SUCCESS")))
else:
print(f" - {f}: {color(Fore.BOLD_RED, 'FAILED')}")
print(" - {}: {}".format(f, color(Fore.BOLD_RED, "FAILED")))
failed += 1
return failed
def command_idedata(args, config):
from esphome import platformio_api
import json
logging.disable(logging.INFO)
logging.disable(logging.WARNING)
idedata = platformio_api.get_idedata(config)
if idedata is None:
return 1
print(json.dumps(idedata.raw, indent=2) + "\n")
return 0
PRE_CONFIG_ACTIONS = {
"wizard": command_wizard,
"version": command_version,
@@ -496,7 +449,6 @@ POST_CONFIG_ACTIONS = {
"clean-mqtt": command_clean_mqtt,
"mqtt-fingerprint": command_mqtt_fingerprint,
"clean": command_clean,
"idedata": command_idedata,
}
@@ -520,9 +472,75 @@ def parse_args(argv):
metavar=("key", "value"),
)
# 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",
"update-all",
],
)
# 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.")
@@ -607,7 +625,10 @@ def parse_args(argv):
"wizard",
help="A helpful setup wizard that will guide you through setting up ESPHome.",
)
parser_wizard.add_argument("configuration", help="Your YAML configuration file.")
parser_wizard.add_argument(
"configuration",
help="Your YAML configuration file.",
)
parser_fingerprint = subparsers.add_parser(
"mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker."
@@ -629,7 +650,8 @@ def parse_args(argv):
"dashboard", help="Create a simple web server for a dashboard."
)
parser_dashboard.add_argument(
"configuration", help="Your YAML configuration file directory."
"configuration",
help="Your YAML configuration file directory.",
)
parser_dashboard.add_argument(
"--port",
@@ -637,12 +659,6 @@ def parse_args(argv):
type=int,
default=6052,
)
parser_dashboard.add_argument(
"--address",
help="The address to bind to.",
type=str,
default="0.0.0.0",
)
parser_dashboard.add_argument(
"--username",
help="The optional username to require for authentication.",
@@ -666,115 +682,31 @@ def parse_args(argv):
)
parser_vscode = subparsers.add_parser("vscode")
parser_vscode.add_argument("configuration", help="Your YAML configuration file.")
parser_vscode.add_argument(
"configuration", help="Your YAML configuration file.", nargs=1
)
parser_vscode.add_argument("--ace", action="store_true")
parser_update = subparsers.add_parser("update-all")
parser_update.add_argument(
"configuration", help="Your YAML configuration file directories.", nargs="+"
"configuration", help="Your YAML configuration file directory.", nargs=1
)
parser_idedata = subparsers.add_parser("idedata")
parser_idedata.add_argument(
"configuration", help="Your YAML configuration file(s).", nargs=1
)
# Keep backward compatibility with the old command line format of
# esphome <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, if parsing using the current format fails, construct an ad-hoc parser
# that doesn't actually process the arguments, but parses them enough to let us
# figure out if the old format is used. In that case, swap the command and
# configuration in the arguments and retry with the normal parser (and raise
# a deprecation warning).
arguments = argv[1:]
# On Python 3.9+ we can simply set exit_on_error=False in the constructor
def _raise(x):
raise argparse.ArgumentError(None, x)
# First, try new-style parsing, but don't exit in case of failure
try:
# duplicate parser so that we can use the original one to raise errors later on
current_parser = argparse.ArgumentParser(add_help=False, parents=[parser])
current_parser.set_defaults(deprecated_argv_suggestion=None)
current_parser.error = _raise
return current_parser.parse_args(arguments)
except argparse.ArgumentError:
pass
# Second, try compat parsing and rearrange the command-line if it succeeds
# Disable argparse's built-in help option and add it manually to prevent this
# parser from printing the help messagefor the old format when invoked with -h.
compat_parser = argparse.ArgumentParser(parents=[options_parser], add_help=False)
compat_parser.add_argument("-h", "--help", action="store_true")
compat_parser.add_argument("configuration", nargs="*")
compat_parser.add_argument(
"command",
choices=[
"config",
"compile",
"upload",
"logs",
"run",
"clean-mqtt",
"wizard",
"mqtt-fingerprint",
"version",
"clean",
"dashboard",
"vscode",
"update-all",
],
)
try:
compat_parser.error = _raise
result, unparsed = compat_parser.parse_known_args(argv[1:])
last_option = len(arguments) - len(unparsed) - 1 - len(result.configuration)
unparsed = [
"--device" if arg in ("--upload-port", "--serial-port") else arg
for arg in unparsed
]
arguments = (
arguments[0:last_option]
+ [result.command]
+ result.configuration
+ unparsed
)
deprecated_argv_suggestion = arguments
except argparse.ArgumentError:
# old-style parsing failed, don't suggest any argument
deprecated_argv_suggestion = None
# Finally, run the new-style parser again with the possibly swapped arguments,
# and let it error out if the command is unparsable.
parser.set_defaults(deprecated_argv_suggestion=deprecated_argv_suggestion)
return parser.parse_args(arguments)
return parser.parse_args(argv[1:])
def run_esphome(argv):
args = parse_args(argv)
CORE.dashboard = args.dashboard
setup_log(
args.verbose,
args.quiet,
# Show timestamp for dashboard access logs
args.command == "dashboard",
)
setup_log(args.verbose, args.quiet)
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))
_LOGGER.warning(" esphome %s", " ".join(args.deprecated_argv_suggestion[1:]))
if sys.version_info < (3, 7, 0):
_LOGGER.error(
@@ -791,16 +723,12 @@ def run_esphome(argv):
return 1
for conf_path in args.configuration:
if any(os.path.basename(conf_path) == x for x in SECRETS_FILES):
_LOGGER.warning("Skipping secrets file %s", conf_path)
continue
CORE.config_path = conf_path
CORE.dashboard = args.dashboard
config = read_config(dict(args.substitution) if args.substitution else {})
if config is None:
return 2
return 1
CORE.config = config
if args.command not in POST_CONFIG_ACTIONS:

3997
esphome/api/api_pb2.py Normal file

File diff suppressed because one or more lines are too long

523
esphome/api/client.py Normal file
View File

@@ -0,0 +1,523 @@
from datetime import datetime
import functools
import logging
import socket
import threading
import time
# pylint: disable=unused-import
from typing import Optional # noqa
from google.protobuf import message # noqa
from esphome import const
import esphome.api.api_pb2 as pb
from esphome.const import CONF_PASSWORD, CONF_PORT
from esphome.core import EsphomeError
from esphome.helpers import resolve_ip_address, indent
from esphome.log import color, Fore
from esphome.util import safe_print
_LOGGER = logging.getLogger(__name__)
class APIConnectionError(EsphomeError):
pass
MESSAGE_TYPE_TO_PROTO = {
1: pb.HelloRequest,
2: pb.HelloResponse,
3: pb.ConnectRequest,
4: pb.ConnectResponse,
5: pb.DisconnectRequest,
6: pb.DisconnectResponse,
7: pb.PingRequest,
8: pb.PingResponse,
9: pb.DeviceInfoRequest,
10: pb.DeviceInfoResponse,
11: pb.ListEntitiesRequest,
12: pb.ListEntitiesBinarySensorResponse,
13: pb.ListEntitiesCoverResponse,
14: pb.ListEntitiesFanResponse,
15: pb.ListEntitiesLightResponse,
16: pb.ListEntitiesSensorResponse,
17: pb.ListEntitiesSwitchResponse,
18: pb.ListEntitiesTextSensorResponse,
19: pb.ListEntitiesDoneResponse,
20: pb.SubscribeStatesRequest,
21: pb.BinarySensorStateResponse,
22: pb.CoverStateResponse,
23: pb.FanStateResponse,
24: pb.LightStateResponse,
25: pb.SensorStateResponse,
26: pb.SwitchStateResponse,
27: pb.TextSensorStateResponse,
28: pb.SubscribeLogsRequest,
29: pb.SubscribeLogsResponse,
30: pb.CoverCommandRequest,
31: pb.FanCommandRequest,
32: pb.LightCommandRequest,
33: pb.SwitchCommandRequest,
34: pb.SubscribeServiceCallsRequest,
35: pb.ServiceCallResponse,
36: pb.GetTimeRequest,
37: pb.GetTimeResponse,
}
def _varuint_to_bytes(value):
if value <= 0x7F:
return bytes([value])
ret = bytes()
while value:
temp = value & 0x7F
value >>= 7
if value:
ret += bytes([temp | 0x80])
else:
ret += bytes([temp])
return ret
def _bytes_to_varuint(value):
result = 0
bitpos = 0
for val in value:
result |= (val & 0x7F) << bitpos
bitpos += 7
if (val & 0x80) == 0:
return result
return None
# pylint: disable=too-many-instance-attributes,not-callable
class APIClient(threading.Thread):
def __init__(self, address, port, password):
threading.Thread.__init__(self)
self._address = address # type: str
self._port = port # type: int
self._password = password # type: Optional[str]
self._socket = None # type: Optional[socket.socket]
self._socket_open_event = threading.Event()
self._socket_write_lock = threading.Lock()
self._connected = False
self._authenticated = False
self._message_handlers = []
self._keepalive = 5
self._ping_timer = None
self.on_disconnect = None
self.on_connect = None
self.on_login = None
self.auto_reconnect = False
self._running_event = threading.Event()
self._stop_event = threading.Event()
@property
def stopped(self):
return self._stop_event.is_set()
def _refresh_ping(self):
if self._ping_timer is not None:
self._ping_timer.cancel()
self._ping_timer = None
def func():
self._ping_timer = None
if self._connected:
try:
self.ping()
except APIConnectionError as err:
self._fatal_error(err)
else:
self._refresh_ping()
self._ping_timer = threading.Timer(self._keepalive, func)
self._ping_timer.start()
def _cancel_ping(self):
if self._ping_timer is not None:
self._ping_timer.cancel()
self._ping_timer = None
def _close_socket(self):
self._cancel_ping()
if self._socket is not None:
self._socket.close()
self._socket = None
self._socket_open_event.clear()
self._connected = False
self._authenticated = False
self._message_handlers = []
def stop(self, force=False):
if self.stopped:
raise ValueError
if self._connected and not force:
try:
self.disconnect()
except APIConnectionError:
pass
self._close_socket()
self._stop_event.set()
if not force:
self.join()
def connect(self):
if not self._running_event.wait(0.1):
raise APIConnectionError("You need to call start() first!")
if self._connected:
self.disconnect(on_disconnect=False)
try:
ip = resolve_ip_address(self._address)
except EsphomeError as err:
_LOGGER.warning(
"Error resolving IP address of %s. Is it connected to WiFi?",
self._address,
)
_LOGGER.warning(
"(If this error persists, please set a static IP address: "
"https://esphome.io/components/wifi.html#manual-ips)"
)
raise APIConnectionError(err) from err
_LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip)
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._socket.settimeout(10.0)
self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
import ssl
context = ssl.SSLContext()
self._socket = context.wrap_socket(self._socket)
try:
self._socket.connect((ip, self._port))
except OSError as err:
err = APIConnectionError(f"Error connecting to {ip}: {err}")
self._fatal_error(err)
raise err
self._socket.settimeout(0.1)
self._socket_open_event.set()
hello = pb.HelloRequest()
hello.client_info = f"ESPHome v{const.__version__}"
try:
resp = self._send_message_await_response(hello, pb.HelloResponse)
except APIConnectionError as err:
self._fatal_error(err)
raise err
_LOGGER.debug(
"Successfully connected to %s ('%s' API=%s.%s)",
self._address,
resp.server_info,
resp.api_version_major,
resp.api_version_minor,
)
self._connected = True
self._refresh_ping()
if self.on_connect is not None:
self.on_connect()
def _check_connected(self):
if not self._connected:
err = APIConnectionError("Must be connected!")
self._fatal_error(err)
raise err
def login(self):
self._check_connected()
if self._authenticated:
raise APIConnectionError("Already logged in!")
connect = pb.ConnectRequest()
if self._password is not None:
connect.password = self._password
resp = self._send_message_await_response(connect, pb.ConnectResponse)
if resp.invalid_password:
raise APIConnectionError("Invalid password!")
self._authenticated = True
if self.on_login is not None:
self.on_login()
def _fatal_error(self, err):
was_connected = self._connected
self._close_socket()
if was_connected and self.on_disconnect is not None:
self.on_disconnect(err)
def _write(self, data): # type: (bytes) -> None
if self._socket is None:
raise APIConnectionError("Socket closed")
# _LOGGER.debug("Write: %s", format_bytes(data))
with self._socket_write_lock:
try:
self._socket.sendall(data)
except OSError as err:
err = APIConnectionError(f"Error while writing data: {err}")
self._fatal_error(err)
raise err
def _send_message(self, msg):
# type: (message.Message) -> None
for message_type, klass in MESSAGE_TYPE_TO_PROTO.items():
if isinstance(msg, klass):
break
else:
raise ValueError
encoded = msg.SerializeToString()
_LOGGER.debug("Sending %s:\n%s", type(msg), indent(str(msg)))
req = bytes([0])
req += _varuint_to_bytes(len(encoded))
req += _varuint_to_bytes(message_type)
req += encoded
self._write(req)
def _send_message_await_response_complex(
self, send_msg, do_append, do_stop, timeout=5
):
event = threading.Event()
responses = []
def on_message(resp):
if do_append(resp):
responses.append(resp)
if do_stop(resp):
event.set()
self._message_handlers.append(on_message)
self._send_message(send_msg)
ret = event.wait(timeout)
try:
self._message_handlers.remove(on_message)
except ValueError:
pass
if not ret:
raise APIConnectionError("Timeout while waiting for message response!")
return responses
def _send_message_await_response(self, send_msg, response_type, timeout=5):
def is_response(msg):
return isinstance(msg, response_type)
return self._send_message_await_response_complex(
send_msg, is_response, is_response, timeout
)[0]
def device_info(self):
self._check_connected()
return self._send_message_await_response(
pb.DeviceInfoRequest(), pb.DeviceInfoResponse
)
def ping(self):
self._check_connected()
return self._send_message_await_response(pb.PingRequest(), pb.PingResponse)
def disconnect(self, on_disconnect=True):
self._check_connected()
try:
self._send_message_await_response(
pb.DisconnectRequest(), pb.DisconnectResponse
)
except APIConnectionError:
pass
self._close_socket()
if self.on_disconnect is not None and on_disconnect:
self.on_disconnect(None)
def _check_authenticated(self):
if not self._authenticated:
raise APIConnectionError("Must login first!")
def subscribe_logs(self, on_log, log_level=7, dump_config=False):
self._check_authenticated()
def on_msg(msg):
if isinstance(msg, pb.SubscribeLogsResponse):
on_log(msg)
self._message_handlers.append(on_msg)
req = pb.SubscribeLogsRequest(dump_config=dump_config)
req.level = log_level
self._send_message(req)
def _recv(self, amount):
ret = bytes()
if amount == 0:
return ret
while len(ret) < amount:
if self.stopped:
raise APIConnectionError("Stopped!")
if not self._socket_open_event.is_set():
raise APIConnectionError("No socket!")
try:
val = self._socket.recv(amount - len(ret))
except AttributeError as err:
raise APIConnectionError("Socket was closed") from err
except socket.timeout:
continue
except OSError as err:
raise APIConnectionError(f"Error while receiving data: {err}") from err
ret += val
return ret
def _recv_varint(self):
raw = bytes()
while not raw or raw[-1] & 0x80:
raw += self._recv(1)
return _bytes_to_varuint(raw)
def _run_once(self):
if not self._socket_open_event.wait(0.1):
return
# Preamble
if self._recv(1)[0] != 0x00:
raise APIConnectionError("Invalid preamble")
length = self._recv_varint()
msg_type = self._recv_varint()
raw_msg = self._recv(length)
if msg_type not in MESSAGE_TYPE_TO_PROTO:
_LOGGER.debug("Skipping message type %s", msg_type)
return
msg = MESSAGE_TYPE_TO_PROTO[msg_type]()
msg.ParseFromString(raw_msg)
_LOGGER.debug("Got message: %s:\n%s", type(msg), indent(str(msg)))
for msg_handler in self._message_handlers[:]:
msg_handler(msg)
self._handle_internal_messages(msg)
def run(self):
self._running_event.set()
while not self.stopped:
try:
self._run_once()
except APIConnectionError as err:
if self.stopped:
break
if self._connected:
_LOGGER.error("Error while reading incoming messages: %s", err)
self._fatal_error(err)
self._running_event.clear()
def _handle_internal_messages(self, msg):
if isinstance(msg, pb.DisconnectRequest):
self._send_message(pb.DisconnectResponse())
if self._socket is not None:
self._socket.close()
self._socket = None
self._connected = False
if self.on_disconnect is not None:
self.on_disconnect(None)
elif isinstance(msg, pb.PingRequest):
self._send_message(pb.PingResponse())
elif isinstance(msg, pb.GetTimeRequest):
resp = pb.GetTimeResponse()
resp.epoch_seconds = int(time.time())
self._send_message(resp)
def run_logs(config, address):
conf = config["api"]
port = conf[CONF_PORT]
password = conf[CONF_PASSWORD]
_LOGGER.info("Starting log output from %s using esphome API", address)
cli = APIClient(address, port, password)
stopping = False
retry_timer = []
has_connects = []
def try_connect(err, tries=0):
if stopping:
return
if err:
_LOGGER.warning("Disconnected from API: %s", err)
while retry_timer:
retry_timer.pop(0).cancel()
error = None
try:
cli.connect()
cli.login()
except APIConnectionError as err2: # noqa
error = err2
if error is None:
_LOGGER.info("Successfully connected to %s", address)
return
wait_time = int(min(1.5 ** min(tries, 100), 30))
if not has_connects:
_LOGGER.warning(
"Initial connection failed. The ESP might not be connected "
"to WiFi yet (%s). Re-Trying in %s seconds",
error,
wait_time,
)
else:
_LOGGER.warning(
"Couldn't connect to API (%s). Trying to reconnect in %s seconds",
error,
wait_time,
)
timer = threading.Timer(
wait_time, functools.partial(try_connect, None, tries + 1)
)
timer.start()
retry_timer.append(timer)
def on_log(msg):
time_ = datetime.now().time().strftime("[%H:%M:%S]")
text = msg.message
if msg.send_failed:
text = color(
Fore.WHITE,
"(Message skipped because it was too big to fit in "
"TCP buffer - This is only cosmetic)",
)
safe_print(time_ + text)
def on_login():
try:
cli.subscribe_logs(on_log, dump_config=not has_connects)
has_connects.append(True)
except APIConnectionError:
cli.disconnect()
cli.on_disconnect = try_connect
cli.on_login = on_login
cli.start()
try:
try_connect(None)
while True:
time.sleep(1)
except KeyboardInterrupt:
stopping = True
cli.stop(True)
while retry_timer:
retry_timer.pop(0).cancel()
return 0

View File

@@ -3,11 +3,9 @@ import esphome.config_validation as cv
from esphome.const import (
CONF_AUTOMATION_ID,
CONF_CONDITION,
CONF_COUNT,
CONF_ELSE,
CONF_ID,
CONF_THEN,
CONF_TIMEOUT,
CONF_TRIGGER_ID,
CONF_TYPE_ID,
CONF_TIME,
@@ -67,7 +65,6 @@ DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
IfAction = cg.esphome_ns.class_("IfAction", Action)
WhileAction = cg.esphome_ns.class_("WhileAction", Action)
RepeatAction = cg.esphome_ns.class_("RepeatAction", Action)
WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component)
UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action)
Automation = cg.esphome_ns.class_("Automation")
@@ -243,32 +240,10 @@ async def while_action_to_code(config, action_id, template_arg, args):
return var
@register_action(
"repeat",
RepeatAction,
cv.Schema(
{
cv.Required(CONF_COUNT): cv.templatable(cv.positive_not_null_int),
cv.Required(CONF_THEN): validate_action_list,
}
),
)
async def repeat_action_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32)
cg.add(var.set_count(count_template))
actions = await build_action_list(config[CONF_THEN], template_arg, args)
cg.add(var.add_then(actions))
return var
def validate_wait_until(value):
schema = cv.Schema(
{
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
cv.Optional(CONF_TIMEOUT): cv.templatable(
cv.positive_time_period_milliseconds
),
}
)
if isinstance(value, dict) and CONF_CONDITION in value:
@@ -280,9 +255,6 @@ def validate_wait_until(value):
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)
if CONF_TIMEOUT in config:
template_ = await cg.templatable(config[CONF_TIMEOUT], args, cg.uint32)
cg.add(var.set_timeout_value(template_))
await cg.register_component(var, {})
return var

View File

@@ -1,4 +1,201 @@
from .const import VARIANT_ESP32, VARIANT_ESP32S2, VARIANT_ESP32C3
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,
@@ -94,7 +291,6 @@ ESP32_BOARD_PINS = {
"SW2": 2,
"SW3": 0,
},
"az-delivery-devkit-v4": {},
"bpi-bit": {
"BUTTON_A": 35,
"BUTTON_B": 27,
@@ -124,8 +320,6 @@ ESP32_BOARD_PINS = {
"RGB_LED": 4,
"TEMPERATURE_SENSOR": 34,
},
"briki_abc_esp32": {},
"briki_mbc-wb_esp32": {},
"d-duino-32": {
"D1": 5,
"D10": 1,
@@ -186,58 +380,11 @@ ESP32_BOARD_PINS = {
"esp32cam": {},
"esp32dev": {},
"esp32doit-devkit-v1": {"LED": 2},
"esp32doit-espduino": {"TX0": 1, "RX0": 3, "CMD": 11, "CLK": 6, "SD0": 7, "SD1": 8},
"esp32thing": {"BUTTON": 0, "LED": 5, "SS": 2},
"esp32thing_plus": {
"SDA": 23,
"SCL": 22,
"SS": 33,
"MOSI": 18,
"MISO": 19,
"SCK": 5,
"A0": 26,
"A1": 25,
"A2": 34,
"A3": 39,
"A4": 36,
"A5": 4,
"A6": 14,
"A7": 32,
"A8": 15,
"A9": 33,
"A10": 27,
"A11": 12,
"A12": 13,
},
"esp32vn-iot-uno": {},
"espea32": {"BUTTON": 0, "LED": 5},
"espectro32": {"LED": 15, "SD_SS": 33},
"espino32": {"BUTTON": 0, "LED": 16},
"etboard": {
"LED_BUILTIN": 5,
"TX": 34,
"RX": 35,
"SS": 29,
"MOSI": 37,
"MISO": 31,
"SCK": 30,
"A0": 36,
"A1": 39,
"A2": 32,
"A3": 33,
"A4": 34,
"A5": 35,
"A6": 25,
"A7": 26,
"D2": 27,
"D3": 14,
"D4": 12,
"D5": 13,
"D6": 15,
"D7": 16,
"D8": 17,
"D9": 4,
},
"featheresp32": {
"A0": 26,
"A1": 25,
@@ -287,18 +434,6 @@ ESP32_BOARD_PINS = {
"SW4": 21,
},
"frogboard": {},
"healtypi4": {
"KEY_BUILTIN": 17,
"ADS1292_DRDY_PIN": 26,
"ADS1292_CS_PIN": 13,
"ADS1292_START_PIN": 14,
"ADS1292_PWDN_PIN": 27,
"AFE4490_CS_PIN": 21,
"AFE4490_DRDY_PIN": 39,
"AFE4490_PWDN_PIN": 4,
"PUSH_BUTTON": 17,
"SLIDE_SWITCH": 16,
},
"heltec_wifi_kit_32": {
"A1": 37,
"A2": 38,
@@ -309,7 +444,6 @@ ESP32_BOARD_PINS = {
"SDA_OLED": 4,
"Vext": 21,
},
"heltec_wifi_kit_32_v2": "heltec_wifi_kit_32",
"heltec_wifi_lora_32": {
"BUTTON": 0,
"DIO0": 26,
@@ -355,68 +489,8 @@ ESP32_BOARD_PINS = {
"SS": 18,
"Vext": 21,
},
"heltec_wireless_stick_lite": {
"LED_BUILTIN": 25,
"KEY_BUILTIN": 0,
"SS": 18,
"MOSI": 27,
"MISO": 19,
"SCK": 5,
"Vext": 21,
"LED": 25,
"RST_LoRa": 14,
"DIO0": 26,
"DIO1": 35,
"DIO2": 34,
},
"honeylemon": {
"LED_BUILTIN": 2,
"BUILTIN_KEY": 0,
},
"hornbill32dev": {"BUTTON": 0, "LED": 13},
"hornbill32minima": {"SS": 2},
"imbrios-logsens-v1p1": {
"LED_BUILTIN": 33,
"UART2_TX": 17,
"UART2_RX": 16,
"UART2_RTS": 4,
"CAN_TX": 17,
"CAN_RX": 16,
"CAN_TXDE": 4,
"SS": 15,
"MOSI": 13,
"MISO": 12,
"SCK": 14,
"SPI_SS1": 23,
"BUZZER_CTRL": 19,
"SD_CARD_DETECT": 35,
"SW2_BUILDIN": 0,
"SW3_BUILDIN": 36,
"SW4_BUILDIN": 34,
"LED1_BUILDIN": 32,
"LED2_BUILDIN": 33,
},
"inex_openkb": {
"LED_BUILTIN": 16,
"LDR_PIN": 36,
"SW1": 16,
"SW2": 14,
"BT_LED": 17,
"WIFI_LED": 2,
"NTP_LED": 15,
"IOT_LED": 12,
"BUZZER": 13,
"INPUT1": 32,
"INPUT2": 33,
"INPUT3": 34,
"INPUT4": 35,
"OUTPUT1": 26,
"OUTPUT2": 27,
"SDA0": 21,
"SCL0": 22,
"SDA1": 4,
"SCL1": 5,
},
"intorobot": {
"A1": 39,
"A2": 35,
@@ -454,40 +528,6 @@ ESP32_BOARD_PINS = {
"iotaap_magnolia": {},
"iotbusio": {},
"iotbusproteus": {},
"kits-edu": {},
"labplus_mpython": {
"SDA": 23,
"SCL": 22,
"P0": 33,
"P1": 32,
"P2": 35,
"P3": 34,
"P4": 39,
"P5": 0,
"P6": 16,
"P7": 17,
"P8": 26,
"P9": 25,
"P10": 36,
"P11": 2,
"P13": 18,
"P14": 19,
"P15": 21,
"P16": 5,
"P19": 22,
"P20": 23,
"P": 27,
"Y": 14,
"T": 12,
"H": 13,
"O": 15,
"N": 4,
"BTN_A": 0,
"BTN_B": 2,
"SOUND": 36,
"LIGHT": 39,
"BUZZER": 16,
},
"lolin32": {"LED": 5},
"lolin32_lite": {"LED": 22},
"lolin_d32": {"LED": 5, "_VBAT": 35},
@@ -514,16 +554,6 @@ ESP32_BOARD_PINS = {
"SDA": 12,
"SS": 18,
},
"m5stack-atom": {
"SDA": 26,
"SCL": 32,
"ADC1": 35,
"ADC2": 36,
"SS": 19,
"MOSI": 33,
"MISO": 23,
"SCK": 22,
},
"m5stack-core-esp32": {
"ADC1": 35,
"ADC2": 36,
@@ -550,26 +580,6 @@ ESP32_BOARD_PINS = {
"RXD2": 16,
"TXD2": 17,
},
"m5stack-core2": {
"SDA": 32,
"SCL": 33,
"SS": 5,
"MOSI": 23,
"MISO": 38,
"SCK": 18,
"ADC1": 35,
"ADC2": 36,
},
"m5stack-coreink": {
"SDA": 32,
"SCL": 33,
"SS": 9,
"MOSI": 23,
"MISO": 34,
"SCK": 18,
"ADC1": 35,
"ADC2": 36,
},
"m5stack-fire": {
"ADC1": 35,
"ADC2": 36,
@@ -620,17 +630,6 @@ ESP32_BOARD_PINS = {
"RXD2": 16,
"TXD2": 17,
},
"m5stack-timer-cam": {
"LED_BUILTIN": 2,
"SDA": 4,
"SCL": 13,
"SS": 5,
"MOSI": 23,
"MISO": 19,
"SCK": 18,
"ADC1": 35,
"ADC2": 36,
},
"m5stick-c": {
"ADC1": 35,
"ADC2": 36,
@@ -665,17 +664,6 @@ ESP32_BOARD_PINS = {
"RIGHT_PUTTON": 34,
"YELLOW_LED": 18,
},
"mgbot-iotik32a": {
"LED_BUILTIN": 4,
"TX2": 17,
"RX2": 16,
},
"mgbot-iotik32b": {
"LED_BUILTIN": 18,
"IR": 27,
"TX2": 17,
"RX2": 16,
},
"mhetesp32devkit": {"LED": 2},
"mhetesp32minikit": {"LED": 2},
"microduino-core-esp32": {
@@ -752,7 +740,6 @@ ESP32_BOARD_PINS = {
},
"node32s": {},
"nodemcu-32s": {"BUTTON": 0, "LED": 2},
"nscreen-32": {},
"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": {
@@ -779,10 +766,6 @@ ESP32_BOARD_PINS = {
"VBAT": 35,
},
"pico32": {},
"piranha_esp32": {
"LED_BUILTIN": 2,
"KEY_BUILTIN": 0,
},
"pocket_32": {"LED": 16},
"pycom_gpy": {
"A1": 37,
@@ -795,14 +778,7 @@ ESP32_BOARD_PINS = {
"SDA": 12,
"SS": 17,
},
"qchip": "heltec_wifi_kit_32",
"quantum": {},
"s_odi_ultra": {
"LED_BUILTIN": 2,
"LED_BUILTINB": 4,
},
"sensesiot_weizen": {},
"sg-o_airMon": {},
"sparkfun_lora_gateway_1-channel": {"MISO": 12, "MOSI": 13, "SCK": 14, "SS": 16},
"tinypico": {},
"ttgo-lora32-v1": {
@@ -814,26 +790,6 @@ ESP32_BOARD_PINS = {
"SCK": 5,
"SS": 18,
},
"ttgo-lora32-v2": {
"LED_BUILTIN": 22,
"KEY_BUILTIN": 0,
"SS": 18,
"MOSI": 27,
"MISO": 19,
"SCK": 5,
"A1": 37,
"A2": 38,
},
"ttgo-lora32-v21": {
"LED_BUILTIN": 25,
"KEY_BUILTIN": 0,
"SS": 18,
"MOSI": 27,
"MISO": 19,
"SCK": 5,
"A1": 37,
"A2": 38,
},
"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},
@@ -899,153 +855,21 @@ ESP32_BOARD_PINS = {
"T5": 5,
"T6": 4,
},
"wifiduino32": {
"LED_BUILTIN": 2,
"KEY_BUILTIN": 0,
"SDA": 5,
"SCL": 16,
"A0": 27,
"A1": 14,
"A2": 12,
"A3": 35,
"A4": 13,
"A5": 4,
"D0": 3,
"D1": 1,
"D2": 17,
"D3": 15,
"D4": 32,
"D5": 33,
"D6": 25,
"D7": 26,
"D8": 23,
"D9": 22,
"D10": 21,
"D11": 19,
"D12": 18,
"D13": 2,
},
"xinabox_cw02": {"LED": 27},
}
"""
BOARD_TO_VARIANT generated with:
git clone https://github.com/platformio/platform-espressif32
for x in platform-espressif32/boards/*.json; do
mcu=$(jq -r .build.mcu <"$x");
fname=$(basename "$x")
board="${fname%.*}"
variant=$(echo "$mcu" | tr '[:lower:]' '[:upper:]')
echo " \"$board\": VARIANT_${variant},"
done | sort
"""
BOARD_TO_VARIANT = {
"alksesp32": VARIANT_ESP32,
"az-delivery-devkit-v4": VARIANT_ESP32,
"bpi-bit": VARIANT_ESP32,
"briki_abc_esp32": VARIANT_ESP32,
"briki_mbc-wb_esp32": VARIANT_ESP32,
"d-duino-32": VARIANT_ESP32,
"esp320": VARIANT_ESP32,
"esp32-c3-devkitm-1": VARIANT_ESP32C3,
"esp32cam": VARIANT_ESP32,
"esp32-devkitlipo": VARIANT_ESP32,
"esp32dev": VARIANT_ESP32,
"esp32doit-devkit-v1": VARIANT_ESP32,
"esp32doit-espduino": VARIANT_ESP32,
"esp32-evb": VARIANT_ESP32,
"esp32-gateway": VARIANT_ESP32,
"esp32-poe-iso": VARIANT_ESP32,
"esp32-poe": VARIANT_ESP32,
"esp32-pro": VARIANT_ESP32,
"esp32-s2-kaluga-1": VARIANT_ESP32S2,
"esp32-s2-saola-1": VARIANT_ESP32S2,
"esp32thing_plus": VARIANT_ESP32,
"esp32thing": VARIANT_ESP32,
"esp32vn-iot-uno": VARIANT_ESP32,
"espea32": VARIANT_ESP32,
"espectro32": VARIANT_ESP32,
"espino32": VARIANT_ESP32,
"esp-wrover-kit": VARIANT_ESP32,
"etboard": VARIANT_ESP32,
"featheresp32-s2": VARIANT_ESP32S2,
"featheresp32": VARIANT_ESP32,
"firebeetle32": VARIANT_ESP32,
"fm-devkit": VARIANT_ESP32,
"frogboard": VARIANT_ESP32,
"healthypi4": VARIANT_ESP32,
"heltec_wifi_kit_32_v2": VARIANT_ESP32,
"heltec_wifi_kit_32": VARIANT_ESP32,
"heltec_wifi_lora_32_V2": VARIANT_ESP32,
"heltec_wifi_lora_32": VARIANT_ESP32,
"heltec_wireless_stick_lite": VARIANT_ESP32,
"heltec_wireless_stick": VARIANT_ESP32,
"honeylemon": VARIANT_ESP32,
"hornbill32dev": VARIANT_ESP32,
"hornbill32minima": VARIANT_ESP32,
"imbrios-logsens-v1p1": VARIANT_ESP32,
"inex_openkb": VARIANT_ESP32,
"intorobot": VARIANT_ESP32,
"iotaap_magnolia": VARIANT_ESP32,
"iotbusio": VARIANT_ESP32,
"iotbusproteus": VARIANT_ESP32,
"kits-edu": VARIANT_ESP32,
"labplus_mpython": VARIANT_ESP32,
"lolin32_lite": VARIANT_ESP32,
"lolin32": VARIANT_ESP32,
"lolin_d32_pro": VARIANT_ESP32,
"lolin_d32": VARIANT_ESP32,
"lopy4": VARIANT_ESP32,
"lopy": VARIANT_ESP32,
"m5stack-atom": VARIANT_ESP32,
"m5stack-core2": VARIANT_ESP32,
"m5stack-core-esp32": VARIANT_ESP32,
"m5stack-coreink": VARIANT_ESP32,
"m5stack-fire": VARIANT_ESP32,
"m5stack-grey": VARIANT_ESP32,
"m5stack-timer-cam": VARIANT_ESP32,
"m5stick-c": VARIANT_ESP32,
"magicbit": VARIANT_ESP32,
"mgbot-iotik32a": VARIANT_ESP32,
"mgbot-iotik32b": VARIANT_ESP32,
"mhetesp32devkit": VARIANT_ESP32,
"mhetesp32minikit": VARIANT_ESP32,
"microduino-core-esp32": VARIANT_ESP32,
"nano32": VARIANT_ESP32,
"nina_w10": VARIANT_ESP32,
"node32s": VARIANT_ESP32,
"nodemcu-32s": VARIANT_ESP32,
"nscreen-32": VARIANT_ESP32,
"odroid_esp32": VARIANT_ESP32,
"onehorse32dev": VARIANT_ESP32,
"oroca_edubot": VARIANT_ESP32,
"pico32": VARIANT_ESP32,
"piranha_esp32": VARIANT_ESP32,
"pocket_32": VARIANT_ESP32,
"pycom_gpy": VARIANT_ESP32,
"qchip": VARIANT_ESP32,
"quantum": VARIANT_ESP32,
"sensesiot_weizen": VARIANT_ESP32,
"sg-o_airMon": VARIANT_ESP32,
"s_odi_ultra": VARIANT_ESP32,
"sparkfun_lora_gateway_1-channel": VARIANT_ESP32,
"tinypico": VARIANT_ESP32,
"ttgo-lora32-v1": VARIANT_ESP32,
"ttgo-lora32-v21": VARIANT_ESP32,
"ttgo-lora32-v2": VARIANT_ESP32,
"ttgo-t1": VARIANT_ESP32,
"ttgo-t7-v13-mini32": VARIANT_ESP32,
"ttgo-t7-v14-mini32": VARIANT_ESP32,
"ttgo-t-beam": VARIANT_ESP32,
"ttgo-t-watch": VARIANT_ESP32,
"turta_iot_node": VARIANT_ESP32,
"vintlabs-devkit-v1": VARIANT_ESP32,
"wemosbat": VARIANT_ESP32,
"wemos_d1_mini32": VARIANT_ESP32,
"wesp32": VARIANT_ESP32,
"widora-air": VARIANT_ESP32,
"wifiduino32": VARIANT_ESP32,
"xinabox_cw02": VARIANT_ESP32,
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

@@ -30,7 +30,6 @@ from esphome.cpp_generator import ( # noqa
add_library,
add_build_flag,
add_define,
add_platformio_option,
get_variable,
get_variable_with_full_id,
process_lambda,
@@ -67,7 +66,7 @@ from esphome.cpp_types import ( # noqa
NAN,
esphome_ns,
App,
EntityBase,
Nameable,
Component,
ComponentPtr,
PollingComponent,
@@ -79,7 +78,4 @@ from esphome.cpp_types import ( # noqa
JsonObjectConstRef,
Controller,
GPIOPin,
InternalGPIOPin,
gpio_Flags,
EntityCategory,
)

View File

@@ -1,7 +1,7 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/esphal.h"
#include "esphome/components/stepper/stepper.h"
namespace esphome {

View File

@@ -1,16 +1,10 @@
#ifdef USE_ARDUINO
#include "ac_dimmer.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <cmath>
#ifdef USE_ESP8266
#ifdef ARDUINO_ARCH_ESP8266
#include <core_esp8266_waveform.h>
#endif
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
#include <esp32-hal-timer.h>
#endif
namespace esphome {
namespace ac_dimmer {
@@ -23,15 +17,12 @@ static AcDimmerDataStore *all_dimmers[32]; // NOLINT(cppcoreguidelines-avoid-no
/// 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)
/// However other factors like gate driver propagation time
/// are also considered and a really low value is not important
/// See also: https://github.com/esphome/issues/issues/1632
static const uint32_t GATE_ENABLE_TIME = 50;
static const uint32_t GATE_ENABLE_TIME = 10;
/// Function called from timer interrupt
/// Input is current time in microseconds (micros())
/// Returns when next "event" is expected in µs, or 0 if no such event known.
uint32_t IRAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
uint32_t ICACHE_RAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
// If no ZC signal received yet.
if (this->crossed_zero_at == 0)
return 0;
@@ -43,13 +34,13 @@ uint32_t IRAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
if (this->enable_time_us != 0 && time_since_zc >= this->enable_time_us) {
this->enable_time_us = 0;
this->gate_pin.digital_write(true);
this->gate_pin->digital_write(true);
// Prevent too short pulses
this->disable_time_us = std::max(this->disable_time_us, time_since_zc + GATE_ENABLE_TIME);
this->disable_time_us = max(this->disable_time_us, time_since_zc + GATE_ENABLE_TIME);
}
if (this->disable_time_us != 0 && time_since_zc >= this->disable_time_us) {
this->disable_time_us = 0;
this->gate_pin.digital_write(false);
this->gate_pin->digital_write(false);
}
if (time_since_zc < this->enable_time_us)
@@ -69,7 +60,7 @@ uint32_t IRAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
}
/// Run timer interrupt code and return in how many µs the next event is expected
uint32_t IRAM_ATTR HOT timer_interrupt() {
uint32_t ICACHE_RAM_ATTR HOT timer_interrupt() {
// run at least with 1kHz
uint32_t min_dt_us = 1000;
uint32_t now = micros();
@@ -86,7 +77,7 @@ uint32_t IRAM_ATTR HOT timer_interrupt() {
}
/// GPIO interrupt routine, called when ZC pin triggers
void IRAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
uint32_t prev_crossed = this->crossed_zero_at;
// 50Hz mains frequency should give a half cycle of 10ms a 60Hz will give 8.33ms
@@ -103,7 +94,7 @@ void IRAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
if (this->value == 65535) {
// fully on, enable output immediately
this->gate_pin.digital_write(true);
this->gate_pin->digital_write(true);
} else if (this->init_cycle) {
// send a full cycle
this->init_cycle = false;
@@ -111,29 +102,29 @@ void IRAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
this->disable_time_us = cycle_time_us;
} else if (this->value == 0) {
// fully off, disable output immediately
this->gate_pin.digital_write(false);
this->gate_pin->digital_write(false);
} else {
if (this->method == DIM_METHOD_TRAILING) {
this->enable_time_us = 1; // cannot be 0
this->disable_time_us = std::max((uint32_t) 10, this->value * this->cycle_time_us / 65535);
this->disable_time_us = max((uint32_t) 10, this->value * this->cycle_time_us / 65535);
} else {
// calculate time until enable in µs: (1.0-value)*cycle_time, but with integer arithmetic
// also take into account min_power
auto min_us = this->cycle_time_us * this->min_power / 1000;
this->enable_time_us = std::max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535);
this->enable_time_us = max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535);
if (this->method == DIM_METHOD_LEADING_PULSE) {
// Minimum pulse time should be enough for the triac to trigger when it is close to the ZC zone
// this is for brightness near 99%
this->disable_time_us = std::max(this->enable_time_us + GATE_ENABLE_TIME, (uint32_t) cycle_time_us / 10);
this->disable_time_us = max(this->enable_time_us + GATE_ENABLE_TIME, (uint32_t) cycle_time_us / 10);
} else {
this->gate_pin.digital_write(false);
this->gate_pin->digital_write(false);
this->disable_time_us = this->cycle_time_us;
}
}
}
}
void IRAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) {
void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) {
// Attaching pin interrupts on the same pin will override the previous interrupt
// However, the user expects that multiple dimmers sharing the same ZC pin will work.
// We solve this in a bit of a hacky way: On each pin interrupt, we check all dimmers
@@ -147,11 +138,11 @@ void IRAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) {
}
}
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
// ESP32 implementation, uses basically the same code but needs to wrap
// timer_interrupt() function to auto-reschedule
static hw_timer_t *dimmer_timer = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void IRAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); }
static hw_timer_t *dimmer_timer = nullptr;
void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); }
#endif
void AcDimmer::setup() {
@@ -180,16 +171,15 @@ void AcDimmer::setup() {
if (setup_zero_cross_pin) {
this->zero_cross_pin_->setup();
this->store_.zero_cross_pin = this->zero_cross_pin_->to_isr();
this->zero_cross_pin_->attach_interrupt(&AcDimmerDataStore::s_gpio_intr, &this->store_,
gpio::INTERRUPT_FALLING_EDGE);
this->zero_cross_pin_->attach_interrupt(&AcDimmerDataStore::s_gpio_intr, &this->store_, FALLING);
}
#ifdef USE_ESP8266
#ifdef ARDUINO_ARCH_ESP8266
// Uses ESP8266 waveform (soft PWM) class
// PWM and AcDimmer can even run at the same time this way
setTimer1Callback(&timer_interrupt);
#endif
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
// 80 Divider -> 1 count=1µs
dimmer_timer = timerBegin(0, 80, true);
timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr, true);
@@ -225,5 +215,3 @@ void AcDimmer::dump_config() {
} // namespace ac_dimmer
} // namespace esphome
#endif // USE_ARDUINO

View File

@@ -1,9 +1,7 @@
#pragma once
#ifdef USE_ARDUINO
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/esphal.h"
#include "esphome/components/output/float_output.h"
namespace esphome {
@@ -13,11 +11,11 @@ enum DimMethod { DIM_METHOD_LEADING_PULSE = 0, DIM_METHOD_LEADING, DIM_METHOD_TR
struct AcDimmerDataStore {
/// Zero-cross pin
ISRInternalGPIOPin zero_cross_pin;
ISRInternalGPIOPin *zero_cross_pin;
/// Zero-cross pin number - used to share ZC pin across multiple dimmers
uint8_t zero_cross_pin_number;
/// Output pin to write to
ISRInternalGPIOPin gate_pin;
ISRInternalGPIOPin *gate_pin;
/// Value of the dimmer - 0 to 65535.
uint16_t value;
/// Minimum power for activation
@@ -39,7 +37,7 @@ struct AcDimmerDataStore {
void gpio_intr();
static void s_gpio_intr(AcDimmerDataStore *store);
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
static void s_timer_intr();
#endif
};
@@ -49,16 +47,16 @@ class AcDimmer : public output::FloatOutput, public Component {
void setup() override;
void dump_config() override;
void set_gate_pin(InternalGPIOPin *gate_pin) { gate_pin_ = gate_pin; }
void set_zero_cross_pin(InternalGPIOPin *zero_cross_pin) { zero_cross_pin_ = zero_cross_pin; }
void set_gate_pin(GPIOPin *gate_pin) { gate_pin_ = gate_pin; }
void set_zero_cross_pin(GPIOPin *zero_cross_pin) { zero_cross_pin_ = zero_cross_pin; }
void set_init_with_half_cycle(bool init_with_half_cycle) { init_with_half_cycle_ = init_with_half_cycle; }
void set_method(DimMethod method) { method_ = method; }
protected:
void write_state(float state) override;
InternalGPIOPin *gate_pin_;
InternalGPIOPin *zero_cross_pin_;
GPIOPin *gate_pin_;
GPIOPin *zero_cross_pin_;
AcDimmerDataStore store_;
bool init_with_half_cycle_;
DimMethod method_;
@@ -66,5 +64,3 @@ class AcDimmer : public output::FloatOutput, public Component {
} // namespace ac_dimmer
} // namespace esphome
#endif // USE_ARDUINO

View File

@@ -19,20 +19,17 @@ DIM_METHODS = {
CONF_GATE_PIN = "gate_pin"
CONF_ZERO_CROSS_PIN = "zero_cross_pin"
CONF_INIT_WITH_HALF_CYCLE = "init_with_half_cycle"
CONFIG_SCHEMA = cv.All(
output.FLOAT_OUTPUT_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(AcDimmer),
cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema,
cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean,
cv.Optional(CONF_METHOD, default="leading pulse"): cv.enum(
DIM_METHODS, upper=True, space="_"
),
}
).extend(cv.COMPONENT_SCHEMA),
cv.only_with_arduino,
)
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(AcDimmer),
cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema,
cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean,
cv.Optional(CONF_METHOD, default="leading pulse"): cv.enum(
DIM_METHODS, upper=True, space="_"
),
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):

View File

@@ -25,7 +25,7 @@ void AdalightLightEffect::stop() {
AddressableLightEffect::stop();
}
unsigned int AdalightLightEffect::get_frame_size_(int led_count) const {
int AdalightLightEffect::get_frame_size_(int led_count) const {
// 3 bytes: Ada
// 2 bytes: LED count
// 1 byte: checksum
@@ -44,7 +44,6 @@ void AdalightLightEffect::blank_all_leds_(light::AddressableLight &it) {
for (int led = it.size(); led-- > 0;) {
it[led].set(Color::BLACK);
}
it.schedule_show();
}
void AdalightLightEffect::apply(light::AddressableLight &it, const Color &current_color) {
@@ -134,7 +133,6 @@ AdalightLightEffect::Frame AdalightLightEffect::parse_frame_(light::AddressableL
it[led].set(Color(led_data[0], led_data[1], led_data[2], white));
}
it.schedule_show();
return CONSUMED;
}

View File

@@ -25,7 +25,7 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U
CONSUMED,
};
unsigned int get_frame_size_(int led_count) const;
int get_frame_size_(int led_count) const;
void reset_frame_(light::AddressableLight &it);
void blank_all_leds_(light::AddressableLight &it);
Frame parse_frame_(light::AddressableLight &it);

View File

@@ -1,14 +1,8 @@
#include "adc_sensor.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#ifdef USE_ESP8266
#ifdef USE_ADC_SENSOR_VCC
#include <Esp.h>
ADC_MODE(ADC_VCC)
#else
#include <Arduino.h>
#endif
#endif
namespace esphome {
@@ -16,153 +10,151 @@ namespace adc {
static const char *const TAG = "adc";
#ifdef ARDUINO_ARCH_ESP32
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() {
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
#ifndef USE_ADC_SENSOR_VCC
pin_->setup();
GPIOPin(this->pin_, INPUT).setup();
#endif
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
adc1_config_channel_atten(gpio_to_adc1(pin_), attenuation_);
adc1_config_width(ADC_WIDTH_BIT_12);
if (!autorange_) {
adc1_config_channel_atten(channel_, attenuation_);
}
// load characteristics for each attenuation
for (int i = 0; i < (int) ADC_ATTEN_MAX; i++) {
auto cal_value = esp_adc_cal_characterize(ADC_UNIT_1, (adc_atten_t) i, ADC_WIDTH_BIT_12,
1100, // default vref
&cal_characteristics_[i]);
switch (cal_value) {
case ESP_ADC_CAL_VAL_EFUSE_VREF:
ESP_LOGV(TAG, "Using eFuse Vref for calibration");
break;
case ESP_ADC_CAL_VAL_EFUSE_TP:
ESP_LOGV(TAG, "Using two-point eFuse Vref for calibration");
break;
case ESP_ADC_CAL_VAL_DEFAULT_VREF:
default:
break;
}
}
// adc_gpio_init doesn't exist on ESP32-C3 or ESP32-H2
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32H2)
adc_gpio_init(ADC_UNIT_1, (adc_channel_t) channel_);
#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2
adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_));
#endif
#endif
#endif // USE_ESP32
}
void ADCSensor::dump_config() {
LOG_SENSOR("", "ADC Sensor", this);
#ifdef USE_ESP8266
#ifdef ARDUINO_ARCH_ESP8266
#ifdef USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Pin: VCC");
#else
LOG_PIN(" Pin: ", pin_);
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
#endif
#endif
#ifdef ARDUINO_ARCH_ESP32
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
switch (this->attenuation_) {
case ADC_ATTEN_DB_0:
ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
break;
case ADC_ATTEN_DB_2_5:
ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)");
break;
case ADC_ATTEN_DB_6:
ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)");
break;
case ADC_ATTEN_DB_11:
ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)");
break;
default: // This is to satisfy the unused ADC_ATTEN_MAX
break;
}
#endif
#endif // USE_ESP8266
#ifdef USE_ESP32
LOG_PIN(" Pin: ", pin_);
if (autorange_)
ESP_LOGCONFIG(TAG, " Attenuation: auto");
else
switch (this->attenuation_) {
case ADC_ATTEN_DB_0:
ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
break;
case ADC_ATTEN_DB_2_5:
ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)");
break;
case ADC_ATTEN_DB_6:
ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)");
break;
case ADC_ATTEN_DB_11:
ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)");
break;
default: // This is to satisfy the unused ADC_ATTEN_MAX
break;
}
#endif // USE_ESP32
LOG_UPDATE_INTERVAL(this);
}
float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
void ADCSensor::update() {
float value_v = this->sample();
ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
ESP_LOGD(TAG, "'%s': Got voltage=%.2fV", this->get_name().c_str(), value_v);
this->publish_state(value_v);
}
#ifdef USE_ESP8266
float ADCSensor::sample() {
#ifdef ARDUINO_ARCH_ESP32
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_ATTEN_DB_0:
value_v *= 1.1;
break;
case ADC_ATTEN_DB_2_5:
value_v *= 1.5;
break;
case ADC_ATTEN_DB_6:
value_v *= 2.2;
break;
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
#ifdef ARDUINO_ARCH_ESP8266
#ifdef USE_ADC_SENSOR_VCC
int raw = ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance)
return ESP.getVcc() / 1024.0f;
#else
int raw = analogRead(this->pin_->get_pin()); // NOLINT
return analogRead(this->pin_) / 1024.0f; // NOLINT
#endif
if (output_raw_) {
return raw;
}
return raw / 1024.0f;
}
#endif
#ifdef USE_ESP32
float ADCSensor::sample() {
if (!autorange_) {
int raw = adc1_get_raw(channel_);
if (raw == -1) {
return NAN;
}
if (output_raw_) {
return raw;
}
uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int) attenuation_]);
return mv / 1000.0f;
}
int raw11, raw6 = 4095, raw2 = 4095, raw0 = 4095;
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_11);
raw11 = adc1_get_raw(channel_);
if (raw11 < 4095) {
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_6);
raw6 = adc1_get_raw(channel_);
if (raw6 < 4095) {
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_2_5);
raw2 = adc1_get_raw(channel_);
if (raw2 < 4095) {
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_0);
raw0 = adc1_get_raw(channel_);
}
}
}
if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw11 == -1) {
return NAN;
}
uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int) ADC_ATTEN_DB_11]);
uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int) ADC_ATTEN_DB_6]);
uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int) ADC_ATTEN_DB_2_5]);
uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int) ADC_ATTEN_DB_0]);
// Contribution of each value, in range 0-2048
uint32_t c11 = std::min(raw11, 2048);
uint32_t c6 = 2048 - std::abs(raw6 - 2048);
uint32_t c2 = 2048 - std::abs(raw2 - 2048);
uint32_t c0 = std::min(4095 - raw0, 2048);
// max theoretical csum value is 2048*4 = 8192
uint32_t csum = c11 + c6 + c2 + c0;
// each mv is max 3900; so max value is 3900*2048*4, fits in unsigned
uint32_t mv_scaled = (mv11 * c11) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0);
return mv_scaled / (float) (csum * 1000U);
}
#endif // USE_ESP32
#ifdef USE_ESP8266
#ifdef ARDUINO_ARCH_ESP8266
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
#endif

View File

@@ -1,14 +1,13 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/esphal.h"
#include "esphome/core/defines.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/voltage_sampler/voltage_sampler.h"
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
#include "driver/adc.h"
#include <esp_adc_cal.h>
#endif
namespace esphome {
@@ -16,11 +15,9 @@ namespace adc {
class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
public:
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
/// Set the attenuation for this pin. Only available on the ESP32.
void set_attenuation(adc_atten_t attenuation) { attenuation_ = attenuation; }
void set_channel(adc1_channel_t channel) { channel_ = channel; }
void set_autorange(bool autorange) { autorange_ = autorange; }
void set_attenuation(adc_atten_t attenuation);
#endif
/// Update adc values.
@@ -30,23 +27,18 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
void dump_config() override;
/// `HARDWARE_LATE` setup priority.
float get_setup_priority() const override;
void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
void set_output_raw(bool output_raw) { output_raw_ = output_raw; }
void set_pin(uint8_t pin) { this->pin_ = pin; }
float sample() override;
#ifdef USE_ESP8266
#ifdef ARDUINO_ARCH_ESP8266
std::string unique_id() override;
#endif
protected:
InternalGPIOPin *pin_;
bool output_raw_{false};
uint8_t pin_;
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
adc_atten_t attenuation_{ADC_ATTEN_DB_0};
adc1_channel_t channel_{};
bool autorange_{false};
esp_adc_cal_characteristics_t cal_characteristics_[(int) ADC_ATTEN_MAX] = {};
#endif
};

View File

@@ -4,24 +4,12 @@ from esphome import pins
from esphome.components import sensor, voltage_sampler
from esphome.const import (
CONF_ATTENUATION,
CONF_RAW,
CONF_ID,
CONF_INPUT,
CONF_NUMBER,
CONF_PIN,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
)
from esphome.core import CORE
from esphome.components.esp32 import get_esp32_variant
from esphome.components.esp32.const import (
VARIANT_ESP32,
VARIANT_ESP32C3,
VARIANT_ESP32H2,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
)
AUTO_LOAD = ["voltage_sampler"]
@@ -31,99 +19,14 @@ ATTENUATION_MODES = {
"2.5db": cg.global_ns.ADC_ATTEN_DB_2_5,
"6db": cg.global_ns.ADC_ATTEN_DB_6,
"11db": cg.global_ns.ADC_ATTEN_DB_11,
"auto": "auto",
}
adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
# pin to adc1 channel mapping
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
VARIANT_ESP32: {
36: adc1_channel_t.ADC1_CHANNEL_0,
37: adc1_channel_t.ADC1_CHANNEL_1,
38: adc1_channel_t.ADC1_CHANNEL_2,
39: adc1_channel_t.ADC1_CHANNEL_3,
32: adc1_channel_t.ADC1_CHANNEL_4,
33: adc1_channel_t.ADC1_CHANNEL_5,
34: adc1_channel_t.ADC1_CHANNEL_6,
35: adc1_channel_t.ADC1_CHANNEL_7,
},
VARIANT_ESP32S2: {
1: adc1_channel_t.ADC1_CHANNEL_0,
2: adc1_channel_t.ADC1_CHANNEL_1,
3: adc1_channel_t.ADC1_CHANNEL_2,
4: adc1_channel_t.ADC1_CHANNEL_3,
5: adc1_channel_t.ADC1_CHANNEL_4,
6: adc1_channel_t.ADC1_CHANNEL_5,
7: adc1_channel_t.ADC1_CHANNEL_6,
8: adc1_channel_t.ADC1_CHANNEL_7,
9: adc1_channel_t.ADC1_CHANNEL_8,
10: adc1_channel_t.ADC1_CHANNEL_9,
},
VARIANT_ESP32S3: {
1: adc1_channel_t.ADC1_CHANNEL_0,
2: adc1_channel_t.ADC1_CHANNEL_1,
3: adc1_channel_t.ADC1_CHANNEL_2,
4: adc1_channel_t.ADC1_CHANNEL_3,
5: adc1_channel_t.ADC1_CHANNEL_4,
6: adc1_channel_t.ADC1_CHANNEL_5,
7: adc1_channel_t.ADC1_CHANNEL_6,
8: adc1_channel_t.ADC1_CHANNEL_7,
9: adc1_channel_t.ADC1_CHANNEL_8,
10: adc1_channel_t.ADC1_CHANNEL_9,
},
VARIANT_ESP32C3: {
0: adc1_channel_t.ADC1_CHANNEL_0,
1: adc1_channel_t.ADC1_CHANNEL_1,
2: adc1_channel_t.ADC1_CHANNEL_2,
3: adc1_channel_t.ADC1_CHANNEL_3,
4: adc1_channel_t.ADC1_CHANNEL_4,
},
VARIANT_ESP32H2: {
0: adc1_channel_t.ADC1_CHANNEL_0,
1: adc1_channel_t.ADC1_CHANNEL_1,
2: adc1_channel_t.ADC1_CHANNEL_2,
3: adc1_channel_t.ADC1_CHANNEL_3,
4: adc1_channel_t.ADC1_CHANNEL_4,
},
}
def validate_adc_pin(value):
if str(value).upper() == "VCC":
return cv.only_on_esp8266("VCC")
if CORE.is_esp32:
value = pins.internal_gpio_input_pin_number(value)
variant = get_esp32_variant()
if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL:
raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported")
if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]:
raise cv.Invalid(f"{variant} doesn't support ADC on this pin")
return pins.internal_gpio_input_pin_schema(value)
if CORE.is_esp8266:
from esphome.components.esp8266.gpio import CONF_ANALOG
value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})(
value
)
if value != 17: # A0
raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.")
return pins.gpio_pin_schema(
{CONF_ANALOG: True, CONF_INPUT: True}, internal=True
)(value)
raise NotImplementedError
def validate_config(config):
if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto":
raise cv.Invalid("Automatic attenuation cannot be used when raw output is set.")
return config
vcc = str(value).upper()
if vcc == "VCC":
return cv.only_on_esp8266(vcc)
return pins.analog_pin(value)
adc_ns = cg.esphome_ns.namespace("adc")
@@ -131,7 +34,7 @@ ADCSensor = adc_ns.class_(
"ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
)
CONFIG_SCHEMA = cv.All(
CONFIG_SCHEMA = (
sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2,
@@ -142,14 +45,12 @@ CONFIG_SCHEMA = cv.All(
{
cv.GenerateID(): cv.declare_id(ADCSensor),
cv.Required(CONF_PIN): validate_adc_pin,
cv.Optional(CONF_RAW, default=False): cv.boolean,
cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All(
cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)
),
}
)
.extend(cv.polling_component_schema("60s")),
validate_config,
.extend(cv.polling_component_schema("60s"))
)
@@ -161,20 +62,7 @@ async def to_code(config):
if config[CONF_PIN] == "VCC":
cg.add_define("USE_ADC_SENSOR_VCC")
else:
pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(pin))
if CONF_RAW in config:
cg.add(var.set_output_raw(config[CONF_RAW]))
cg.add(var.set_pin(config[CONF_PIN]))
if CONF_ATTENUATION in config:
if config[CONF_ATTENUATION] == "auto":
cg.add(var.set_autorange(cg.global_ns.true))
else:
cg.add(var.set_attenuation(config[CONF_ATTENUATION]))
if CORE.is_esp32:
variant = get_esp32_variant()
pin_num = config[CONF_PIN][CONF_NUMBER]
chan = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num]
cg.add(var.set_channel(chan))
cg.add(var.set_attenuation(config[CONF_ATTENUATION]))

View File

@@ -8,7 +8,9 @@ static const char *const TAG = "ade7953";
void ADE7953::dump_config() {
ESP_LOGCONFIG(TAG, "ADE7953:");
LOG_PIN(" IRQ Pin: ", irq_pin_);
if (this->has_irq_) {
ESP_LOGCONFIG(TAG, " IRQ Pin: GPIO%u", this->irq_pin_number_);
}
LOG_I2C_DEVICE(this);
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Voltage Sensor", this->voltage_sensor_);
@@ -18,28 +20,27 @@ void ADE7953::dump_config() {
LOG_SENSOR(" ", "Active Power B Sensor", this->active_power_b_sensor_);
}
#define ADE_PUBLISH_(name, val, factor) \
if (err == i2c::ERROR_OK && this->name##_sensor_) { \
float value = (val) / (factor); \
#define ADE_PUBLISH_(name, factor) \
if ((name) && this->name##_sensor_) { \
float value = *(name) / (factor); \
this->name##_sensor_->publish_state(value); \
}
#define ADE_PUBLISH(name, val, factor) ADE_PUBLISH_(name, val, factor)
#define ADE_PUBLISH(name, factor) ADE_PUBLISH_(name, factor)
void ADE7953::update() {
if (!this->is_setup_)
return;
uint32_t val;
i2c::ErrorCode err = ade_read_32_(0x0312, &val);
ADE_PUBLISH(active_power_a, (int32_t) val, 154.0f);
err = ade_read_32_(0x0313, &val);
ADE_PUBLISH(active_power_b, (int32_t) val, 154.0f);
err = ade_read_32_(0x031A, &val);
ADE_PUBLISH(current_a, (uint32_t) val, 100000.0f);
err = ade_read_32_(0x031B, &val);
ADE_PUBLISH(current_b, (uint32_t) val, 100000.0f);
err = ade_read_32_(0x031C, &val);
ADE_PUBLISH(voltage, (uint32_t) val, 26000.0f);
auto active_power_a = this->ade_read_<int32_t>(0x0312);
ADE_PUBLISH(active_power_a, 154.0f);
auto active_power_b = this->ade_read_<int32_t>(0x0313);
ADE_PUBLISH(active_power_b, 154.0f);
auto current_a = this->ade_read_<uint32_t>(0x031A);
ADE_PUBLISH(current_a, 100000.0f);
auto current_b = this->ade_read_<uint32_t>(0x031B);
ADE_PUBLISH(current_b, 100000.0f);
auto voltage = this->ade_read_<uint32_t>(0x031C);
ADE_PUBLISH(voltage, 26000.0f);
// auto apparent_power_a = this->ade_read_<int32_t>(0x0310);
// auto apparent_power_b = this->ade_read_<int32_t>(0x0311);

View File

@@ -1,7 +1,6 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
@@ -10,7 +9,10 @@ namespace ade7953 {
class ADE7953 : public i2c::I2CDevice, public PollingComponent {
public:
void set_irq_pin(InternalGPIOPin *irq_pin) { irq_pin_ = irq_pin; }
void set_irq_pin(uint8_t irq_pin) {
has_irq_ = true;
irq_pin_number_ = irq_pin;
}
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
void set_current_a_sensor(sensor::Sensor *current_a_sensor) { current_a_sensor_ = current_a_sensor; }
void set_current_b_sensor(sensor::Sensor *current_b_sensor) { current_b_sensor_ = current_b_sensor; }
@@ -22,13 +24,15 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent {
}
void setup() override {
if (this->irq_pin_ != nullptr) {
if (this->has_irq_) {
auto pin = GPIOPin(this->irq_pin_number_, INPUT);
this->irq_pin_ = &pin;
this->irq_pin_->setup();
}
this->set_timeout(100, [this]() {
this->ade_write_8_(0x0010, 0x04);
this->ade_write_8_(0x00FE, 0xAD);
this->ade_write_16_(0x0120, 0x0030);
this->ade_write_<uint8_t>(0x0010, 0x04);
this->ade_write_<uint8_t>(0x00FE, 0xAD);
this->ade_write_<uint16_t>(0x0120, 0x0030);
this->is_setup_ = true;
});
}
@@ -38,51 +42,31 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent {
void update() override;
protected:
i2c::ErrorCode ade_write_8_(uint16_t reg, uint8_t value) {
template<typename T> bool ade_write_(uint16_t reg, T value) {
std::vector<uint8_t> data;
data.push_back(reg >> 8);
data.push_back(reg >> 0);
data.push_back(value);
return write(data.data(), data.size());
for (int i = sizeof(T) - 1; i >= 0; i--)
data.push_back(value >> (i * 8));
return this->write_bytes_raw(data);
}
i2c::ErrorCode ade_write_16_(uint16_t reg, uint16_t value) {
std::vector<uint8_t> data;
data.push_back(reg >> 8);
data.push_back(reg >> 0);
data.push_back(value >> 8);
data.push_back(value >> 0);
return write(data.data(), data.size());
}
i2c::ErrorCode ade_write_32_(uint16_t reg, uint32_t value) {
std::vector<uint8_t> data;
data.push_back(reg >> 8);
data.push_back(reg >> 0);
data.push_back(value >> 24);
data.push_back(value >> 16);
data.push_back(value >> 8);
data.push_back(value >> 0);
return write(data.data(), data.size());
}
i2c::ErrorCode ade_read_32_(uint16_t reg, uint32_t *value) {
uint8_t reg_data[2];
reg_data[0] = reg >> 8;
reg_data[1] = reg >> 0;
i2c::ErrorCode err = write(reg_data, 2);
if (err != i2c::ERROR_OK)
return err;
uint8_t recv[4];
err = read(recv, 4);
if (err != i2c::ERROR_OK)
return err;
*value = 0;
*value |= ((uint32_t) recv[0]) << 24;
*value |= ((uint32_t) recv[1]) << 16;
*value |= ((uint32_t) recv[2]) << 8;
*value |= ((uint32_t) recv[3]);
return i2c::ERROR_OK;
template<typename T> optional<T> ade_read_(uint16_t reg) {
uint8_t hi = reg >> 8;
uint8_t lo = reg >> 0;
if (!this->write_bytes_raw({hi, lo}))
return {};
auto ret = this->read_bytes_raw<sizeof(T)>();
if (!ret.has_value())
return {};
T result = 0;
for (int i = 0, j = sizeof(T) - 1; i < sizeof(T); i++, j--)
result |= T((*ret)[i]) << (j * 8);
return result;
}
InternalGPIOPin *irq_pin_ = nullptr;
bool has_irq_ = false;
uint8_t irq_pin_number_;
GPIOPin *irq_pin_{nullptr};
bool is_setup_{false};
sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *current_a_sensor_{nullptr};

View File

@@ -29,7 +29,7 @@ CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(ADE7953),
cv.Optional(CONF_IRQ_PIN): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_IRQ_PIN): pins.input_pin,
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=1,
@@ -73,8 +73,7 @@ async def to_code(config):
await i2c.register_i2c_device(var, config)
if CONF_IRQ_PIN in config:
irq_pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN])
cg.add(var.set_irq_pin(irq_pin))
cg.add(var.set_irq_pin(config[CONF_IRQ_PIN]))
for key in [
CONF_VOLTAGE,

View File

@@ -1,6 +1,5 @@
#include "ads1115.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace ads1115 {
@@ -160,7 +159,7 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
float ADS1115Sensor::sample() { return this->parent_->request_measurement(this); }
void ADS1115Sensor::update() {
float v = this->parent_->request_measurement(this);
if (!std::isnan(v)) {
if (!isnan(v)) {
ESP_LOGD(TAG, "'%s': Got Voltage=%fV", this->get_name().c_str(), v);
this->publish_state(v);
}

View File

@@ -14,7 +14,6 @@
#include "aht10.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace aht10 {
@@ -34,19 +33,8 @@ void AHT10Component::setup() {
this->mark_failed();
return;
}
uint8_t data = 0;
if (this->write(&data, 1) != i2c::ERROR_OK) {
ESP_LOGD(TAG, "Communication with AHT10 failed!");
this->mark_failed();
return;
}
delay(AHT10_DEFAULT_DELAY);
if (this->read(&data, 1) != i2c::ERROR_OK) {
ESP_LOGD(TAG, "Communication with AHT10 failed!");
this->mark_failed();
return;
}
if (this->read(&data, 1) != i2c::ERROR_OK) {
uint8_t data;
if (!this->read_byte(0, &data, AHT10_DEFAULT_DELAY)) {
ESP_LOGD(TAG, "Communication with AHT10 failed!");
this->mark_failed();
return;
@@ -67,19 +55,15 @@ void AHT10Component::update() {
return;
}
uint8_t data[6];
uint8_t delay_ms = AHT10_DEFAULT_DELAY;
uint8_t delay = AHT10_DEFAULT_DELAY;
if (this->humidity_sensor_ != nullptr)
delay_ms = AHT10_HUMIDITY_DELAY;
bool success = false;
delay = AHT10_HUMIDITY_DELAY;
for (int i = 0; i < AHT10_ATTEMPTS; ++i) {
ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis());
delay(delay_ms);
if (this->read(data, 6) != i2c::ERROR_OK) {
ESP_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...");
continue;
}
if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy
} else if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy
ESP_LOGD(TAG, "AHT10 is busy, waiting...");
} else if (data[1] == 0x0 && data[2] == 0x0 && (data[3] >> 4) == 0x0) {
// Unrealistic humidity (0x0)
@@ -96,12 +80,11 @@ void AHT10Component::update() {
}
} else {
// data is valid, we can break the loop
ESP_LOGVV(TAG, "Answer at %6u", millis());
success = true;
ESP_LOGVV(TAG, "Answer at %6ld", millis());
break;
}
}
if (!success || (data[0] & 0x80) == 0x80) {
if ((data[0] & 0x80) == 0x80) {
ESP_LOGE(TAG, "Measurements reading timed-out!");
this->status_set_warning();
return;
@@ -110,19 +93,19 @@ void AHT10Component::update() {
uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4;
float temperature = ((200.0f * (float) raw_temperature) / 1048576.0f) - 50.0f;
float temperature = ((200.0 * (float) raw_temperature) / 1048576.0) - 50.0;
float humidity;
if (raw_humidity == 0) { // unrealistic value
humidity = NAN;
} else {
humidity = (float) raw_humidity * 100.0f / 1048576.0f;
humidity = (float) raw_humidity * 100.0 / 1048576.0;
}
if (this->temperature_sensor_ != nullptr) {
this->temperature_sensor_->publish_state(temperature);
}
if (this->humidity_sensor_ != nullptr) {
if (std::isnan(humidity))
if (isnan(humidity))
ESP_LOGW(TAG, "Invalid humidity! Sensor reported 0%% Hum");
this->humidity_sensor_->publish_state(humidity);
}

View File

@@ -1,23 +0,0 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import esp32_ble_tracker
from esphome.const import CONF_ID
DEPENDENCIES = ["esp32_ble_tracker"]
CODEOWNERS = ["@jeromelaban"]
airthings_ble_ns = cg.esphome_ns.namespace("airthings_ble")
AirthingsListener = airthings_ble_ns.class_(
"AirthingsListener", esp32_ble_tracker.ESPBTDeviceListener
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(AirthingsListener),
}
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield esp32_ble_tracker.register_ble_device(var, config)

View File

@@ -1,33 +0,0 @@
#include "airthings_listener.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
namespace esphome {
namespace airthings_ble {
static const char *const TAG = "airthings_ble";
bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
for (auto &it : device.get_manufacturer_datas()) {
if (it.uuid == esp32_ble_tracker::ESPBTUUID::from_uint32(0x0334)) {
if (it.data.size() < 4)
continue;
uint32_t sn = it.data[0];
sn |= ((uint32_t) it.data[1] << 8);
sn |= ((uint32_t) it.data[2] << 16);
sn |= ((uint32_t) it.data[3] << 24);
ESP_LOGD(TAG, "Found AirThings device Serial:%u (MAC: %s)", sn, device.address_str().c_str());
return true;
}
}
return false;
}
} // namespace airthings_ble
} // namespace esphome
#endif

View File

@@ -1,19 +0,0 @@
#pragma once
#ifdef USE_ESP32
#include "esphome/core/component.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
namespace esphome {
namespace airthings_ble {
class AirthingsListener : public esp32_ble_tracker::ESPBTDeviceListener {
public:
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
};
} // namespace airthings_ble
} // namespace esphome
#endif

View File

@@ -1 +0,0 @@
CODEOWNERS = ["@ncareau"]

View File

@@ -1,113 +0,0 @@
#include "airthings_wave_mini.h"
#ifdef USE_ESP32
namespace esphome {
namespace airthings_wave_mini {
static const char *const TAG = "airthings_wave_mini";
void AirthingsWaveMini::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) {
switch (event) {
case ESP_GATTC_OPEN_EVT: {
if (param->open.status == ESP_GATT_OK) {
ESP_LOGI(TAG, "Connected successfully!");
}
break;
}
case ESP_GATTC_DISCONNECT_EVT: {
ESP_LOGW(TAG, "Disconnected!");
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
this->handle_ = 0;
auto chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(),
sensors_data_characteristic_uuid_.to_string().c_str());
break;
}
this->handle_ = chr->handle;
this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED;
request_read_values_();
break;
}
case ESP_GATTC_READ_CHAR_EVT: {
if (param->read.conn_id != this->parent()->conn_id)
break;
if (param->read.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
break;
}
if (param->read.handle == this->handle_) {
read_sensors_(param->read.value, param->read.value_len);
}
break;
}
default:
break;
}
}
void AirthingsWaveMini::read_sensors_(uint8_t *raw_value, uint16_t value_len) {
auto value = (WaveMiniReadings *) raw_value;
if (sizeof(WaveMiniReadings) <= value_len) {
this->humidity_sensor_->publish_state(value->humidity / 100.0f);
this->pressure_sensor_->publish_state(value->pressure / 50.0f);
this->temperature_sensor_->publish_state(value->temperature / 100.0f - 273.15f);
if (is_valid_voc_value_(value->voc)) {
this->tvoc_sensor_->publish_state(value->voc);
}
// This instance must not stay connected
// so other clients can connect to it (e.g. the
// mobile app).
parent()->set_enabled(false);
}
}
bool AirthingsWaveMini::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; }
void AirthingsWaveMini::update() {
if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) {
if (!parent()->enabled) {
ESP_LOGW(TAG, "Reconnecting to device");
parent()->set_enabled(true);
parent()->connect();
} else {
ESP_LOGW(TAG, "Connection in progress");
}
}
}
void AirthingsWaveMini::request_read_values_() {
auto status =
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
}
}
void AirthingsWaveMini::dump_config() {
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
}
AirthingsWaveMini::AirthingsWaveMini()
: PollingComponent(10000),
service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)),
sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {}
} // namespace airthings_wave_mini
} // namespace esphome
#endif // USE_ESP32

View File

@@ -1,65 +0,0 @@
#pragma once
#ifdef USE_ESP32
#include <esp_gattc_api.h>
#include <algorithm>
#include <iterator>
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
#include "esphome/core/log.h"
namespace esphome {
namespace airthings_wave_mini {
static const char *const SERVICE_UUID = "b42e3882-ade7-11e4-89d3-123b93f75cba";
static const char *const CHARACTERISTIC_UUID = "b42e3b98-ade7-11e4-89d3-123b93f75cba";
class AirthingsWaveMini : public PollingComponent, public ble_client::BLEClientNode {
public:
AirthingsWaveMini();
void dump_config() override;
void update() override;
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
void set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; }
void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
protected:
bool is_valid_voc_value_(uint16_t voc);
void read_sensors_(uint8_t *value, uint16_t value_len);
void request_read_values_();
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
sensor::Sensor *pressure_sensor_{nullptr};
sensor::Sensor *tvoc_sensor_{nullptr};
uint16_t handle_;
esp32_ble_tracker::ESPBTUUID service_uuid_;
esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_;
struct WaveMiniReadings {
uint16_t unused01;
uint16_t temperature;
uint16_t pressure;
uint16_t humidity;
uint16_t voc;
uint16_t unused02;
uint32_t unused03;
uint32_t unused04;
};
};
} // namespace airthings_wave_mini
} // namespace esphome
#endif // USE_ESP32

View File

@@ -1,82 +0,0 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, ble_client
from esphome.const import (
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
STATE_CLASS_MEASUREMENT,
UNIT_PERCENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
CONF_ID,
CONF_HUMIDITY,
CONF_TVOC,
CONF_PRESSURE,
CONF_TEMPERATURE,
UNIT_PARTS_PER_BILLION,
ICON_RADIATOR,
)
DEPENDENCIES = ["ble_client"]
airthings_wave_mini_ns = cg.esphome_ns.namespace("airthings_wave_mini")
AirthingsWaveMini = airthings_wave_mini_ns.class_(
"AirthingsWaveMini", cg.PollingComponent, ble_client.BLEClientNode
)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(AirthingsWaveMini),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=2,
),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement=UNIT_HECTOPASCAL,
accuracy_decimals=2,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TVOC): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_BILLION,
icon=ICON_RADIATOR,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("5min"))
.extend(ble_client.BLE_CLIENT_SCHEMA),
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await ble_client.register_ble_node(var, config)
if CONF_HUMIDITY in config:
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
cg.add(var.set_humidity(sens))
if CONF_TEMPERATURE in config:
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
cg.add(var.set_temperature(sens))
if CONF_PRESSURE in config:
sens = await sensor.new_sensor(config[CONF_PRESSURE])
cg.add(var.set_pressure(sens))
if CONF_TVOC in config:
sens = await sensor.new_sensor(config[CONF_TVOC])
cg.add(var.set_tvoc(sens))

View File

@@ -1 +0,0 @@
CODEOWNERS = ["@jeromelaban"]

View File

@@ -1,137 +0,0 @@
#include "airthings_wave_plus.h"
#ifdef USE_ESP32
namespace esphome {
namespace airthings_wave_plus {
static const char *const TAG = "airthings_wave_plus";
void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) {
switch (event) {
case ESP_GATTC_OPEN_EVT: {
if (param->open.status == ESP_GATT_OK) {
ESP_LOGI(TAG, "Connected successfully!");
}
break;
}
case ESP_GATTC_DISCONNECT_EVT: {
ESP_LOGW(TAG, "Disconnected!");
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
this->handle_ = 0;
auto chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(),
sensors_data_characteristic_uuid_.to_string().c_str());
break;
}
this->handle_ = chr->handle;
this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED;
request_read_values_();
break;
}
case ESP_GATTC_READ_CHAR_EVT: {
if (param->read.conn_id != this->parent()->conn_id)
break;
if (param->read.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
break;
}
if (param->read.handle == this->handle_) {
read_sensors_(param->read.value, param->read.value_len);
}
break;
}
default:
break;
}
}
void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) {
auto value = (WavePlusReadings *) raw_value;
if (sizeof(WavePlusReadings) <= value_len) {
ESP_LOGD(TAG, "version = %d", value->version);
if (value->version == 1) {
ESP_LOGD(TAG, "ambient light = %d", value->ambientLight);
this->humidity_sensor_->publish_state(value->humidity / 2.0f);
if (is_valid_radon_value_(value->radon)) {
this->radon_sensor_->publish_state(value->radon);
}
if (is_valid_radon_value_(value->radon_lt)) {
this->radon_long_term_sensor_->publish_state(value->radon_lt);
}
this->temperature_sensor_->publish_state(value->temperature / 100.0f);
this->pressure_sensor_->publish_state(value->pressure / 50.0f);
if (is_valid_co2_value_(value->co2)) {
this->co2_sensor_->publish_state(value->co2);
}
if (is_valid_voc_value_(value->voc)) {
this->tvoc_sensor_->publish_state(value->voc);
}
// This instance must not stay connected
// so other clients can connect to it (e.g. the
// mobile app).
parent()->set_enabled(false);
} else {
ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version);
}
}
}
bool AirthingsWavePlus::is_valid_radon_value_(uint16_t radon) { return 0 <= radon && radon <= 16383; }
bool AirthingsWavePlus::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; }
bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return 0 <= co2 && co2 <= 16383; }
void AirthingsWavePlus::update() {
if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) {
if (!parent()->enabled) {
ESP_LOGW(TAG, "Reconnecting to device");
parent()->set_enabled(true);
parent()->connect();
} else {
ESP_LOGW(TAG, "Connection in progress");
}
}
}
void AirthingsWavePlus::request_read_values_() {
auto status =
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
}
}
void AirthingsWavePlus::dump_config() {
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
LOG_SENSOR(" ", "Radon", this->radon_sensor_);
LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
LOG_SENSOR(" ", "CO2", this->co2_sensor_);
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
}
AirthingsWavePlus::AirthingsWavePlus()
: PollingComponent(10000),
service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)),
sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {}
} // namespace airthings_wave_plus
} // namespace esphome
#endif // USE_ESP32

View File

@@ -1,75 +0,0 @@
#pragma once
#ifdef USE_ESP32
#include <esp_gattc_api.h>
#include <algorithm>
#include <iterator>
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
#include "esphome/core/log.h"
namespace esphome {
namespace airthings_wave_plus {
static const char *const SERVICE_UUID = "b42e1c08-ade7-11e4-89d3-123b93f75cba";
static const char *const CHARACTERISTIC_UUID = "b42e2a68-ade7-11e4-89d3-123b93f75cba";
class AirthingsWavePlus : public PollingComponent, public ble_client::BLEClientNode {
public:
AirthingsWavePlus();
void dump_config() override;
void update() override;
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
void set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; }
void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; }
void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; }
void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; }
void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
protected:
bool is_valid_radon_value_(uint16_t radon);
bool is_valid_voc_value_(uint16_t voc);
bool is_valid_co2_value_(uint16_t co2);
void read_sensors_(uint8_t *value, uint16_t value_len);
void request_read_values_();
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *radon_sensor_{nullptr};
sensor::Sensor *radon_long_term_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
sensor::Sensor *pressure_sensor_{nullptr};
sensor::Sensor *co2_sensor_{nullptr};
sensor::Sensor *tvoc_sensor_{nullptr};
uint16_t handle_;
esp32_ble_tracker::ESPBTUUID service_uuid_;
esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_;
struct WavePlusReadings {
uint8_t version;
uint8_t humidity;
uint8_t ambientLight;
uint8_t unused01;
uint16_t radon;
uint16_t radon_lt;
uint16_t temperature;
uint16_t pressure;
uint16_t co2;
uint16_t voc;
};
};
} // namespace airthings_wave_plus
} // namespace esphome
#endif // USE_ESP32

View File

@@ -1,116 +0,0 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, ble_client
from esphome.const import (
DEVICE_CLASS_CARBON_DIOXIDE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
STATE_CLASS_MEASUREMENT,
UNIT_PERCENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
ICON_RADIOACTIVE,
CONF_ID,
CONF_RADON,
CONF_RADON_LONG_TERM,
CONF_HUMIDITY,
CONF_TVOC,
CONF_CO2,
CONF_PRESSURE,
CONF_TEMPERATURE,
UNIT_BECQUEREL_PER_CUBIC_METER,
UNIT_PARTS_PER_MILLION,
UNIT_PARTS_PER_BILLION,
ICON_RADIATOR,
)
DEPENDENCIES = ["ble_client"]
airthings_wave_plus_ns = cg.esphome_ns.namespace("airthings_wave_plus")
AirthingsWavePlus = airthings_wave_plus_ns.class_(
"AirthingsWavePlus", cg.PollingComponent, ble_client.BLEClientNode
)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(AirthingsWavePlus),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=0,
),
cv.Optional(CONF_RADON): sensor.sensor_schema(
unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
icon=ICON_RADIOACTIVE,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_RADON_LONG_TERM): sensor.sensor_schema(
unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
icon=ICON_RADIOACTIVE,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement=UNIT_HECTOPASCAL,
accuracy_decimals=1,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CO2): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
accuracy_decimals=0,
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TVOC): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_BILLION,
icon=ICON_RADIATOR,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("5min"))
.extend(ble_client.BLE_CLIENT_SCHEMA),
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await ble_client.register_ble_node(var, config)
if CONF_HUMIDITY in config:
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
cg.add(var.set_humidity(sens))
if CONF_RADON in config:
sens = await sensor.new_sensor(config[CONF_RADON])
cg.add(var.set_radon(sens))
if CONF_RADON_LONG_TERM in config:
sens = await sensor.new_sensor(config[CONF_RADON_LONG_TERM])
cg.add(var.set_radon_long_term(sens))
if CONF_TEMPERATURE in config:
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
cg.add(var.set_temperature(sens))
if CONF_PRESSURE in config:
sens = await sensor.new_sensor(config[CONF_PRESSURE])
cg.add(var.set_pressure(sens))
if CONF_CO2 in config:
sens = await sensor.new_sensor(config[CONF_CO2])
cg.add(var.set_co2(sens))
if CONF_TVOC in config:
sens = await sensor.new_sensor(config[CONF_TVOC])
cg.add(var.set_tvoc(sens))

View File

@@ -5,7 +5,6 @@
#include "am2320.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace am2320 {
@@ -38,9 +37,9 @@ void AM2320Component::update() {
return;
}
float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0f;
float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0;
temperature = (data[4] & 0x80) ? -temperature : temperature;
float humidity = ((data[2] << 8) + data[3]) / 10.0f;
float humidity = ((data[2] << 8) + data[3]) / 10.0;
ESP_LOGD(TAG, "Got temperature=%.1f°C humidity=%.1f%%", temperature, humidity);
if (this->temperature_sensor_ != nullptr)
@@ -78,7 +77,7 @@ bool AM2320Component::read_bytes_(uint8_t a_register, uint8_t *data, uint8_t len
if (conversion > 0)
delay(conversion);
return this->read(data, len) == i2c::ERROR_OK;
return this->parent_->raw_receive(this->address_, data, len);
}
bool AM2320Component::read_data_(uint8_t *data) {

View File

@@ -1,116 +0,0 @@
#include "am43.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#ifdef USE_ESP32
namespace esphome {
namespace am43 {
static const char *const TAG = "am43";
void Am43::dump_config() {
ESP_LOGCONFIG(TAG, "AM43");
LOG_SENSOR(" ", "Battery", this->battery_);
LOG_SENSOR(" ", "Illuminance", this->illuminance_);
}
void Am43::setup() {
this->encoder_ = make_unique<Am43Encoder>();
this->decoder_ = make_unique<Am43Decoder>();
this->logged_in_ = false;
this->last_battery_update_ = 0;
this->current_sensor_ = 0;
}
void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
switch (event) {
case ESP_GATTC_OPEN_EVT: {
this->logged_in_ = false;
break;
}
case ESP_GATTC_DISCONNECT_EVT: {
this->logged_in_ = false;
this->node_state = espbt::ClientState::IDLE;
if (this->battery_ != nullptr)
this->battery_->publish_state(NAN);
if (this->illuminance_ != nullptr)
this->illuminance_->publish_state(NAN);
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID);
if (chr == nullptr) {
if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) {
ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.",
this->parent_->address_str().c_str());
} else {
ESP_LOGE(TAG, "[%s] No control service found at device, not an AM43..?",
this->parent_->address_str().c_str());
}
break;
}
this->char_handle_ = chr->handle;
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
this->node_state = espbt::ClientState::ESTABLISHED;
this->update();
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.handle != this->char_handle_)
break;
this->decoder_->decode(param->notify.value, param->notify.value_len);
if (this->battery_ != nullptr && this->decoder_->has_battery_level() &&
millis() - this->last_battery_update_ > 10000) {
this->battery_->publish_state(this->decoder_->battery_level_);
this->last_battery_update_ = millis();
}
if (this->illuminance_ != nullptr && this->decoder_->has_light_level()) {
this->illuminance_->publish_state(this->decoder_->light_level_);
}
if (this->current_sensor_ > 0) {
if (this->illuminance_ != nullptr) {
auto packet = this->encoder_->get_light_level_request();
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP,
ESP_GATT_AUTH_REQ_NONE);
if (status)
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(),
status);
}
this->current_sensor_ = 0;
}
break;
}
default:
break;
}
}
void Am43::update() {
if (this->node_state != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str());
return;
}
if (this->current_sensor_ == 0) {
if (this->battery_ != nullptr) {
auto packet = this->encoder_->get_battery_level_request();
auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status)
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
}
this->current_sensor_++;
}
}
} // namespace am43
} // namespace esphome
#endif

View File

@@ -1,45 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/am43/am43_base.h"
#ifdef USE_ESP32
#include <esp_gattc_api.h>
namespace esphome {
namespace am43 {
namespace espbt = esphome::esp32_ble_tracker;
class Am43 : public esphome::ble_client::BLEClientNode, public PollingComponent {
public:
void setup() override;
void update() override;
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_battery(sensor::Sensor *battery) { battery_ = battery; }
void set_illuminance(sensor::Sensor *illuminance) { illuminance_ = illuminance; }
protected:
uint16_t char_handle_;
std::unique_ptr<Am43Encoder> encoder_;
std::unique_ptr<Am43Decoder> decoder_;
bool logged_in_;
sensor::Sensor *battery_{nullptr};
sensor::Sensor *illuminance_{nullptr};
uint8_t current_sensor_;
// The AM43 often gets into a state where it spams loads of battery update
// notifications. Here we will limit to no more than every 10s.
uint8_t last_battery_update_;
};
} // namespace am43
} // namespace esphome
#endif

View File

@@ -1,144 +0,0 @@
#include "am43_base.h"
#include <cstring>
#include <cstdio>
namespace esphome {
namespace am43 {
const uint8_t START_PACKET[5] = {0x00, 0xff, 0x00, 0x00, 0x9a};
std::string pkt_to_hex(const uint8_t *data, uint16_t len) {
char buf[64];
memset(buf, 0, 64);
for (int i = 0; i < len; i++)
sprintf(&buf[i * 2], "%02x", data[i]);
std::string ret = buf;
return ret;
}
Am43Packet *Am43Encoder::get_battery_level_request() {
uint8_t data = 0x1;
return this->encode_(0xA2, &data, 1);
}
Am43Packet *Am43Encoder::get_light_level_request() {
uint8_t data = 0x1;
return this->encode_(0xAA, &data, 1);
}
Am43Packet *Am43Encoder::get_position_request() {
uint8_t data = 0x1;
return this->encode_(CMD_GET_POSITION, &data, 1);
}
Am43Packet *Am43Encoder::get_send_pin_request(uint16_t pin) {
uint8_t data[2];
data[0] = (pin & 0xFF00) >> 8;
data[1] = pin & 0xFF;
return this->encode_(CMD_SEND_PIN, data, 2);
}
Am43Packet *Am43Encoder::get_open_request() {
uint8_t data = 0xDD;
return this->encode_(CMD_SET_STATE, &data, 1);
}
Am43Packet *Am43Encoder::get_close_request() {
uint8_t data = 0xEE;
return this->encode_(CMD_SET_STATE, &data, 1);
}
Am43Packet *Am43Encoder::get_stop_request() {
uint8_t data = 0xCC;
return this->encode_(CMD_SET_STATE, &data, 1);
}
Am43Packet *Am43Encoder::get_set_position_request(uint8_t position) {
return this->encode_(CMD_SET_POSITION, &position, 1);
}
void Am43Encoder::checksum_() {
uint8_t checksum = 0;
int i = 0;
for (i = 0; i < this->packet_.length; i++)
checksum = checksum ^ this->packet_.data[i];
this->packet_.data[i] = checksum ^ 0xff;
this->packet_.length++;
}
Am43Packet *Am43Encoder::encode_(uint8_t command, uint8_t *data, uint8_t length) {
memcpy(this->packet_.data, START_PACKET, 5);
this->packet_.data[5] = command;
this->packet_.data[6] = length;
memcpy(&this->packet_.data[7], data, length);
this->packet_.length = length + 7;
this->checksum_();
ESP_LOGV("am43", "ENC(%d): 0x%s", packet_.length, pkt_to_hex(packet_.data, packet_.length).c_str());
return &this->packet_;
}
#define VERIFY_MIN_LENGTH(x) \
if (length < (x)) \
return;
void Am43Decoder::decode(const uint8_t *data, uint16_t length) {
this->has_battery_level_ = false;
this->has_light_level_ = false;
this->has_set_position_response_ = false;
this->has_set_state_response_ = false;
this->has_position_ = false;
this->has_pin_response_ = false;
ESP_LOGV("am43", "DEC(%d): 0x%s", length, pkt_to_hex(data, length).c_str());
if (length < 2 || data[0] != 0x9a)
return;
switch (data[1]) {
case CMD_GET_BATTERY_LEVEL: {
VERIFY_MIN_LENGTH(8);
this->battery_level_ = data[7];
this->has_battery_level_ = true;
break;
}
case CMD_GET_LIGHT_LEVEL: {
VERIFY_MIN_LENGTH(5);
this->light_level_ = 100 * ((float) data[4] / 9);
this->has_light_level_ = true;
break;
}
case CMD_GET_POSITION: {
VERIFY_MIN_LENGTH(6);
this->position_ = data[5];
this->has_position_ = true;
break;
}
case CMD_NOTIFY_POSITION: {
VERIFY_MIN_LENGTH(5);
this->position_ = data[4];
this->has_position_ = true;
break;
}
case CMD_SEND_PIN: {
VERIFY_MIN_LENGTH(4);
this->pin_ok_ = data[3] == RESPONSE_ACK;
this->has_pin_response_ = true;
break;
}
case CMD_SET_POSITION: {
VERIFY_MIN_LENGTH(4);
this->set_position_ok_ = data[3] == RESPONSE_ACK;
this->has_set_position_response_ = true;
break;
}
case CMD_SET_STATE: {
VERIFY_MIN_LENGTH(4);
this->set_state_ok_ = data[3] == RESPONSE_ACK;
this->has_set_state_response_ = true;
break;
}
default:
break;
}
};
} // namespace am43
} // namespace esphome

View File

@@ -1,78 +0,0 @@
#pragma once
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace am43 {
static const uint16_t AM43_SERVICE_UUID = 0xFE50;
static const uint16_t AM43_CHARACTERISTIC_UUID = 0xFE51;
//
// Tuya identifiers, only to detect and warn users as they are incompatible.
static const uint16_t AM43_TUYA_SERVICE_UUID = 0x1910;
static const uint16_t AM43_TUYA_CHARACTERISTIC_UUID = 0x2b11;
struct Am43Packet {
uint8_t length;
uint8_t data[24];
};
static const uint8_t CMD_GET_BATTERY_LEVEL = 0xA2;
static const uint8_t CMD_GET_LIGHT_LEVEL = 0xAA;
static const uint8_t CMD_GET_POSITION = 0xA7;
static const uint8_t CMD_SEND_PIN = 0x17;
static const uint8_t CMD_SET_STATE = 0x0A;
static const uint8_t CMD_SET_POSITION = 0x0D;
static const uint8_t CMD_NOTIFY_POSITION = 0xA1;
static const uint8_t RESPONSE_ACK = 0x5A;
static const uint8_t RESPONSE_NACK = 0xA5;
class Am43Encoder {
public:
Am43Packet *get_battery_level_request();
Am43Packet *get_light_level_request();
Am43Packet *get_position_request();
Am43Packet *get_send_pin_request(uint16_t pin);
Am43Packet *get_open_request();
Am43Packet *get_close_request();
Am43Packet *get_stop_request();
Am43Packet *get_set_position_request(uint8_t position);
protected:
void checksum_();
Am43Packet *encode_(uint8_t command, uint8_t *data, uint8_t length);
Am43Packet packet_;
};
class Am43Decoder {
public:
void decode(const uint8_t *data, uint16_t length);
bool has_battery_level() { return this->has_battery_level_; }
bool has_light_level() { return this->has_light_level_; }
bool has_set_position_response() { return this->has_set_position_response_; }
bool has_set_state_response() { return this->has_set_state_response_; }
bool has_position() { return this->has_position_; }
bool has_pin_response() { return this->has_pin_response_; }
union {
uint8_t position_;
uint8_t battery_level_;
float light_level_;
uint8_t set_position_ok_;
uint8_t set_state_ok_;
uint8_t pin_ok_;
};
protected:
bool has_battery_level_;
bool has_light_level_;
bool has_set_position_response_;
bool has_set_state_response_;
bool has_position_;
bool has_pin_response_;
};
} // namespace am43
} // namespace esphome

View File

@@ -1,36 +0,0 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import cover, ble_client
from esphome.const import CONF_ID, CONF_PIN
CODEOWNERS = ["@buxtronix"]
DEPENDENCIES = ["ble_client"]
AUTO_LOAD = ["am43"]
CONF_INVERT_POSITION = "invert_position"
am43_ns = cg.esphome_ns.namespace("am43")
Am43Component = am43_ns.class_(
"Am43Component", cover.Cover, ble_client.BLEClientNode, cg.Component
)
CONFIG_SCHEMA = (
cover.COVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(Am43Component),
cv.Optional(CONF_PIN, default=8888): cv.int_range(min=0, max=0xFFFF),
cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean,
}
)
.extend(ble_client.BLE_CLIENT_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_pin(config[CONF_PIN]))
cg.add(var.set_invert_position(config[CONF_INVERT_POSITION]))
yield cg.register_component(var, config)
yield cover.register_cover(var, config)
yield ble_client.register_ble_node(var, config)

View File

@@ -1,149 +0,0 @@
#include "am43_cover.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
namespace esphome {
namespace am43 {
static const char *const TAG = "am43_cover";
using namespace esphome::cover;
void Am43Component::dump_config() {
LOG_COVER("", "AM43 Cover", this);
ESP_LOGCONFIG(TAG, " Device Pin: %d", this->pin_);
ESP_LOGCONFIG(TAG, " Invert Position: %d", (int) this->invert_position_);
}
void Am43Component::setup() {
this->position = COVER_OPEN;
this->encoder_ = make_unique<Am43Encoder>();
this->decoder_ = make_unique<Am43Decoder>();
this->logged_in_ = false;
}
void Am43Component::loop() {
if (this->node_state == espbt::ClientState::ESTABLISHED && !this->logged_in_) {
auto packet = this->encoder_->get_send_pin_request(this->pin_);
auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
ESP_LOGI(TAG, "[%s] Logging into AM43", this->get_name().c_str());
if (status)
ESP_LOGW(TAG, "[%s] Error writing set_pin to device, error = %d", this->get_name().c_str(), status);
else
this->logged_in_ = true;
}
}
CoverTraits Am43Component::get_traits() {
auto traits = CoverTraits();
traits.set_supports_position(true);
traits.set_supports_tilt(false);
traits.set_is_assumed_state(false);
return traits;
}
void Am43Component::control(const CoverCall &call) {
if (this->node_state != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "[%s] Cannot send cover control, not connected", this->get_name().c_str());
return;
}
if (call.get_stop()) {
auto packet = this->encoder_->get_stop_request();
auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status)
ESP_LOGW(TAG, "[%s] Error writing stop command to device, error = %d", this->get_name().c_str(), status);
}
if (call.get_position().has_value()) {
auto pos = *call.get_position();
if (this->invert_position_)
pos = 1 - pos;
auto packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100));
auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status)
ESP_LOGW(TAG, "[%s] Error writing set_position command to device, error = %d", this->get_name().c_str(), status);
}
}
void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) {
switch (event) {
case ESP_GATTC_DISCONNECT_EVT: {
this->logged_in_ = false;
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID);
if (chr == nullptr) {
if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) {
ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.", this->get_name().c_str());
} else {
ESP_LOGE(TAG, "[%s] No control service found at device, not an AM43..?", this->get_name().c_str());
}
break;
}
this->char_handle_ = chr->handle;
auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle);
if (status) {
ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status);
}
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
this->node_state = espbt::ClientState::ESTABLISHED;
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.handle != this->char_handle_)
break;
this->decoder_->decode(param->notify.value, param->notify.value_len);
if (this->decoder_->has_position()) {
this->position = ((float) this->decoder_->position_ / 100.0);
if (!this->invert_position_)
this->position = 1 - this->position;
if (this->position > 0.97)
this->position = 1.0;
if (this->position < 0.02)
this->position = 0.0;
this->publish_state();
}
if (this->decoder_->has_pin_response()) {
if (this->decoder_->pin_ok_) {
ESP_LOGI(TAG, "[%s] AM43 pin accepted.", this->get_name().c_str());
auto packet = this->encoder_->get_position_request();
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP,
ESP_GATT_AUTH_REQ_NONE);
if (status)
ESP_LOGW(TAG, "[%s] Error writing set_position to device, error = %d", this->get_name().c_str(), status);
} else {
ESP_LOGW(TAG, "[%s] AM43 pin rejected!", this->get_name().c_str());
}
}
if (this->decoder_->has_set_position_response() && !this->decoder_->set_position_ok_)
ESP_LOGW(TAG, "[%s] Got nack after sending set_position. Bad pin?", this->get_name().c_str());
if (this->decoder_->has_set_state_response() && !this->decoder_->set_state_ok_)
ESP_LOGW(TAG, "[%s] Got nack after sending set_state. Bad pin?", this->get_name().c_str());
break;
}
default:
break;
}
}
} // namespace am43
} // namespace esphome
#endif

View File

@@ -1,45 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/cover/cover.h"
#include "esphome/components/am43/am43_base.h"
#ifdef USE_ESP32
#include <esp_gattc_api.h>
namespace esphome {
namespace am43 {
namespace espbt = esphome::esp32_ble_tracker;
class Am43Component : public cover::Cover, public esphome::ble_client::BLEClientNode, public Component {
public:
void setup() override;
void loop() override;
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
cover::CoverTraits get_traits() override;
void set_pin(uint16_t pin) { this->pin_ = pin; }
void set_invert_position(bool invert_position) { this->invert_position_ = invert_position; }
protected:
void control(const cover::CoverCall &call) override;
uint16_t char_handle_;
uint16_t pin_;
bool invert_position_;
std::unique_ptr<Am43Encoder> encoder_;
std::unique_ptr<Am43Decoder> decoder_;
bool logged_in_;
float position_;
};
} // namespace am43
} // namespace esphome
#endif

View File

@@ -1,52 +0,0 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, ble_client
from esphome.const import (
CONF_ID,
CONF_BATTERY_LEVEL,
DEVICE_CLASS_BATTERY,
ENTITY_CATEGORY_DIAGNOSTIC,
CONF_ILLUMINANCE,
ICON_BRIGHTNESS_5,
UNIT_PERCENT,
)
CODEOWNERS = ["@buxtronix"]
am43_ns = cg.esphome_ns.namespace("am43")
Am43 = am43_ns.class_("Am43", ble_client.BLEClientNode, cg.PollingComponent)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(Am43),
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
device_class=DEVICE_CLASS_BATTERY,
accuracy_decimals=0,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
icon=ICON_BRIGHTNESS_5,
accuracy_decimals=0,
),
}
)
.extend(ble_client.BLE_CLIENT_SCHEMA)
.extend(cv.polling_component_schema("120s"))
)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield ble_client.register_ble_node(var, config)
if CONF_BATTERY_LEVEL in config:
sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL])
cg.add(var.set_battery(sens))
if CONF_ILLUMINANCE in config:
sens = yield sensor.new_sensor(config[CONF_ILLUMINANCE])
cg.add(var.set_illuminance(sens))

View File

@@ -5,7 +5,7 @@ from esphome.components import display, font
import esphome.components.image as espImage
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import CONF_FILE, CONF_ID, CONF_RAW_DATA_ID, CONF_RESIZE, CONF_TYPE
from esphome.const import CONF_FILE, CONF_ID, CONF_TYPE, CONF_RESIZE
from esphome.core import CORE, HexInt
_LOGGER = logging.getLogger(__name__)
@@ -15,6 +15,8 @@ MULTI_CONF = True
Animation_ = display.display_ns.class_("Animation")
CONF_RAW_DATA_ID = "raw_data_id"
ANIMATION_SCHEMA = cv.Schema(
{
cv.Required(CONF_ID): cv.declare_id(Animation_),
@@ -44,9 +46,8 @@ async def to_code(config):
width, height = image.size
frames = image.n_frames
if CONF_RESIZE in config:
new_width_max, new_height_max = config[CONF_RESIZE]
ratio = min(new_width_max / width, new_height_max / height)
width, height = int(width * ratio), int(height * ratio)
image.thumbnail(config[CONF_RESIZE])
width, height = image.size
else:
if width > 500 or height > 500:
_LOGGER.warning(
@@ -60,13 +61,7 @@ async def to_code(config):
for frameIndex in range(frames):
image.seek(frameIndex)
frame = image.convert("L", dither=Image.NONE)
if CONF_RESIZE in config:
frame = frame.resize([width, height])
pixels = list(frame.getdata())
if len(pixels) != height * width:
raise core.EsphomeError(
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
)
for pix in pixels:
data[pos] = pix
pos += 1
@@ -76,16 +71,8 @@ async def to_code(config):
pos = 0
for frameIndex in range(frames):
image.seek(frameIndex)
if CONF_RESIZE in config:
image.thumbnail(config[CONF_RESIZE])
frame = image.convert("RGB")
if CONF_RESIZE in config:
frame = frame.resize([width, height])
pixels = list(frame.getdata())
if len(pixels) != height * width:
raise core.EsphomeError(
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
)
for pix in pixels:
data[pos] = pix[0]
pos += 1
@@ -100,8 +87,6 @@ async def to_code(config):
for frameIndex in range(frames):
image.seek(frameIndex)
frame = image.convert("1", dither=Image.NONE)
if CONF_RESIZE in config:
frame = frame.resize([width, height])
for y in range(height):
for x in range(width):
if frame.getpixel((x, y)):

View File

@@ -1,19 +1,19 @@
#include "anova.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace anova {
static const char *const TAG = "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_ = make_unique<AnovaCodec>();
this->codec_ = new AnovaCodec();
this->current_request_ = 0;
}
@@ -72,7 +72,7 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
this->node_state = espbt::ClientState::ESTABLISHED;
this->node_state = espbt::ClientState::Established;
this->current_request_ = 0;
this->update();
break;
@@ -129,13 +129,13 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
void Anova::set_unit_of_measurement(const char *unit) { this->fahrenheit_ = !strncmp(unit, "f", 1); }
void Anova::update() {
if (this->node_state != espbt::ClientState::ESTABLISHED)
if (this->node_state != espbt::ClientState::Established)
return;
if (this->current_request_ < 2) {
auto pkt = this->codec_->get_read_device_status_request();
if (this->current_request_ == 0)
this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c');
auto pkt = this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c');
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status)

View File

@@ -6,7 +6,7 @@
#include "esphome/components/climate/climate.h"
#include "anova_base.h"
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
#include <esp_gattc_api.h>
@@ -27,10 +27,10 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode
esp_ble_gattc_cb_param_t *param) override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
climate::ClimateTraits traits() override {
climate::ClimateTraits traits() {
auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(true);
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::ClimateMode::CLIMATE_MODE_HEAT});
traits.set_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);
@@ -39,7 +39,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode
void set_unit_of_measurement(const char *);
protected:
std::unique_ptr<AnovaCodec> codec_;
AnovaCodec *codec_;
void control(const climate::ClimateCall &call) override;
uint16_t char_handle_;
uint8_t current_request_;

View File

@@ -1,6 +1,4 @@
#include "anova_base.h"
#include <cstdio>
#include <cstring>
namespace esphome {
namespace anova {
@@ -73,46 +71,51 @@ AnovaPacket *AnovaCodec::get_stop_request() {
}
void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
char buf[32];
memset(buf, 0, sizeof(buf));
strncpy(buf, (char *) data, std::min<uint16_t>(length, sizeof(buf) - 1));
memset(this->buf_, 0, 32);
strncpy(this->buf_, (char *) data, length);
this->has_target_temp_ = this->has_current_temp_ = this->has_unit_ = this->has_running_ = false;
switch (this->current_query_) {
case READ_DEVICE_STATUS: {
if (!strncmp(buf, "stopped", 7)) {
if (!strncmp(this->buf_, "stopped", 7)) {
this->has_running_ = true;
this->running_ = false;
}
if (!strncmp(buf, "running", 7)) {
if (!strncmp(this->buf_, "running", 7)) {
this->has_running_ = true;
this->running_ = true;
}
break;
}
case START: {
if (!strncmp(buf, "start", 5)) {
if (!strncmp(this->buf_, "start", 5)) {
this->has_running_ = true;
this->running_ = true;
}
break;
}
case STOP: {
if (!strncmp(buf, "stop", 4)) {
if (!strncmp(this->buf_, "stop", 4)) {
this->has_running_ = true;
this->running_ = false;
}
break;
}
case READ_TARGET_TEMPERATURE:
case READ_TARGET_TEMPERATURE: {
this->target_temp_ = strtof(this->buf_, nullptr);
if (this->fahrenheit_)
this->target_temp_ = ftoc(this->target_temp_);
this->has_target_temp_ = true;
break;
}
case SET_TARGET_TEMPERATURE: {
this->target_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f);
this->target_temp_ = strtof(this->buf_, nullptr);
if (this->fahrenheit_)
this->target_temp_ = ftoc(this->target_temp_);
this->has_target_temp_ = true;
break;
}
case READ_CURRENT_TEMPERATURE: {
this->current_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f);
this->current_temp_ = strtof(this->buf_, nullptr);
if (this->fahrenheit_)
this->current_temp_ = ftoc(this->current_temp_);
this->has_current_temp_ = true;
@@ -120,8 +123,8 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
}
case SET_UNIT:
case READ_UNIT: {
this->unit_ = buf[0];
this->fahrenheit_ = buf[0] == 'f';
this->unit_ = this->buf_[0];
this->fahrenheit_ = this->buf_[0] == 'f';
this->has_unit_ = true;
break;
}

View File

@@ -70,6 +70,7 @@ class AnovaCodec {
bool has_current_temp_;
bool has_unit_;
bool has_running_;
char buf_[32];
bool fahrenheit_;
CurrentQuery current_query_;

View File

@@ -1,6 +1,5 @@
#include "apds9960.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace apds9960 {

View File

@@ -121,7 +121,6 @@ async def to_code(config):
decoded = base64.b64decode(conf[CONF_KEY])
cg.add(var.set_noise_psk(list(decoded)))
cg.add_define("USE_API_NOISE")
cg.add_library("esphome/noise-c", "0.1.4")
else:
cg.add_define("USE_API_PLAINTEXT")

View File

@@ -40,7 +40,6 @@ service APIConnection {
rpc climate_command (ClimateCommandRequest) returns (void) {}
rpc number_command (NumberCommandRequest) returns (void) {}
rpc select_command (SelectCommandRequest) returns (void) {}
rpc button_command (ButtonCommandRequest) returns (void) {}
}
@@ -183,8 +182,6 @@ message DeviceInfoResponse {
// The esphome project details if set
string project_name = 8;
string project_version = 9;
uint32 webserver_port = 10;
}
message ListEntitiesRequest {
@@ -204,14 +201,6 @@ message SubscribeStatesRequest {
// Empty
}
// ==================== COMMON =====================
enum EntityCategory {
ENTITY_CATEGORY_NONE = 0;
ENTITY_CATEGORY_CONFIG = 1;
ENTITY_CATEGORY_DIAGNOSTIC = 2;
}
// ==================== BINARY SENSOR ====================
message ListEntitiesBinarySensorResponse {
option (id) = 12;
@@ -226,8 +215,6 @@ message ListEntitiesBinarySensorResponse {
string device_class = 5;
bool is_status_binary_sensor = 6;
bool disabled_by_default = 7;
string icon = 8;
EntityCategory entity_category = 9;
}
message BinarySensorStateResponse {
option (id) = 21;
@@ -258,8 +245,6 @@ message ListEntitiesCoverResponse {
bool supports_tilt = 7;
string device_class = 8;
bool disabled_by_default = 9;
string icon = 10;
EntityCategory entity_category = 11;
}
enum LegacyCoverState {
@@ -328,8 +313,6 @@ message ListEntitiesFanResponse {
bool supports_direction = 7;
int32 supported_speed_count = 8;
bool disabled_by_default = 9;
string icon = 10;
EntityCategory entity_category = 11;
}
enum FanSpeed {
FAN_SPEED_LOW = 0;
@@ -405,8 +388,6 @@ message ListEntitiesLightResponse {
float max_mireds = 10;
repeated string effects = 11;
bool disabled_by_default = 13;
string icon = 14;
EntityCategory entity_category = 15;
}
message LightStateResponse {
option (id) = 24;
@@ -467,7 +448,6 @@ message LightCommandRequest {
enum SensorStateClass {
STATE_CLASS_NONE = 0;
STATE_CLASS_MEASUREMENT = 1;
STATE_CLASS_TOTAL_INCREASING = 2;
}
enum SensorLastResetType {
@@ -492,10 +472,8 @@ message ListEntitiesSensorResponse {
bool force_update = 8;
string device_class = 9;
SensorStateClass state_class = 10;
// Last reset type removed in 2021.9.0
SensorLastResetType legacy_last_reset_type = 11;
SensorLastResetType last_reset_type = 11;
bool disabled_by_default = 12;
EntityCategory entity_category = 13;
}
message SensorStateResponse {
option (id) = 25;
@@ -524,7 +502,6 @@ message ListEntitiesSwitchResponse {
string icon = 5;
bool assumed_state = 6;
bool disabled_by_default = 7;
EntityCategory entity_category = 8;
}
message SwitchStateResponse {
option (id) = 26;
@@ -558,7 +535,6 @@ message ListEntitiesTextSensorResponse {
string icon = 5;
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
}
message TextSensorStateResponse {
option (id) = 27;
@@ -579,10 +555,9 @@ enum LogLevel {
LOG_LEVEL_ERROR = 1;
LOG_LEVEL_WARN = 2;
LOG_LEVEL_INFO = 3;
LOG_LEVEL_CONFIG = 4;
LOG_LEVEL_DEBUG = 5;
LOG_LEVEL_VERBOSE = 6;
LOG_LEVEL_VERY_VERBOSE = 7;
LOG_LEVEL_DEBUG = 4;
LOG_LEVEL_VERBOSE = 5;
LOG_LEVEL_VERY_VERBOSE = 6;
}
message SubscribeLogsRequest {
option (id) = 28;
@@ -597,6 +572,7 @@ message SubscribeLogsResponse {
option (no_delay) = false;
LogLevel level = 1;
string tag = 2;
string message = 3;
bool send_failed = 4;
}
@@ -719,8 +695,6 @@ message ListEntitiesCameraResponse {
string name = 3;
string unique_id = 4;
bool disabled_by_default = 5;
string icon = 6;
EntityCategory entity_category = 7;
}
message CameraImageResponse {
@@ -814,8 +788,6 @@ message ListEntitiesClimateResponse {
repeated ClimatePreset supported_presets = 16;
repeated string supported_custom_presets = 17;
bool disabled_by_default = 18;
string icon = 19;
EntityCategory entity_category = 20;
}
message ClimateStateResponse {
option (id) = 47;
@@ -869,11 +841,6 @@ message ClimateCommandRequest {
}
// ==================== NUMBER ====================
enum NumberMode {
NUMBER_MODE_AUTO = 0;
NUMBER_MODE_BOX = 1;
NUMBER_MODE_SLIDER = 2;
}
message ListEntitiesNumberResponse {
option (id) = 49;
option (source) = SOURCE_SERVER;
@@ -889,9 +856,6 @@ message ListEntitiesNumberResponse {
float max_value = 7;
float step = 8;
bool disabled_by_default = 9;
EntityCategory entity_category = 10;
string unit_of_measurement = 11;
NumberMode mode = 12;
}
message NumberStateResponse {
option (id) = 50;
@@ -929,7 +893,6 @@ message ListEntitiesSelectResponse {
string icon = 5;
repeated string options = 6;
bool disabled_by_default = 7;
EntityCategory entity_category = 8;
}
message SelectStateResponse {
option (id) = 53;
@@ -952,28 +915,3 @@ message SelectCommandRequest {
fixed32 key = 1;
string state = 2;
}
// ==================== BUTTON ====================
message ListEntitiesButtonResponse {
option (id) = 61;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BUTTON";
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
string device_class = 8;
}
message ButtonCommandRequest {
option (id) = 62;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BUTTON";
option (no_delay) = true;
fixed32 key = 1;
}

View File

@@ -1,10 +1,8 @@
#include "api_connection.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/log.h"
#include "esphome/components/network/util.h"
#include "esphome/core/util.h"
#include "esphome/core/version.h"
#include "esphome/core/hal.h"
#include <cerrno>
#include <errno.h>
#ifdef USE_DEEP_SLEEP
#include "esphome/components/deep_sleep/deep_sleep_component.h"
@@ -22,43 +20,49 @@ namespace api {
static const char *const TAG = "api.connection";
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
: parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) {
: parent_(parent),
initial_state_iterator_(parent, this),
list_entities_iterator_(parent, this) {
this->proto_write_buffer_.reserve(64);
#if defined(USE_API_PLAINTEXT)
helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
#elif defined(USE_API_NOISE)
#ifdef USE_API_NOISE
helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())};
#elif defined(USE_API_PLAINTEXT)
helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
#else
#error "No frame helper defined"
#error "No api frame helper enabled"
#endif
}
void APIConnection::start() {
this->last_traffic_ = millis();
APIError err = helper_->init();
if (err != APIError::OK) {
on_fatal_error();
ESP_LOGW(TAG, "%s: Helper init failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
ESP_LOGW(TAG, "Helper init failed: %d errno=%d", (int) err, errno);
remove_ = true;
return;
}
client_info_ = helper_->getpeername();
helper_->set_log_info(client_info_);
}
void APIConnection::force_disconnect_client() {
this->helper_->close();
this->remove_ = true;
}
void APIConnection::loop() {
if (this->remove_)
return;
if (!network::is_connected()) {
if (!network_is_connected()) {
// when network is disconnected force disconnect immediately
// don't wait for timeout
this->on_fatal_error();
ESP_LOGW(TAG, "%s: Network unavailable, disconnecting", client_info_.c_str());
return;
}
if (this->next_close_) {
// requested a disconnect
this->helper_->close();
this->remove_ = true;
return;
@@ -67,7 +71,7 @@ void APIConnection::loop() {
APIError err = helper_->loop();
if (err != APIError::OK) {
on_fatal_error();
ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
ESP_LOGW(TAG, "%s: Socket operation failed: %d", client_info_.c_str(), (int) err);
return;
}
ReadPacketBuffer buffer;
@@ -76,18 +80,16 @@ void APIConnection::loop() {
// pass
} else if (err != APIError::OK) {
on_fatal_error();
if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) {
ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str());
} else if (err == APIError::CONNECTION_CLOSED) {
ESP_LOGW(TAG, "%s: Connection closed", client_info_.c_str());
} else {
ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
}
ESP_LOGW(TAG, "%s: Reading failed: %d", client_info_.c_str(), (int) err);
return;
} else {
this->last_traffic_ = millis();
// read a packet
this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
this->read_message(
buffer.data_len,
buffer.type,
&buffer.container[buffer.data_offset]
);
if (this->remove_)
return;
}
@@ -100,8 +102,8 @@ void APIConnection::loop() {
if (this->sent_ping_) {
// Disconnect if not responded within 2.5*keepalive
if (now - this->last_traffic_ > (keepalive * 5) / 2) {
on_fatal_error();
ESP_LOGW(TAG, "%s didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str());
this->force_disconnect_client();
ESP_LOGW(TAG, "'%s' didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str());
}
} else if (now - this->last_traffic_ > keepalive) {
this->sent_ping_ = true;
@@ -129,38 +131,10 @@ void APIConnection::loop() {
}
}
#endif
if (state_subs_at_ != -1) {
const auto &subs = this->parent_->get_state_subs();
if (state_subs_at_ >= (int) subs.size()) {
state_subs_at_ = -1;
} else {
auto &it = subs[state_subs_at_];
SubscribeHomeAssistantStateResponse resp;
resp.entity_id = it.entity_id;
resp.attribute = it.attribute.value();
if (this->send_subscribe_home_assistant_state_response(resp)) {
state_subs_at_++;
}
}
}
}
std::string get_default_unique_id(const std::string &component_type, EntityBase *entity) {
return App.get_name() + component_type + entity->get_object_id();
}
DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) {
// remote initiated disconnect_client
// don't close yet, we still need to send the disconnect response
// close will happen on next loop
ESP_LOGD(TAG, "%s requested disconnected", client_info_.c_str());
this->next_close_ = true;
DisconnectResponse resp;
return resp;
}
void APIConnection::on_disconnect_response(const DisconnectResponse &value) {
// pass
std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) {
return App.get_name() + component_type + nameable->get_object_id();
}
#ifdef USE_BINARY_SENSOR
@@ -183,8 +157,6 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_
msg.device_class = binary_sensor->get_device_class();
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
msg.disabled_by_default = binary_sensor->is_disabled_by_default();
msg.icon = binary_sensor->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(binary_sensor->get_entity_category());
return this->send_list_entities_binary_sensor_response(msg);
}
#endif
@@ -217,8 +189,6 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
msg.supports_tilt = traits.get_supports_tilt();
msg.device_class = cover->get_device_class();
msg.disabled_by_default = cover->is_disabled_by_default();
msg.icon = cover->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(cover->get_entity_category());
return this->send_list_entities_cover_response(msg);
}
void APIConnection::cover_command(const CoverCommandRequest &msg) {
@@ -251,9 +221,6 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) {
#endif
#ifdef USE_FAN
// Shut-up about usage of deprecated speed_level_to_enum/speed_enum_to_level functions for a bit.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
bool APIConnection::send_fan_state(fan::FanState *fan) {
if (!this->state_subscription_)
return false;
@@ -284,8 +251,6 @@ bool APIConnection::send_fan_info(fan::FanState *fan) {
msg.supports_direction = traits.supports_direction();
msg.supported_speed_count = traits.supported_speed_count();
msg.disabled_by_default = fan->is_disabled_by_default();
msg.icon = fan->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(fan->get_entity_category());
return this->send_list_entities_fan_response(msg);
}
void APIConnection::fan_command(const FanCommandRequest &msg) {
@@ -310,7 +275,6 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
call.set_direction(static_cast<fan::FanDirection>(msg.direction));
call.perform();
}
#pragma GCC diagnostic pop
#endif
#ifdef USE_LIGHT
@@ -326,15 +290,22 @@ bool APIConnection::send_light_state(light::LightState *light) {
resp.key = light->get_object_id_hash();
resp.state = values.is_on();
resp.color_mode = static_cast<enums::ColorMode>(color_mode);
resp.brightness = values.get_brightness();
resp.color_brightness = values.get_color_brightness();
resp.red = values.get_red();
resp.green = values.get_green();
resp.blue = values.get_blue();
resp.white = values.get_white();
resp.color_temperature = values.get_color_temperature();
resp.cold_white = values.get_cold_white();
resp.warm_white = values.get_warm_white();
if (color_mode & light::ColorCapability::BRIGHTNESS)
resp.brightness = values.get_brightness();
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 (color_mode & light::ColorCapability::WHITE)
resp.white = values.get_white();
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);
@@ -348,8 +319,6 @@ bool APIConnection::send_light_info(light::LightState *light) {
msg.unique_id = get_default_unique_id("light", light);
msg.disabled_by_default = light->is_disabled_by_default();
msg.icon = light->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(light->get_entity_category());
for (auto mode : traits.get_supported_color_modes())
msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode));
@@ -434,9 +403,10 @@ 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->get_state_class());
msg.state_class = static_cast<enums::SensorStateClass>(sensor->state_class);
msg.last_reset_type = static_cast<enums::SensorLastResetType>(sensor->last_reset_type);
msg.disabled_by_default = sensor->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(sensor->get_entity_category());
return this->send_list_entities_sensor_response(msg);
}
#endif
@@ -460,7 +430,6 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
msg.icon = a_switch->get_icon();
msg.assumed_state = a_switch->assumed_state();
msg.disabled_by_default = a_switch->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(a_switch->get_entity_category());
return this->send_list_entities_switch_response(msg);
}
void APIConnection::switch_command(const SwitchCommandRequest &msg) {
@@ -496,7 +465,6 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor)
msg.unique_id = get_default_unique_id("text_sensor", text_sensor);
msg.icon = text_sensor->get_icon();
msg.disabled_by_default = text_sensor->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(text_sensor->get_entity_category());
return this->send_list_entities_text_sensor_response(msg);
}
#endif
@@ -542,8 +510,6 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
msg.unique_id = get_default_unique_id("climate", climate);
msg.disabled_by_default = climate->is_disabled_by_default();
msg.icon = climate->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(climate->get_entity_category());
msg.supports_current_temperature = traits.get_supports_current_temperature();
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
@@ -616,11 +582,8 @@ bool APIConnection::send_number_info(number::Number *number) {
msg.object_id = number->get_object_id();
msg.name = number->get_name();
msg.unique_id = get_default_unique_id("number", number);
msg.icon = number->get_icon();
msg.icon = number->traits.get_icon();
msg.disabled_by_default = number->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(number->get_entity_category());
msg.unit_of_measurement = number->traits.get_unit_of_measurement();
msg.mode = static_cast<enums::NumberMode>(number->traits.get_mode());
msg.min_value = number->traits.get_min_value();
msg.max_value = number->traits.get_max_value();
@@ -656,9 +619,8 @@ bool APIConnection::send_select_info(select::Select *select) {
msg.object_id = select->get_object_id();
msg.name = select->get_name();
msg.unique_id = get_default_unique_id("select", select);
msg.icon = select->get_icon();
msg.icon = select->traits.get_icon();
msg.disabled_by_default = select->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(select->get_entity_category());
for (const auto &option : select->traits.get_options())
msg.options.push_back(option);
@@ -676,35 +638,13 @@ void APIConnection::select_command(const SelectCommandRequest &msg) {
}
#endif
#ifdef USE_BUTTON
bool APIConnection::send_button_info(button::Button *button) {
ListEntitiesButtonResponse msg;
msg.key = button->get_object_id_hash();
msg.object_id = button->get_object_id();
msg.name = button->get_name();
msg.unique_id = get_default_unique_id("button", button);
msg.icon = button->get_icon();
msg.disabled_by_default = button->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(button->get_entity_category());
msg.device_class = button->get_device_class();
return this->send_list_entities_button_response(msg);
}
void APIConnection::button_command(const ButtonCommandRequest &msg) {
button::Button *button = App.get_button_by_key(msg.key);
if (button == nullptr)
return;
button->press();
}
#endif
#ifdef USE_ESP32_CAMERA
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
if (!this->state_subscription_)
return;
if (this->image_reader_.available())
return;
this->image_reader_.set_image(std::move(image));
this->image_reader_.set_image(image);
}
bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
ListEntitiesCameraResponse msg;
@@ -713,8 +653,6 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
msg.name = camera->get_name();
msg.unique_id = get_default_unique_id("camera", camera);
msg.disabled_by_default = camera->is_disabled_by_default();
msg.icon = camera->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(camera->get_entity_category());
return this->send_list_entities_camera_response(msg);
}
void APIConnection::camera_image(const CameraImageRequest &msg) {
@@ -743,10 +681,20 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin
auto buffer = this->create_buffer();
// LogLevel level = 1;
buffer.encode_uint32(1, static_cast<uint32_t>(level));
// string tag = 2;
// buffer.encode_string(2, tag, strlen(tag));
// string message = 3;
buffer.encode_string(3, line, strlen(line));
// SubscribeLogsResponse - 29
return this->send_buffer(buffer, 29);
bool success = this->send_buffer(buffer, 29);
if (!success) {
buffer = this->create_buffer();
// bool send_failed = 4;
buffer.encode_bool(4, true);
return this->send_buffer(buffer, 29);
} else {
return true;
}
}
HelloResponse APIConnection::hello(const HelloRequest &msg) {
@@ -768,7 +716,7 @@ ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
// bool invalid_password = 1;
resp.invalid_password = !correct;
if (correct) {
ESP_LOGD(TAG, "%s: Connected successfully", this->client_info_.c_str());
ESP_LOGD(TAG, "Client '%s' connected successfully!", this->client_info_.c_str());
this->connection_state_ = ConnectionState::AUTHENTICATED;
#ifdef USE_HOMEASSISTANT_TIME
@@ -786,16 +734,15 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
resp.mac_address = get_mac_address_pretty();
resp.esphome_version = ESPHOME_VERSION;
resp.compilation_time = App.get_compilation_time();
resp.model = ESPHOME_BOARD;
#ifdef ARDUINO_BOARD
resp.model = ARDUINO_BOARD;
#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
#ifdef USE_WEBSERVER
resp.webserver_port = WEBSERVER_PORT;
#endif
return resp;
}
@@ -817,39 +764,32 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
}
}
void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) {
state_subs_at_ = 0;
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;
}
}
}
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) {
if (this->remove_)
return false;
if (!this->helper_->can_write_without_blocking()) {
delay(0);
APIError err = helper_->loop();
if (err != APIError::OK) {
on_fatal_error();
ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
return false;
}
if (!this->helper_->can_write_without_blocking()) {
// SubscribeLogsResponse
if (message_type != 29) {
ESP_LOGV(TAG, "Cannot send message because of TCP buffer space");
}
delay(0);
return false;
}
}
if (!this->helper_->can_write_without_blocking())
return false;
APIError err = this->helper_->write_packet(message_type, buffer.get_buffer()->data(), buffer.get_buffer()->size());
APIError err = this->helper_->write_packet(
message_type,
buffer.get_buffer()->data(),
buffer.get_buffer()->size()
);
if (err == APIError::WOULD_BLOCK)
return false;
if (err != APIError::OK) {
on_fatal_error();
if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) {
ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str());
} else {
ESP_LOGW(TAG, "%s: Packet write failed %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
}
ESP_LOGW(TAG, "%s: Packet write failed %d errno=%d", client_info_.c_str(), (int) err, errno);
return false;
}
this->last_traffic_ = millis();
@@ -857,13 +797,14 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type)
}
void APIConnection::on_unauthenticated_access() {
this->on_fatal_error();
ESP_LOGD(TAG, "%s: tried to access without authentication.", this->client_info_.c_str());
ESP_LOGD(TAG, "'%s' tried to access without authentication.", this->client_info_.c_str());
}
void APIConnection::on_no_setup_connection() {
this->on_fatal_error();
ESP_LOGD(TAG, "%s: tried to access without full connection.", this->client_info_.c_str());
ESP_LOGD(TAG, "'%s' tried to access without full connection.", this->client_info_.c_str());
}
void APIConnection::on_fatal_error() {
ESP_LOGV(TAG, "Error: Disconnecting %s", this->client_info_.c_str());
this->helper_->close();
this->remove_ = true;
}

View File

@@ -16,6 +16,7 @@ class APIConnection : public APIServerConnection {
virtual ~APIConnection() = default;
void start();
void force_disconnect_client();
void loop();
bool send_list_info_done() {
@@ -73,10 +74,6 @@ class APIConnection : public APIServerConnection {
bool send_select_state(select::Select *select, std::string state);
bool send_select_info(select::Select *select);
void select_command(const SelectCommandRequest &msg) override;
#endif
#ifdef USE_BUTTON
bool send_button_info(button::Button *button);
void button_command(const ButtonCommandRequest &msg) override;
#endif
bool send_log_message(int level, const char *tag, const char *line);
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
@@ -91,7 +88,10 @@ class APIConnection : public APIServerConnection {
}
#endif
void on_disconnect_response(const DisconnectResponse &value) override;
void on_disconnect_response(const DisconnectResponse &value) override {
this->helper_->close();
this->remove_ = true;
}
void on_ping_response(const PingResponse &value) override {
// we initiated ping
this->sent_ping_ = false;
@@ -102,7 +102,14 @@ class APIConnection : public APIServerConnection {
#endif
HelloResponse hello(const HelloRequest &msg) override;
ConnectResponse connect(const ConnectRequest &msg) override;
DisconnectResponse disconnect(const DisconnectRequest &msg) override;
DisconnectResponse disconnect(const DisconnectRequest &msg) override {
// remote initiated disconnect_client
// don't close yet, we still need to send the disconnect response
// close will happen on next loop
this->next_close_ = true;
DisconnectResponse resp;
return resp;
}
PingResponse ping(const PingRequest &msg) override { return {}; }
DeviceInfoResponse device_info(const DeviceInfoRequest &msg) override;
void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); }
@@ -170,7 +177,6 @@ class APIConnection : public APIServerConnection {
APIServer *parent_;
InitialStateIterator initial_state_iterator_;
ListEntitiesIterator list_entities_iterator_;
int state_subs_at_ = -1;
};
} // namespace api

View File

@@ -3,14 +3,13 @@
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "proto.h"
#include <cstring>
namespace esphome {
namespace api {
static const char *const TAG = "api.socket";
/// Is the given return value (from write syscalls) a wouldblock error?
/// Is the given return value (from read/write syscalls) a wouldblock error?
bool is_would_block(ssize_t ret) {
if (ret == -1) {
return errno == EWOULDBLOCK || errno == EAGAIN;
@@ -18,61 +17,7 @@ bool is_would_block(ssize_t ret) {
return ret == 0;
}
const char *api_error_to_str(APIError err) {
// not using switch to ensure compiler doesn't try to build a big table out of it
if (err == APIError::OK) {
return "OK";
} else if (err == APIError::WOULD_BLOCK) {
return "WOULD_BLOCK";
} else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) {
return "BAD_HANDSHAKE_PACKET_LEN";
} else if (err == APIError::BAD_INDICATOR) {
return "BAD_INDICATOR";
} else if (err == APIError::BAD_DATA_PACKET) {
return "BAD_DATA_PACKET";
} else if (err == APIError::TCP_NODELAY_FAILED) {
return "TCP_NODELAY_FAILED";
} else if (err == APIError::TCP_NONBLOCKING_FAILED) {
return "TCP_NONBLOCKING_FAILED";
} else if (err == APIError::CLOSE_FAILED) {
return "CLOSE_FAILED";
} else if (err == APIError::SHUTDOWN_FAILED) {
return "SHUTDOWN_FAILED";
} else if (err == APIError::BAD_STATE) {
return "BAD_STATE";
} else if (err == APIError::BAD_ARG) {
return "BAD_ARG";
} else if (err == APIError::SOCKET_READ_FAILED) {
return "SOCKET_READ_FAILED";
} else if (err == APIError::SOCKET_WRITE_FAILED) {
return "SOCKET_WRITE_FAILED";
} else if (err == APIError::HANDSHAKESTATE_READ_FAILED) {
return "HANDSHAKESTATE_READ_FAILED";
} else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) {
return "HANDSHAKESTATE_WRITE_FAILED";
} else if (err == APIError::HANDSHAKESTATE_BAD_STATE) {
return "HANDSHAKESTATE_BAD_STATE";
} else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) {
return "CIPHERSTATE_DECRYPT_FAILED";
} else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) {
return "CIPHERSTATE_ENCRYPT_FAILED";
} else if (err == APIError::OUT_OF_MEMORY) {
return "OUT_OF_MEMORY";
} else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) {
return "HANDSHAKESTATE_SETUP_FAILED";
} else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) {
return "HANDSHAKESTATE_SPLIT_FAILED";
} else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) {
return "BAD_HANDSHAKE_ERROR_BYTE";
} else if (err == APIError::CONNECTION_CLOSED) {
return "CONNECTION_CLOSED";
}
return "UNKNOWN";
}
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__)
// uncomment to log raw packets
//#define HELPER_LOG_PACKETS
#ifdef USE_API_NOISE
static const char *const PROLOGUE_INIT = "NoiseAPIInit";
@@ -128,7 +73,6 @@ APIError APINoiseFrameHelper::init() {
HELPER_LOG("Setting nonblocking failed with errno %d", errno);
return APIError::TCP_NONBLOCKING_FAILED;
}
int enable = 1;
err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
if (err != 0) {
@@ -174,6 +118,9 @@ APIError APINoiseFrameHelper::loop() {
* errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase.
*/
APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
int err;
APIError aerr;
if (frame == nullptr) {
HELPER_LOG("Bad argument for try_read_frame_");
return APIError::BAD_ARG;
@@ -184,20 +131,15 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
// no header information yet
size_t to_read = 3 - rx_header_buf_len_;
ssize_t received = socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
return APIError::WOULD_BLOCK;
}
if (is_would_block(received)) {
return APIError::WOULD_BLOCK;
} else if (received == -1) {
state_ = State::FAILED;
HELPER_LOG("Socket read failed with errno %d", errno);
return APIError::SOCKET_READ_FAILED;
} else if (received == 0) {
state_ = State::FAILED;
HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED;
}
rx_header_buf_len_ += received;
if ((size_t) received != to_read) {
if (received != to_read) {
// not a full read
return APIError::WOULD_BLOCK;
}
@@ -216,7 +158,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2];
if (state_ != State::DATA && msg_size > 128) {
// for handshake message only permit up to 128 bytes
// for handshake message only permit up to 128 byte
state_ = State::FAILED;
HELPER_LOG("Bad packet len for handshake: %d", msg_size);
return APIError::BAD_HANDSHAKE_PACKET_LEN;
@@ -231,29 +173,22 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
// more data to read
size_t to_read = msg_size - rx_buf_len_;
ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read);
if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
return APIError::WOULD_BLOCK;
}
if (is_would_block(received)) {
return APIError::WOULD_BLOCK;
} else if (received == -1) {
state_ = State::FAILED;
HELPER_LOG("Socket read failed with errno %d", errno);
return APIError::SOCKET_READ_FAILED;
} else if (received == 0) {
state_ = State::FAILED;
HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED;
}
rx_buf_len_ += received;
if ((size_t) received != to_read) {
if (received != to_read) {
// not all read
return APIError::WOULD_BLOCK;
}
}
// uncomment for even more debugging
#ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str());
#endif
// ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str());
frame->msg = std::move(rx_buf_);
// consume msg
rx_buf_ = {};
@@ -282,18 +217,10 @@ APIError APINoiseFrameHelper::state_action_() {
// waiting for client hello
ParsedFrame frame;
aerr = try_read_frame_(&frame);
if (aerr == APIError::BAD_INDICATOR) {
send_explicit_handshake_reject_("Bad indicator byte");
return aerr;
}
if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
send_explicit_handshake_reject_("Bad handshake packet len");
return aerr;
}
if (aerr != APIError::OK)
return aerr;
// ignore contents, may be used in future for flags
prologue_.push_back((uint8_t)(frame.msg.size() >> 8));
prologue_.push_back((uint8_t) (frame.msg.size() >> 8));
prologue_.push_back((uint8_t) frame.msg.size());
prologue_.insert(prologue_.end(), frame.msg.begin(), frame.msg.end());
@@ -324,6 +251,9 @@ APIError APINoiseFrameHelper::state_action_() {
send_explicit_handshake_reject_("Bad indicator byte");
return aerr;
}
if (frame.msg.size() < 1 || frame.msg[0] != 0x00) {
aerr = APIError::BAD_HANDSHAKE_PACKET_LEN;
}
if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
send_explicit_handshake_reject_("Bad handshake packet len");
return aerr;
@@ -331,20 +261,12 @@ APIError APINoiseFrameHelper::state_action_() {
if (aerr != APIError::OK)
return aerr;
if (frame.msg.empty()) {
send_explicit_handshake_reject_("Empty handshake message");
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
} else if (frame.msg[0] != 0x00) {
HELPER_LOG("Bad handshake error byte: %u", frame.msg[0]);
send_explicit_handshake_reject_("Bad handshake error byte");
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
}
NoiseBuffer mbuf;
noise_buffer_init(mbuf);
noise_buffer_set_input(mbuf, frame.msg.data() + 1, frame.msg.size() - 1);
err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
if (err != 0) {
// TODO: explicit rejection
state_ = State::FAILED;
HELPER_LOG("noise_handshakestate_read_message failed: %s", noise_err_to_str(err).c_str());
if (err == NOISE_ERROR_MAC_FAILURE) {
@@ -392,16 +314,12 @@ APIError APINoiseFrameHelper::state_action_() {
}
void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &reason) {
std::vector<uint8_t> data;
data.resize(reason.length() + 1);
data.reserve(reason.size() + 1);
data[0] = 0x01; // failure
for (size_t i = 0; i < reason.length(); i++) {
data[i + 1] = (uint8_t) reason[i];
for (size_t i = 0; i < reason.size(); i++) {
data[i+1] = (uint8_t) reason[i];
}
// temporarily remove failed state
auto orig_state = state_;
state_ = State::EXPLICIT_REJECT;
write_frame_(data.data(), data.size());
state_ = orig_state;
}
APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
@@ -457,7 +375,9 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
buffer->type = type;
return APIError::OK;
}
bool APINoiseFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
bool APINoiseFrameHelper::can_write_without_blocking() {
return state_ == State::DATA && tx_buf_.empty();
}
APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
int err;
APIError aerr;
@@ -483,9 +403,9 @@ APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload
// tmpbuf[1], tmpbuf[2] to be set later
const uint8_t msg_offset = 3;
const uint8_t payload_offset = msg_offset + 4;
tmpbuf[msg_offset + 0] = (uint8_t)(type >> 8); // type
tmpbuf[msg_offset + 0] = (uint8_t) (type >> 8); // type
tmpbuf[msg_offset + 1] = (uint8_t) type;
tmpbuf[msg_offset + 2] = (uint8_t)(payload_len >> 8); // data_len
tmpbuf[msg_offset + 2] = (uint8_t) (payload_len >> 8); // data_len
tmpbuf[msg_offset + 3] = (uint8_t) payload_len;
// copy data
std::copy(payload, payload + payload_len, &tmpbuf[payload_offset]);
@@ -503,15 +423,14 @@ APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload
}
size_t total_len = 3 + mbuf.size;
tmpbuf[1] = (uint8_t)(mbuf.size >> 8);
tmpbuf[1] = (uint8_t) (mbuf.size >> 8);
tmpbuf[2] = (uint8_t) mbuf.size;
struct iovec iov;
iov.iov_base = &tmpbuf[0];
iov.iov_len = total_len;
// write raw to not have two packets sent if NAGLE disabled
return write_raw_(&iov, 1);
aerr = write_raw_(&tmpbuf[0], total_len);
if (aerr != APIError::OK) {
return aerr;
}
return APIError::OK;
}
APIError APINoiseFrameHelper::try_send_tx_buf_() {
// try send from tx_buf
@@ -538,18 +457,14 @@ APIError APINoiseFrameHelper::try_send_tx_buf_() {
* @param data The data to write
* @param len The length of data
*/
APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
if (iovcnt == 0)
APIError APINoiseFrameHelper::write_raw_(const uint8_t *data, size_t len) {
if (len == 0)
return APIError::OK;
int err;
APIError aerr;
size_t total_write_len = 0;
for (int i = 0; i < iovcnt; i++) {
#ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
#endif
total_write_len += iov[i].iov_len;
}
// uncomment for even more debugging
// ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str());
if (!tx_buf_.empty()) {
// try to empty tx_buf_ first
@@ -560,56 +475,41 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
if (!tx_buf_.empty()) {
// tx buf not empty, can't write now because then stream would be inconsistent
for (int i = 0; i < iovcnt; i++) {
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
}
tx_buf_.insert(tx_buf_.end(), data, data + len);
return APIError::OK;
}
ssize_t sent = socket_->writev(iov, iovcnt);
ssize_t sent = socket_->write(data, len);
if (is_would_block(sent)) {
// operation would block, add buffer to tx_buf
for (int i = 0; i < iovcnt; i++) {
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
}
tx_buf_.insert(tx_buf_.end(), data, data + len);
return APIError::OK;
} else if (sent == -1) {
// an error occured
state_ = State::FAILED;
HELPER_LOG("Socket write failed with errno %d", errno);
return APIError::SOCKET_WRITE_FAILED;
} else if ((size_t) sent != total_write_len) {
} else if (sent != len) {
// partially sent, add end to tx_buf
size_t to_consume = sent;
for (int i = 0; i < iovcnt; i++) {
if (to_consume >= iov[i].iov_len) {
to_consume -= iov[i].iov_len;
} else {
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
to_consume = 0;
}
}
tx_buf_.insert(tx_buf_.end(), data + sent, data + len);
return APIError::OK;
}
// fully sent
return APIError::OK;
}
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
APIError aerr;
uint8_t header[3];
header[0] = 0x01; // indicator
header[1] = (uint8_t)(len >> 8);
header[1] = (uint8_t) (len >> 8);
header[2] = (uint8_t) len;
struct iovec iov[2];
iov[0].iov_base = header;
iov[0].iov_len = 3;
iov[1].iov_base = const_cast<uint8_t *>(data);
iov[1].iov_len = len;
return write_raw_(iov, 2);
aerr = write_raw_(header, 3);
if (aerr != APIError::OK)
return aerr;
aerr = write_raw_(data, len);
return aerr;
}
/** Initiate the data structures for the handshake.
@@ -720,10 +620,13 @@ APIError APINoiseFrameHelper::shutdown(int how) {
}
extern "C" {
// declare how noise generates random bytes (here with a good HWRNG based on the RF system)
void noise_rand_bytes(void *output, size_t len) { esphome::fill_random(reinterpret_cast<uint8_t *>(output), len); }
void noise_rand_bytes(void *output, size_t len) {
esphome::fill_random(reinterpret_cast<uint8_t *>(output), len);
}
}
#endif // USE_API_NOISE
#ifdef USE_API_PLAINTEXT
/// Initialize the frame helper, returns OK if successful.
@@ -774,6 +677,9 @@ APIError APIPlaintextFrameHelper::loop() {
* error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
*/
APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
int err;
APIError aerr;
if (frame == nullptr) {
HELPER_LOG("Bad argument for try_read_frame_");
return APIError::BAD_ARG;
@@ -783,17 +689,12 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
while (!rx_header_parsed_) {
uint8_t data;
ssize_t received = socket_->read(&data, 1);
if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
return APIError::WOULD_BLOCK;
}
if (is_would_block(received)) {
return APIError::WOULD_BLOCK;
} else if (received == -1) {
state_ = State::FAILED;
HELPER_LOG("Socket read failed with errno %d", errno);
return APIError::SOCKET_READ_FAILED;
} else if (received == 0) {
state_ = State::FAILED;
HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED;
}
rx_header_buf_.push_back(data);
@@ -805,7 +706,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
}
size_t i = 1;
uint32_t consumed = 0;
size_t consumed = 0;
auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed);
if (!msg_size_varint.has_value()) {
// not enough data there yet
@@ -834,29 +735,22 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
// more data to read
size_t to_read = rx_header_parsed_len_ - rx_buf_len_;
ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read);
if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
return APIError::WOULD_BLOCK;
}
if (is_would_block(received)) {
return APIError::WOULD_BLOCK;
} else if (received == -1) {
state_ = State::FAILED;
HELPER_LOG("Socket read failed with errno %d", errno);
return APIError::SOCKET_READ_FAILED;
} else if (received == 0) {
state_ = State::FAILED;
HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED;
}
rx_buf_len_ += received;
if ((size_t) received != to_read) {
if (received != to_read) {
// not all read
return APIError::WOULD_BLOCK;
}
}
// uncomment for even more debugging
#ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str());
#endif
// ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str());
frame->msg = std::move(rx_buf_);
// consume msg
rx_buf_ = {};
@@ -867,6 +761,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
}
APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
int err;
APIError aerr;
if (state_ != State::DATA) {
@@ -884,8 +779,13 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
buffer->type = rx_header_parsed_type_;
return APIError::OK;
}
bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
bool APIPlaintextFrameHelper::can_write_without_blocking() {
return state_ == State::DATA && tx_buf_.empty();
}
APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
int err;
APIError aerr;
if (state_ != State::DATA) {
return APIError::BAD_STATE;
}
@@ -895,24 +795,28 @@ APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *pay
ProtoVarInt(payload_len).encode(header);
ProtoVarInt(type).encode(header);
struct iovec iov[2];
iov[0].iov_base = &header[0];
iov[0].iov_len = header.size();
iov[1].iov_base = const_cast<uint8_t *>(payload);
iov[1].iov_len = payload_len;
return write_raw_(iov, 2);
aerr = write_raw_(&header[0], header.size());
if (aerr != APIError::OK) {
return aerr;
}
aerr = write_raw_(payload, payload_len);
if (aerr != APIError::OK) {
return aerr;
}
return APIError::OK;
}
APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
// try send from tx_buf
while (state_ != State::CLOSED && !tx_buf_.empty()) {
ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size());
if (is_would_block(sent)) {
break;
} else if (sent == -1) {
if (sent == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN)
break;
state_ = State::FAILED;
HELPER_LOG("Socket write failed with errno %d", errno);
return APIError::SOCKET_WRITE_FAILED;
} else if (sent == 0) {
break;
}
// TODO: inefficient if multiple packets in txbuf
// replace with deque of buffers
@@ -926,18 +830,14 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
* @param data The data to write
* @param len The length of data
*/
APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
if (iovcnt == 0)
APIError APIPlaintextFrameHelper::write_raw_(const uint8_t *data, size_t len) {
if (len == 0)
return APIError::OK;
int err;
APIError aerr;
size_t total_write_len = 0;
for (int i = 0; i < iovcnt; i++) {
#ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
#endif
total_write_len += iov[i].iov_len;
}
// uncomment for even more debugging
// ESP_LOGVV(TAG, "Sending raw: %s", hexencode(data, len).c_str());
if (!tx_buf_.empty()) {
// try to empty tx_buf_ first
@@ -948,43 +848,42 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt
if (!tx_buf_.empty()) {
// tx buf not empty, can't write now because then stream would be inconsistent
for (int i = 0; i < iovcnt; i++) {
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
}
tx_buf_.insert(tx_buf_.end(), data, data + len);
return APIError::OK;
}
ssize_t sent = socket_->writev(iov, iovcnt);
ssize_t sent = socket_->write(data, len);
if (is_would_block(sent)) {
// operation would block, add buffer to tx_buf
for (int i = 0; i < iovcnt; i++) {
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
}
tx_buf_.insert(tx_buf_.end(), data, data + len);
return APIError::OK;
} else if (sent == -1) {
// an error occured
state_ = State::FAILED;
HELPER_LOG("Socket write failed with errno %d", errno);
return APIError::SOCKET_WRITE_FAILED;
} else if ((size_t) sent != total_write_len) {
} else if (sent != len) {
// partially sent, add end to tx_buf
size_t to_consume = sent;
for (int i = 0; i < iovcnt; i++) {
if (to_consume >= iov[i].iov_len) {
to_consume -= iov[i].iov_len;
} else {
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
to_consume = 0;
}
}
tx_buf_.insert(tx_buf_.end(), data + sent, data + len);
return APIError::OK;
}
// fully sent
return APIError::OK;
}
APIError APIPlaintextFrameHelper::write_frame_(const uint8_t *data, size_t len) {
APIError aerr;
uint8_t header[3];
header[0] = 0x01; // indicator
header[1] = (uint8_t) (len >> 8);
header[2] = (uint8_t) len;
aerr = write_raw_(header, 3);
if (aerr != APIError::OK)
return aerr;
aerr = write_raw_(data, len);
return aerr;
}
APIError APIPlaintextFrameHelper::close() {
state_ = State::CLOSED;

View File

@@ -1,8 +1,7 @@
#pragma once
#include <cstdint>
#include <deque>
#include <utility>
#include <vector>
#include <deque>
#include "esphome/core/defines.h"
@@ -52,15 +51,10 @@ enum class APIError : int {
OUT_OF_MEMORY = 1018,
HANDSHAKESTATE_SETUP_FAILED = 1019,
HANDSHAKESTATE_SPLIT_FAILED = 1020,
BAD_HANDSHAKE_ERROR_BYTE = 1021,
CONNECTION_CLOSED = 1022,
};
const char *api_error_to_str(APIError err);
class APIFrameHelper {
public:
virtual ~APIFrameHelper() = default;
virtual APIError init() = 0;
virtual APIError loop() = 0;
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
@@ -76,19 +70,22 @@ class APIFrameHelper {
#ifdef USE_API_NOISE
class APINoiseFrameHelper : public APIFrameHelper {
public:
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx)
: socket_(std::move(socket)), ctx_(std::move(std::move(ctx))) {}
~APINoiseFrameHelper() override;
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx) : socket_(std::move(socket)), ctx_(ctx) {}
~APINoiseFrameHelper();
APIError init() override;
APIError loop() override;
APIError read_packet(ReadPacketBuffer *buffer) override;
bool can_write_without_blocking() override;
APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
std::string getpeername() override { return socket_->getpeername(); }
APIError write_packet(uint16_t type, const uint8_t *data, size_t len) override;
std::string getpeername() override{
return socket_->getpeername();
}
APIError close() override;
APIError shutdown(int how) override;
// Give this helper a name for logging
void set_log_info(std::string info) override { info_ = std::move(info); }
void set_log_info(std::string info) override {
info_ = std::move(info);
}
protected:
struct ParsedFrame {
@@ -99,7 +96,7 @@ class APINoiseFrameHelper : public APIFrameHelper {
APIError try_read_frame_(ParsedFrame *frame);
APIError try_send_tx_buf_();
APIError write_frame_(const uint8_t *data, size_t len);
APIError write_raw_(const struct iovec *iov, int iovcnt);
APIError write_raw_(const uint8_t *data, size_t len);
APIError init_handshake_();
APIError check_handshake_finished_();
void send_explicit_handshake_reject_(const std::string &reason);
@@ -129,7 +126,6 @@ class APINoiseFrameHelper : public APIFrameHelper {
DATA = 5,
CLOSED = 6,
FAILED = 7,
EXPLICIT_REJECT = 8,
} state_ = State::INITIALIZE;
};
#endif // USE_API_NOISE
@@ -138,17 +134,21 @@ class APINoiseFrameHelper : public APIFrameHelper {
class APIPlaintextFrameHelper : public APIFrameHelper {
public:
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_(std::move(socket)) {}
~APIPlaintextFrameHelper() override = default;
~APIPlaintextFrameHelper() = default;
APIError init() override;
APIError loop() override;
APIError read_packet(ReadPacketBuffer *buffer) override;
bool can_write_without_blocking() override;
APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
std::string getpeername() override { return socket_->getpeername(); }
APIError write_packet(uint16_t type, const uint8_t *data, size_t len) override;
std::string getpeername() override {
return socket_->getpeername();
}
APIError close() override;
APIError shutdown(int how) override;
// Give this helper a name for logging
void set_log_info(std::string info) override { info_ = std::move(info); }
void set_log_info(std::string info) override {
info_ = std::move(info);
}
protected:
struct ParsedFrame {
@@ -157,7 +157,8 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
APIError try_read_frame_(ParsedFrame *frame);
APIError try_send_tx_buf_();
APIError write_raw_(const struct iovec *iov, int iovcnt);
APIError write_frame_(const uint8_t *data, size_t len);
APIError write_raw_(const uint8_t *data, size_t len);
std::unique_ptr<socket::Socket> socket_;

View File

@@ -11,13 +11,17 @@ using psk_t = std::array<uint8_t, 32>;
class APINoiseContext {
public:
void set_psk(psk_t psk) { psk_ = psk; }
const psk_t &get_psk() const { return psk_; }
void set_psk(psk_t psk) {
psk_ = std::move(psk);
}
const psk_t &get_psk() const {
return psk_;
}
protected:
psk_t psk_;
};
#endif // USE_API_NOISE
#endif // USE_API_NOISE
} // namespace api
} // namespace esphome

View File

@@ -6,18 +6,6 @@
namespace esphome {
namespace api {
template<> const char *proto_enum_to_string<enums::EntityCategory>(enums::EntityCategory value) {
switch (value) {
case enums::ENTITY_CATEGORY_NONE:
return "ENTITY_CATEGORY_NONE";
case enums::ENTITY_CATEGORY_CONFIG:
return "ENTITY_CATEGORY_CONFIG";
case enums::ENTITY_CATEGORY_DIAGNOSTIC:
return "ENTITY_CATEGORY_DIAGNOSTIC";
default:
return "UNKNOWN";
}
}
template<> const char *proto_enum_to_string<enums::LegacyCoverState>(enums::LegacyCoverState value) {
switch (value) {
case enums::LEGACY_COVER_STATE_OPEN:
@@ -106,8 +94,6 @@ template<> const char *proto_enum_to_string<enums::SensorStateClass>(enums::Sens
return "STATE_CLASS_NONE";
case enums::STATE_CLASS_MEASUREMENT:
return "STATE_CLASS_MEASUREMENT";
case enums::STATE_CLASS_TOTAL_INCREASING:
return "STATE_CLASS_TOTAL_INCREASING";
default:
return "UNKNOWN";
}
@@ -134,8 +120,6 @@ template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel val
return "LOG_LEVEL_WARN";
case enums::LOG_LEVEL_INFO:
return "LOG_LEVEL_INFO";
case enums::LOG_LEVEL_CONFIG:
return "LOG_LEVEL_CONFIG";
case enums::LOG_LEVEL_DEBUG:
return "LOG_LEVEL_DEBUG";
case enums::LOG_LEVEL_VERBOSE:
@@ -266,18 +250,6 @@ template<> const char *proto_enum_to_string<enums::ClimatePreset>(enums::Climate
return "UNKNOWN";
}
}
template<> const char *proto_enum_to_string<enums::NumberMode>(enums::NumberMode value) {
switch (value) {
case enums::NUMBER_MODE_AUTO:
return "NUMBER_MODE_AUTO";
case enums::NUMBER_MODE_BOX:
return "NUMBER_MODE_BOX";
case enums::NUMBER_MODE_SLIDER:
return "NUMBER_MODE_SLIDER";
default:
return "UNKNOWN";
}
}
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
@@ -291,7 +263,7 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value)
void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); }
#ifdef HAS_PROTO_MESSAGE_DUMP
void HelloRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("HelloRequest {\n");
out.append(" client_info: ");
out.append("'").append(this->client_info).append("'");
@@ -330,7 +302,7 @@ void HelloResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void HelloResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("HelloResponse {\n");
out.append(" api_version_major: ");
sprintf(buffer, "%u", this->api_version_major);
@@ -361,7 +333,7 @@ bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value
void ConnectRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->password); }
#ifdef HAS_PROTO_MESSAGE_DUMP
void ConnectRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("ConnectRequest {\n");
out.append(" password: ");
out.append("'").append(this->password).append("'");
@@ -382,7 +354,7 @@ bool ConnectResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); }
#ifdef HAS_PROTO_MESSAGE_DUMP
void ConnectResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("ConnectResponse {\n");
out.append(" invalid_password: ");
out.append(YESNO(this->invalid_password));
@@ -420,10 +392,6 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
this->has_deep_sleep = value.as_bool();
return true;
}
case 10: {
this->webserver_port = value.as_uint32();
return true;
}
default:
return false;
}
@@ -472,11 +440,10 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(7, this->has_deep_sleep);
buffer.encode_string(8, this->project_name);
buffer.encode_string(9, this->project_version);
buffer.encode_uint32(10, this->webserver_port);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void DeviceInfoResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("DeviceInfoResponse {\n");
out.append(" uses_password: ");
out.append(YESNO(this->uses_password));
@@ -513,11 +480,6 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
out.append(" project_version: ");
out.append("'").append(this->project_version).append("'");
out.append("\n");
out.append(" webserver_port: ");
sprintf(buffer, "%u", this->webserver_port);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -543,10 +505,6 @@ bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVar
this->disabled_by_default = value.as_bool();
return true;
}
case 9: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
@@ -569,10 +527,6 @@ bool ListEntitiesBinarySensorResponse::decode_length(uint32_t field_id, ProtoLen
this->device_class = value.as_string();
return true;
}
case 8: {
this->icon = value.as_string();
return true;
}
default:
return false;
}
@@ -595,12 +549,10 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(5, this->device_class);
buffer.encode_bool(6, this->is_status_binary_sensor);
buffer.encode_bool(7, this->disabled_by_default);
buffer.encode_string(8, this->icon);
buffer.encode_enum<enums::EntityCategory>(9, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("ListEntitiesBinarySensorResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -630,14 +582,6 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}");
}
#endif
@@ -672,7 +616,7 @@ void BinarySensorStateResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BinarySensorStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("BinarySensorStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -707,10 +651,6 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->disabled_by_default = value.as_bool();
return true;
}
case 11: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
@@ -733,10 +673,6 @@ bool ListEntitiesCoverResponse::decode_length(uint32_t field_id, ProtoLengthDeli
this->device_class = value.as_string();
return true;
}
case 10: {
this->icon = value.as_string();
return true;
}
default:
return false;
}
@@ -761,12 +697,10 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(7, this->supports_tilt);
buffer.encode_string(8, this->device_class);
buffer.encode_bool(9, this->disabled_by_default);
buffer.encode_string(10, this->icon);
buffer.encode_enum<enums::EntityCategory>(11, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesCoverResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("ListEntitiesCoverResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -804,14 +738,6 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}");
}
#endif
@@ -856,7 +782,7 @@ void CoverStateResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void CoverStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("CoverStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -939,7 +865,7 @@ void CoverCommandRequest::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void CoverCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("CoverCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -1000,10 +926,6 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value
this->disabled_by_default = value.as_bool();
return true;
}
case 11: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
@@ -1022,10 +944,6 @@ bool ListEntitiesFanResponse::decode_length(uint32_t field_id, ProtoLengthDelimi
this->unique_id = value.as_string();
return true;
}
case 10: {
this->icon = value.as_string();
return true;
}
default:
return false;
}
@@ -1050,12 +968,10 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(7, this->supports_direction);
buffer.encode_int32(8, this->supported_speed_count);
buffer.encode_bool(9, this->disabled_by_default);
buffer.encode_string(10, this->icon);
buffer.encode_enum<enums::EntityCategory>(11, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesFanResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("ListEntitiesFanResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -1094,14 +1010,6 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}");
}
#endif
@@ -1151,7 +1059,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void FanStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("FanStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -1252,7 +1160,7 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void FanCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("FanCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -1328,10 +1236,6 @@ bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->disabled_by_default = value.as_bool();
return true;
}
case 15: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
@@ -1354,10 +1258,6 @@ bool ListEntitiesLightResponse::decode_length(uint32_t field_id, ProtoLengthDeli
this->effects.push_back(value.as_string());
return true;
}
case 14: {
this->icon = value.as_string();
return true;
}
default:
return false;
}
@@ -1398,12 +1298,10 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(11, it, true);
}
buffer.encode_bool(13, this->disabled_by_default);
buffer.encode_string(14, this->icon);
buffer.encode_enum<enums::EntityCategory>(15, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesLightResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("ListEntitiesLightResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -1463,14 +1361,6 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}");
}
#endif
@@ -1561,7 +1451,7 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void LightStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("LightStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -1784,7 +1674,7 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void LightCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("LightCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -1923,17 +1813,13 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va
return true;
}
case 11: {
this->legacy_last_reset_type = value.as_enum<enums::SensorLastResetType>();
this->last_reset_type = value.as_enum<enums::SensorLastResetType>();
return true;
}
case 12: {
this->disabled_by_default = value.as_bool();
return true;
}
case 13: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
@@ -1989,13 +1875,12 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(8, this->force_update);
buffer.encode_string(9, this->device_class);
buffer.encode_enum<enums::SensorStateClass>(10, this->state_class);
buffer.encode_enum<enums::SensorLastResetType>(11, this->legacy_last_reset_type);
buffer.encode_enum<enums::SensorLastResetType>(11, this->last_reset_type);
buffer.encode_bool(12, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(13, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("ListEntitiesSensorResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -2039,17 +1924,13 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
out.append(proto_enum_to_string<enums::SensorStateClass>(this->state_class));
out.append("\n");
out.append(" legacy_last_reset_type: ");
out.append(proto_enum_to_string<enums::SensorLastResetType>(this->legacy_last_reset_type));
out.append(" last_reset_type: ");
out.append(proto_enum_to_string<enums::SensorLastResetType>(this->last_reset_type));
out.append("\n");
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}");
}
#endif
@@ -2084,7 +1965,7 @@ void SensorStateResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SensorStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("SensorStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -2112,10 +1993,6 @@ bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool();
return true;
}
case 8: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
@@ -2160,11 +2037,10 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->assumed_state);
buffer.encode_bool(7, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(8, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("ListEntitiesSwitchResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -2194,10 +2070,6 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}");
}
#endif
@@ -2227,7 +2099,7 @@ void SwitchStateResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SwitchStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("SwitchStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -2266,7 +2138,7 @@ void SwitchCommandRequest::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SwitchCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("SwitchCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -2285,10 +2157,6 @@ bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarIn
this->disabled_by_default = value.as_bool();
return true;
}
case 7: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
@@ -2332,11 +2200,10 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("ListEntitiesTextSensorResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -2362,10 +2229,6 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}");
}
#endif
@@ -2406,7 +2269,7 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void TextSensorStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("TextSensorStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -2443,7 +2306,7 @@ void SubscribeLogsRequest::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeLogsRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("SubscribeLogsRequest {\n");
out.append(" level: ");
out.append(proto_enum_to_string<enums::LogLevel>(this->level));
@@ -2471,6 +2334,10 @@ bool SubscribeLogsResponse::decode_varint(uint32_t field_id, ProtoVarInt value)
}
bool SubscribeLogsResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
this->tag = value.as_string();
return true;
}
case 3: {
this->message = value.as_string();
return true;
@@ -2481,17 +2348,22 @@ bool SubscribeLogsResponse::decode_length(uint32_t field_id, ProtoLengthDelimite
}
void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_enum<enums::LogLevel>(1, this->level);
buffer.encode_string(2, this->tag);
buffer.encode_string(3, this->message);
buffer.encode_bool(4, this->send_failed);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeLogsResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("SubscribeLogsResponse {\n");
out.append(" level: ");
out.append(proto_enum_to_string<enums::LogLevel>(this->level));
out.append("\n");
out.append(" tag: ");
out.append("'").append(this->tag).append("'");
out.append("\n");
out.append(" message: ");
out.append("'").append(this->message).append("'");
out.append("\n");
@@ -2528,7 +2400,7 @@ void HomeassistantServiceMap::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void HomeassistantServiceMap::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("HomeassistantServiceMap {\n");
out.append(" key: ");
out.append("'").append(this->key).append("'");
@@ -2587,7 +2459,7 @@ void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void HomeassistantServiceResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("HomeassistantServiceResponse {\n");
out.append(" service: ");
out.append("'").append(this->service).append("'");
@@ -2643,7 +2515,7 @@ void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("SubscribeHomeAssistantStateResponse {\n");
out.append(" entity_id: ");
out.append("'").append(this->entity_id).append("'");
@@ -2680,7 +2552,7 @@ void HomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void HomeAssistantStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("HomeAssistantStateResponse {\n");
out.append(" entity_id: ");
out.append("'").append(this->entity_id).append("'");
@@ -2713,7 +2585,7 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); }
#ifdef HAS_PROTO_MESSAGE_DUMP
void GetTimeResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("GetTimeResponse {\n");
out.append(" epoch_seconds: ");
sprintf(buffer, "%u", this->epoch_seconds);
@@ -2748,7 +2620,7 @@ void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesServicesArgument::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("ListEntitiesServicesArgument {\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
@@ -2793,7 +2665,7 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesServicesResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("ListEntitiesServicesResponse {\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
@@ -2887,7 +2759,7 @@ void ExecuteServiceArgument::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ExecuteServiceArgument::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("ExecuteServiceArgument {\n");
out.append(" bool_: ");
out.append(YESNO(this->bool_));
@@ -2968,7 +2840,7 @@ void ExecuteServiceRequest::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ExecuteServiceRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("ExecuteServiceRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -2989,10 +2861,6 @@ bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool();
return true;
}
case 7: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
@@ -3011,10 +2879,6 @@ bool ListEntitiesCameraResponse::decode_length(uint32_t field_id, ProtoLengthDel
this->unique_id = value.as_string();
return true;
}
case 6: {
this->icon = value.as_string();
return true;
}
default:
return false;
}
@@ -3035,12 +2899,10 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_bool(5, this->disabled_by_default);
buffer.encode_string(6, this->icon);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesCameraResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("ListEntitiesCameraResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -3062,14 +2924,6 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}");
}
#endif
@@ -3110,7 +2964,7 @@ void CameraImageResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void CameraImageResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("CameraImageResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -3147,7 +3001,7 @@ void CameraImageRequest::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void CameraImageRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("CameraImageRequest {\n");
out.append(" single: ");
out.append(YESNO(this->single));
@@ -3197,10 +3051,6 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
this->disabled_by_default = value.as_bool();
return true;
}
case 20: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
@@ -3227,10 +3077,6 @@ bool ListEntitiesClimateResponse::decode_length(uint32_t field_id, ProtoLengthDe
this->supported_custom_presets.push_back(value.as_string());
return true;
}
case 19: {
this->icon = value.as_string();
return true;
}
default:
return false;
}
@@ -3288,12 +3134,10 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(17, it, true);
}
buffer.encode_bool(18, this->disabled_by_default);
buffer.encode_string(19, this->icon);
buffer.encode_enum<enums::EntityCategory>(20, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("ListEntitiesClimateResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -3382,14 +3226,6 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}");
}
#endif
@@ -3480,7 +3316,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ClimateStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("ClimateStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -3668,7 +3504,7 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ClimateCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("ClimateCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -3766,14 +3602,6 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool();
return true;
}
case 10: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 12: {
this->mode = value.as_enum<enums::NumberMode>();
return true;
}
default:
return false;
}
@@ -3796,10 +3624,6 @@ bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDel
this->icon = value.as_string();
return true;
}
case 11: {
this->unit_of_measurement = value.as_string();
return true;
}
default:
return false;
}
@@ -3836,13 +3660,10 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(7, this->max_value);
buffer.encode_float(8, this->step);
buffer.encode_bool(9, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(10, this->entity_category);
buffer.encode_string(11, this->unit_of_measurement);
buffer.encode_enum<enums::NumberMode>(12, this->mode);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesNumberResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("ListEntitiesNumberResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -3883,18 +3704,6 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" unit_of_measurement: ");
out.append("'").append(this->unit_of_measurement).append("'");
out.append("\n");
out.append(" mode: ");
out.append(proto_enum_to_string<enums::NumberMode>(this->mode));
out.append("\n");
out.append("}");
}
#endif
@@ -3929,7 +3738,7 @@ void NumberStateResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void NumberStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("NumberStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -3967,7 +3776,7 @@ void NumberCommandRequest::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void NumberCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("NumberCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -3987,10 +3796,6 @@ bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool();
return true;
}
case 8: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
@@ -4041,11 +3846,10 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(6, it, true);
}
buffer.encode_bool(7, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(8, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSelectResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("ListEntitiesSelectResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -4077,10 +3881,6 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}");
}
#endif
@@ -4121,7 +3921,7 @@ void SelectStateResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SelectStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("SelectStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -4164,7 +3964,7 @@ void SelectCommandRequest::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SelectCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
char buffer[64];
out.append("SelectCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -4177,127 +3977,6 @@ void SelectCommandRequest::dump_to(std::string &out) const {
out.append("}");
}
#endif
bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {
this->disabled_by_default = value.as_bool();
return true;
}
case 7: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
}
bool ListEntitiesButtonResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->object_id = value.as_string();
return true;
}
case 3: {
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
}
case 8: {
this->device_class = value.as_string();
return true;
}
default:
return false;
}
}
bool ListEntitiesButtonResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 2: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
buffer.encode_string(8, this->device_class);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesButtonResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("ListEntitiesButtonResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
out.append("\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" device_class: ");
out.append("'").append(this->device_class).append("'");
out.append("\n");
out.append("}");
}
#endif
bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void ButtonCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); }
#ifdef HAS_PROTO_MESSAGE_DUMP
void ButtonCommandRequest::dump_to(std::string &out) const {
char buffer[64];
out.append("ButtonCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
} // namespace api
} // namespace esphome

View File

@@ -9,11 +9,6 @@ namespace api {
namespace enums {
enum EntityCategory : uint32_t {
ENTITY_CATEGORY_NONE = 0,
ENTITY_CATEGORY_CONFIG = 1,
ENTITY_CATEGORY_DIAGNOSTIC = 2,
};
enum LegacyCoverState : uint32_t {
LEGACY_COVER_STATE_OPEN = 0,
LEGACY_COVER_STATE_CLOSED = 1,
@@ -52,7 +47,6 @@ enum ColorMode : uint32_t {
enum SensorStateClass : uint32_t {
STATE_CLASS_NONE = 0,
STATE_CLASS_MEASUREMENT = 1,
STATE_CLASS_TOTAL_INCREASING = 2,
};
enum SensorLastResetType : uint32_t {
LAST_RESET_NONE = 0,
@@ -64,10 +58,9 @@ enum LogLevel : uint32_t {
LOG_LEVEL_ERROR = 1,
LOG_LEVEL_WARN = 2,
LOG_LEVEL_INFO = 3,
LOG_LEVEL_CONFIG = 4,
LOG_LEVEL_DEBUG = 5,
LOG_LEVEL_VERBOSE = 6,
LOG_LEVEL_VERY_VERBOSE = 7,
LOG_LEVEL_DEBUG = 4,
LOG_LEVEL_VERBOSE = 5,
LOG_LEVEL_VERY_VERBOSE = 6,
};
enum ServiceArgType : uint32_t {
SERVICE_ARG_TYPE_BOOL = 0,
@@ -123,11 +116,6 @@ enum ClimatePreset : uint32_t {
CLIMATE_PRESET_SLEEP = 6,
CLIMATE_PRESET_ACTIVITY = 7,
};
enum NumberMode : uint32_t {
NUMBER_MODE_AUTO = 0,
NUMBER_MODE_BOX = 1,
NUMBER_MODE_SLIDER = 2,
};
} // namespace enums
@@ -234,7 +222,6 @@ class DeviceInfoResponse : public ProtoMessage {
bool has_deep_sleep{false};
std::string project_name{};
std::string project_version{};
uint32_t webserver_port{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -280,8 +267,6 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage {
std::string device_class{};
bool is_status_binary_sensor{false};
bool disabled_by_default{false};
std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -317,8 +302,6 @@ class ListEntitiesCoverResponse : public ProtoMessage {
bool supports_tilt{false};
std::string device_class{};
bool disabled_by_default{false};
std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -375,8 +358,6 @@ class ListEntitiesFanResponse : public ProtoMessage {
bool supports_direction{false};
int32_t supported_speed_count{0};
bool disabled_by_default{false};
std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -441,8 +422,6 @@ class ListEntitiesLightResponse : public ProtoMessage {
float max_mireds{0.0f};
std::vector<std::string> effects{};
bool disabled_by_default{false};
std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -529,9 +508,8 @@ class ListEntitiesSensorResponse : public ProtoMessage {
bool force_update{false};
std::string device_class{};
enums::SensorStateClass state_class{};
enums::SensorLastResetType legacy_last_reset_type{};
enums::SensorLastResetType last_reset_type{};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -565,7 +543,6 @@ class ListEntitiesSwitchResponse : public ProtoMessage {
std::string icon{};
bool assumed_state{false};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -610,7 +587,6 @@ class ListEntitiesTextSensorResponse : public ProtoMessage {
std::string unique_id{};
std::string icon{};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -651,6 +627,7 @@ class SubscribeLogsRequest : public ProtoMessage {
class SubscribeLogsResponse : public ProtoMessage {
public:
enums::LogLevel level{};
std::string tag{};
std::string message{};
bool send_failed{false};
void encode(ProtoWriteBuffer buffer) const override;
@@ -821,8 +798,6 @@ class ListEntitiesCameraResponse : public ProtoMessage {
std::string name{};
std::string unique_id{};
bool disabled_by_default{false};
std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -880,8 +855,6 @@ class ListEntitiesClimateResponse : public ProtoMessage {
std::vector<enums::ClimatePreset> supported_presets{};
std::vector<std::string> supported_custom_presets{};
bool disabled_by_default{false};
std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -961,9 +934,6 @@ class ListEntitiesNumberResponse : public ProtoMessage {
float max_value{0.0f};
float step{0.0f};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
std::string unit_of_measurement{};
enums::NumberMode mode{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -1009,7 +979,6 @@ class ListEntitiesSelectResponse : public ProtoMessage {
std::string icon{};
std::vector<std::string> options{};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -1048,37 +1017,6 @@ class SelectCommandRequest : public ProtoMessage {
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class ListEntitiesButtonResponse : public ProtoMessage {
public:
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
std::string device_class{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
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 ButtonCommandRequest : public ProtoMessage {
public:
uint32_t key{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
};
} // namespace api
} // namespace esphome

View File

@@ -282,16 +282,6 @@ bool APIServerConnectionBase::send_select_state_response(const SelectStateRespon
#endif
#ifdef USE_SELECT
#endif
#ifdef USE_BUTTON
bool APIServerConnectionBase::send_list_entities_button_response(const ListEntitiesButtonResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_button_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesButtonResponse>(msg, 61);
}
#endif
#ifdef USE_BUTTON
#endif
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
switch (msg_type) {
case 1: {
@@ -523,17 +513,6 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str());
#endif
this->on_select_command_request(msg);
#endif
break;
}
case 62: {
#ifdef USE_BUTTON
ButtonCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_button_command_request: %s", msg.dump().c_str());
#endif
this->on_button_command_request(msg);
#endif
break;
}
@@ -758,19 +737,6 @@ void APIServerConnection::on_select_command_request(const SelectCommandRequest &
this->select_command(msg);
}
#endif
#ifdef USE_BUTTON
void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->button_command(msg);
}
#endif
} // namespace api
} // namespace esphome

View File

@@ -129,12 +129,6 @@ class APIServerConnectionBase : public ProtoService {
#endif
#ifdef USE_SELECT
virtual void on_select_command_request(const SelectCommandRequest &value){};
#endif
#ifdef USE_BUTTON
bool send_list_entities_button_response(const ListEntitiesButtonResponse &msg);
#endif
#ifdef USE_BUTTON
virtual void on_button_command_request(const ButtonCommandRequest &value){};
#endif
protected:
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
@@ -177,9 +171,6 @@ class APIServerConnection : public APIServerConnectionBase {
#endif
#ifdef USE_SELECT
virtual void select_command(const SelectCommandRequest &msg) = 0;
#endif
#ifdef USE_BUTTON
virtual void button_command(const ButtonCommandRequest &msg) = 0;
#endif
protected:
void on_hello_request(const HelloRequest &msg) override;
@@ -218,9 +209,6 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_SELECT
void on_select_command_request(const SelectCommandRequest &msg) override;
#endif
#ifdef USE_BUTTON
void on_button_command_request(const ButtonCommandRequest &msg) override;
#endif
};
} // namespace api

View File

@@ -1,13 +1,12 @@
#include "api_server.h"
#include "api_connection.h"
#include "esphome/core/application.h"
#include "esphome/core/defines.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include "esphome/core/util.h"
#include "esphome/core/defines.h"
#include "esphome/core/version.h"
#include "esphome/core/hal.h"
#include "esphome/components/network/util.h"
#include <cerrno>
#include <errno.h>
//#include <arpa/inet.h>
#ifdef USE_LOGGER
#include "esphome/components/logger/logger.h"
@@ -43,10 +42,19 @@ void APIServer::setup() {
return;
}
/*struct sockaddr_storage dest_addr;
memset(&dest_addr, 0, sizeof(dest_addr));
struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *) &dest_addr;
dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY);
dest_addr_ip4->sin_family = AF_INET;
dest_addr_ip4->sin_port = htons(this->port_);
err = socket_->bind((struct sockaddr *) &dest_addr, sizeof(dest_addr));*/
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = ESPHOME_INADDR_ANY;
server.sin_addr.s_addr = INADDR_ANY;
server.sin_port = htons(this->port_);
err = socket_->bind((struct sockaddr *) &server, sizeof(server));
@@ -66,7 +74,7 @@ void APIServer::setup() {
#ifdef USE_LOGGER
if (logger::global_logger != nullptr) {
logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) {
for (auto &c : this->clients_) {
for (auto *c : this->clients_) {
if (!c->remove_)
c->send_log_message(level, tag, message);
}
@@ -77,13 +85,12 @@ void APIServer::setup() {
this->last_connected_ = millis();
#ifdef USE_ESP32_CAMERA
if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) {
esp32_camera::global_esp32_camera->add_image_callback(
[this](const std::shared_ptr<esp32_camera::CameraImage> &image) {
for (auto &c : this->clients_)
if (!c->remove_)
c->send_camera_state(image);
});
if (esp32_camera::global_esp32_camera != nullptr) {
esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr<esp32_camera::CameraImage> image) {
for (auto *c : this->clients_)
if (!c->remove_)
c->send_camera_state(image);
});
}
#endif
}
@@ -98,21 +105,25 @@ void APIServer::loop() {
ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str());
auto *conn = new APIConnection(std::move(sock), this);
clients_.emplace_back(conn);
clients_.push_back(conn);
conn->start();
}
// Partition clients into remove and active
auto new_end = std::partition(this->clients_.begin(), this->clients_.end(),
[](const std::unique_ptr<APIConnection> &conn) { return !conn->remove_; });
auto new_end =
std::partition(this->clients_.begin(), this->clients_.end(), [](APIConnection *conn) { return !conn->remove_; });
// print disconnection messages
for (auto it = new_end; it != this->clients_.end(); ++it) {
ESP_LOGV(TAG, "Removing connection to %s", (*it)->client_info_.c_str());
ESP_LOGD(TAG, "Disconnecting %s", (*it)->client_info_.c_str());
}
// only then delete the pointers, otherwise log routine
// would access freed memory
for (auto it = new_end; it != this->clients_.end(); ++it)
delete *it;
// resize vector
this->clients_.erase(new_end, this->clients_.end());
for (auto &client : this->clients_) {
for (auto *client : this->clients_) {
client->loop();
}
@@ -132,12 +143,7 @@ void APIServer::loop() {
}
void APIServer::dump_config() {
ESP_LOGCONFIG(TAG, "API Server:");
ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->port_);
#ifdef USE_API_NOISE
ESP_LOGCONFIG(TAG, " Using noise encryption: YES");
#else
ESP_LOGCONFIG(TAG, " Using noise encryption: NO");
#endif
ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->port_);
}
bool APIServer::uses_password() const { return !this->password_.empty(); }
bool APIServer::check_password(const std::string &password) const {
@@ -173,7 +179,7 @@ void APIServer::handle_disconnect(APIConnection *conn) {}
void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
for (auto *c : this->clients_)
c->send_binary_sensor_state(obj, state);
}
#endif
@@ -182,7 +188,7 @@ void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool s
void APIServer::on_cover_update(cover::Cover *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
for (auto *c : this->clients_)
c->send_cover_state(obj);
}
#endif
@@ -191,7 +197,7 @@ void APIServer::on_cover_update(cover::Cover *obj) {
void APIServer::on_fan_update(fan::FanState *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
for (auto *c : this->clients_)
c->send_fan_state(obj);
}
#endif
@@ -200,7 +206,7 @@ void APIServer::on_fan_update(fan::FanState *obj) {
void APIServer::on_light_update(light::LightState *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
for (auto *c : this->clients_)
c->send_light_state(obj);
}
#endif
@@ -209,7 +215,7 @@ void APIServer::on_light_update(light::LightState *obj) {
void APIServer::on_sensor_update(sensor::Sensor *obj, float state) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
for (auto *c : this->clients_)
c->send_sensor_state(obj, state);
}
#endif
@@ -218,7 +224,7 @@ void APIServer::on_sensor_update(sensor::Sensor *obj, float state) {
void APIServer::on_switch_update(switch_::Switch *obj, bool state) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
for (auto *c : this->clients_)
c->send_switch_state(obj, state);
}
#endif
@@ -227,7 +233,7 @@ void APIServer::on_switch_update(switch_::Switch *obj, bool 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_)
for (auto *c : this->clients_)
c->send_text_sensor_state(obj, state);
}
#endif
@@ -236,7 +242,7 @@ void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::s
void APIServer::on_climate_update(climate::Climate *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
for (auto *c : this->clients_)
c->send_climate_state(obj);
}
#endif
@@ -245,7 +251,7 @@ void APIServer::on_climate_update(climate::Climate *obj) {
void APIServer::on_number_update(number::Number *obj, float state) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
for (auto *c : this->clients_)
c->send_number_state(obj, state);
}
#endif
@@ -254,7 +260,7 @@ void APIServer::on_number_update(number::Number *obj, float state) {
void APIServer::on_select_update(select::Select *obj, const std::string &state) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
for (auto *c : this->clients_)
c->send_select_state(obj, state);
}
#endif
@@ -265,7 +271,7 @@ APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-c
void APIServer::set_password(const std::string &password) { this->password_ = password; }
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
for (auto &client : this->clients_) {
for (auto *client : this->clients_) {
client->send_homeassistant_service_call(call);
}
}
@@ -285,7 +291,7 @@ uint16_t APIServer::get_port() const { return this->port_; }
void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
#ifdef USE_HOMEASSISTANT_TIME
void APIServer::request_time() {
for (auto &client : this->clients_) {
for (auto *client : this->clients_) {
if (!client->remove_ && client->connection_state_ == APIConnection::ConnectionState::CONNECTED)
client->send_time_request();
}
@@ -293,7 +299,7 @@ void APIServer::request_time() {
#endif
bool APIServer::is_connected() const { return !this->clients_.empty(); }
void APIServer::on_shutdown() {
for (auto &c : this->clients_) {
for (auto *c : this->clients_) {
c->send_disconnect_request(DisconnectRequest());
}
delay(10);

View File

@@ -32,8 +32,12 @@ class APIServer : public Component, public Controller {
void set_reboot_timeout(uint32_t reboot_timeout);
#ifdef USE_API_NOISE
void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(psk); }
std::shared_ptr<APINoiseContext> get_noise_ctx() { return noise_ctx_; }
void set_noise_psk(psk_t psk) {
noise_ctx_->set_psk(std::move(psk));
}
std::shared_ptr<APINoiseContext> get_noise_ctx() {
return noise_ctx_;
}
#endif // USE_API_NOISE
void handle_disconnect(APIConnection *conn);
@@ -91,7 +95,7 @@ class APIServer : public Component, public Controller {
uint16_t port_{6053};
uint32_t reboot_timeout_{300000};
uint32_t last_connected_{0};
std::vector<std::unique_ptr<APIConnection>> clients_;
std::vector<APIConnection *> clients_;
std::string password_;
std::vector<HomeAssistantStateSubscription> state_subs_;
std::vector<UserServiceDescriptor *> user_services_;

View File

@@ -1,72 +0,0 @@
import asyncio
import logging
from datetime import datetime
from typing import Optional
from aioesphomeapi import APIClient, ReconnectLogic, APIConnectionError, LogLevel
import zeroconf
from esphome.const import CONF_KEY, CONF_PORT, CONF_PASSWORD, __version__
from esphome.util import safe_print
from . import CONF_ENCRYPTION
_LOGGER = logging.getLogger(__name__)
async def async_run_logs(config, address):
conf = config["api"]
port: int = int(conf[CONF_PORT])
password: str = conf[CONF_PASSWORD]
noise_psk: Optional[str] = None
if CONF_ENCRYPTION in conf:
noise_psk = conf[CONF_ENCRYPTION][CONF_KEY]
_LOGGER.info("Starting log output from %s using esphome API", address)
zc = zeroconf.Zeroconf()
cli = APIClient(
address,
port,
password,
client_info=f"ESPHome Logs {__version__}",
noise_psk=noise_psk,
)
first_connect = True
def on_log(msg):
time_ = datetime.now().time().strftime("[%H:%M:%S]")
text = msg.message.decode("utf8", "backslashreplace")
safe_print(time_ + text)
async def on_connect():
nonlocal first_connect
try:
await cli.subscribe_logs(
on_log,
log_level=LogLevel.LOG_LEVEL_VERY_VERBOSE,
dump_config=first_connect,
)
first_connect = False
except APIConnectionError:
cli.disconnect()
async def on_disconnect():
_LOGGER.warning("Disconnected from API")
zc = zeroconf.Zeroconf()
reconnect = ReconnectLogic(
client=cli,
on_connect=on_connect,
on_disconnect=on_disconnect,
zeroconf_instance=zc,
)
await reconnect.start()
try:
while True:
await asyncio.sleep(60)
except KeyboardInterrupt:
await reconnect.stop()
zc.close()
def run_logs(config, address):
asyncio.run(async_run_logs(config, address))

View File

@@ -49,7 +49,7 @@ class CustomAPIDevice {
template<typename T, typename... Ts>
void register_service(void (T::*callback)(Ts...), const std::string &name,
const std::array<std::string, sizeof...(Ts)> &arg_names) {
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback);
global_api_server->register_user_service(service);
}
@@ -72,7 +72,7 @@ class CustomAPIDevice {
* @param name The name of the arguments for the service, must match the arguments of the function.
*/
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback);
global_api_server->register_user_service(service);
}

View File

@@ -8,18 +8,6 @@
namespace esphome {
namespace api {
template<typename... X> class TemplatableStringValue : public TemplatableValue<std::string, X...> {
public:
TemplatableStringValue() : TemplatableValue<std::string, X...>() {}
template<typename F, enable_if_t<!is_callable<F, X...>::value, int> = 0>
TemplatableStringValue(F value) : TemplatableValue<std::string, X...>(value) {}
template<typename F, enable_if_t<is_callable<F, X...>::value, int> = 0>
TemplatableStringValue(F f)
: TemplatableValue<std::string, X...>([f](X... x) -> std::string { return to_string(f(x...)); }) {}
};
template<typename... Ts> class TemplatableKeyValuePair {
public:
template<typename T> TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {}
@@ -31,8 +19,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
public:
explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent), is_event_(is_event) {}
template<typename T> void set_service(T service) { this->service_ = service; }
TEMPLATABLE_STRING_VALUE(service);
template<typename T> void add_data(std::string key, T value) {
this->data_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
}
@@ -71,7 +58,6 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
protected:
APIServer *parent_;
bool is_event_;
TemplatableStringValue<Ts...> service_{};
std::vector<TemplatableKeyValuePair<Ts...>> data_;
std::vector<TemplatableKeyValuePair<Ts...>> data_template_;
std::vector<TemplatableKeyValuePair<Ts...>> variables_;

View File

@@ -27,9 +27,6 @@ bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { return this->clie
#ifdef USE_SWITCH
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); }
#endif
#ifdef USE_BUTTON
bool ListEntitiesIterator::on_button(button::Button *button) { return this->client_->send_button_info(button); }
#endif
#ifdef USE_TEXT_SENSOR
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
return this->client_->send_text_sensor_info(text_sensor);

View File

@@ -30,9 +30,6 @@ class ListEntitiesIterator : public ComponentIterator {
#ifdef USE_SWITCH
bool on_switch(switch_::Switch *a_switch) override;
#endif
#ifdef USE_BUTTON
bool on_button(button::Button *button) override;
#endif
#ifdef USE_TEXT_SENSOR
bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
#endif

View File

@@ -246,7 +246,6 @@ class ProtoWriteBuffer {
class ProtoMessage {
public:
virtual ~ProtoMessage() = default;
virtual void encode(ProtoWriteBuffer buffer) const = 0;
void decode(const uint8_t *buffer, size_t length);
#ifdef HAS_PROTO_MESSAGE_DUMP

View File

@@ -31,9 +31,6 @@ class InitialStateIterator : public ComponentIterator {
#ifdef USE_SWITCH
bool on_switch(switch_::Switch *a_switch) override;
#endif
#ifdef USE_BUTTON
bool on_button(button::Button *button) override { return true; };
#endif
#ifdef USE_TEXT_SENSOR
bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
#endif

View File

@@ -116,21 +116,6 @@ void ComponentIterator::advance() {
}
break;
#endif
#ifdef USE_BUTTON
case IteratorState::BUTTON:
if (this->at_ >= App.get_buttons().size()) {
advance_platform = true;
} else {
auto *button = App.get_buttons()[this->at_];
if (button->is_internal()) {
success = true;
break;
} else {
success = this->on_button(button);
}
}
break;
#endif
#ifdef USE_TEXT_SENSOR
case IteratorState::TEXT_SENSOR:
if (this->at_ >= App.get_text_sensors().size()) {

View File

@@ -38,9 +38,6 @@ class ComponentIterator {
#ifdef USE_SWITCH
virtual bool on_switch(switch_::Switch *a_switch) = 0;
#endif
#ifdef USE_BUTTON
virtual bool on_button(button::Button *button) = 0;
#endif
#ifdef USE_TEXT_SENSOR
virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0;
#endif
@@ -81,9 +78,6 @@ class ComponentIterator {
#ifdef USE_SWITCH
SWITCH,
#endif
#ifdef USE_BUTTON
BUTTON,
#endif
#ifdef USE_TEXT_SENSOR
TEXT_SENSOR,
#endif

View File

@@ -1,7 +1,6 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/binary_sensor/binary_sensor.h"

View File

@@ -25,12 +25,8 @@ void I2CAS3935Component::write_register(uint8_t reg, uint8_t mask, uint8_t bits,
uint8_t I2CAS3935Component::read_register(uint8_t reg) {
uint8_t value;
if (write(&reg, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Writing register failed!");
return 0;
}
if (read(&value, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Reading register failed!");
if (!this->read_byte(reg, &value, 2)) {
ESP_LOGW(TAG, "Read failed!");
return 0;
}
return value;

View File

@@ -1,15 +1,9 @@
# Dummy integration to allow relying on AsyncTCP
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.core import CORE, coroutine_with_priority
CODEOWNERS = ["@OttoWinter"]
CONFIG_SCHEMA = cv.All(
cv.Schema({}),
cv.only_with_arduino,
)
@coroutine_with_priority(200.0)
async def to_code(config):
@@ -18,4 +12,4 @@ async def to_code(config):
cg.add_library("esphome/AsyncTCP-esphome", "1.2.2")
elif CORE.is_esp8266:
# https://github.com/OttoWinter/ESPAsyncTCP
cg.add_library("ottowinter/ESPAsyncTCP-esphome", "1.2.3")
cg.add_library("ESPAsyncTCP-esphome", "1.2.3")

View File

@@ -1,7 +1,7 @@
#include "atc_mithermometer.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace atc_mithermometer {
@@ -25,14 +25,14 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device
bool success = false;
for (auto &service_data : device.get_service_datas()) {
auto res = parse_header_(service_data);
if (!res.has_value()) {
auto res = parse_header(service_data);
if (res->is_duplicate) {
continue;
}
if (!(parse_message_(service_data.data, *res))) {
if (!(parse_message(service_data.data, *res))) {
continue;
}
if (!(report_results_(res, device.address_str()))) {
if (!(report_results(res, device.address_str()))) {
continue;
}
if (res->temperature.has_value() && this->temperature_ != nullptr)
@@ -46,10 +46,14 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device
success = true;
}
return success;
if (!success) {
return false;
}
return true;
}
optional<ParseResult> ATCMiThermometer::parse_header_(const esp32_ble_tracker::ServiceData &service_data) {
optional<ParseResult> ATCMiThermometer::parse_header(const esp32_ble_tracker::ServiceData &service_data) {
ParseResult result;
if (!service_data.uuid.contains(0x1A, 0x18)) {
ESP_LOGVV(TAG, "parse_header(): no service data UUID magic bytes.");
@@ -60,15 +64,17 @@ optional<ParseResult> ATCMiThermometer::parse_header_(const esp32_ble_tracker::S
static uint8_t last_frame_count = 0;
if (last_frame_count == raw[12]) {
ESP_LOGVV(TAG, "parse_header(): duplicate data packet received (%hhu).", last_frame_count);
ESP_LOGVV(TAG, "parse_header(): duplicate data packet received (%d).", static_cast<int>(last_frame_count));
result.is_duplicate = true;
return {};
}
last_frame_count = raw[12];
result.is_duplicate = false;
return result;
}
bool ATCMiThermometer::parse_message_(const std::vector<uint8_t> &message, ParseResult &result) {
bool ATCMiThermometer::parse_message(const std::vector<uint8_t> &message, ParseResult &result) {
// Byte 0-5 mac in correct order
// Byte 6-7 Temperature in uint16
// Byte 8 Humidity in percent
@@ -101,7 +107,7 @@ bool ATCMiThermometer::parse_message_(const std::vector<uint8_t> &message, Parse
return true;
}
bool ATCMiThermometer::report_results_(const optional<ParseResult> &result, const std::string &address) {
bool ATCMiThermometer::report_results(const optional<ParseResult> &result, const std::string &address) {
if (!result.has_value()) {
ESP_LOGVV(TAG, "report_results(): no results available.");
return false;

View File

@@ -4,7 +4,7 @@
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace atc_mithermometer {
@@ -14,6 +14,7 @@ struct ParseResult {
optional<float> humidity;
optional<float> battery_level;
optional<float> battery_voltage;
bool is_duplicate;
int raw_offset;
};
@@ -36,9 +37,9 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice
sensor::Sensor *battery_level_{nullptr};
sensor::Sensor *battery_voltage_{nullptr};
optional<ParseResult> parse_header_(const esp32_ble_tracker::ServiceData &service_data);
bool parse_message_(const std::vector<uint8_t> &message, ParseResult &result);
bool report_results_(const optional<ParseResult> &result, const std::string &address);
optional<ParseResult> parse_header(const esp32_ble_tracker::ServiceData &service_data);
bool parse_message(const std::vector<uint8_t> &message, ParseResult &result);
bool report_results(const optional<ParseResult> &result, const std::string &address);
};
} // namespace atc_mithermometer

View File

@@ -12,7 +12,6 @@ from esphome.const import (
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
@@ -50,14 +49,12 @@ CONFIG_SCHEMA = (
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)

View File

@@ -265,57 +265,27 @@ float ATM90E32Component::get_power_factor_c_() {
}
float ATM90E32Component::get_forward_active_energy_a_() {
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYA);
if ((UINT32_MAX - this->phase_[0].cumulative_forward_active_energy_) > val) {
this->phase_[0].cumulative_forward_active_energy_ += val;
} else {
this->phase_[0].cumulative_forward_active_energy_ = val;
}
return ((float) this->phase_[0].cumulative_forward_active_energy_ * 10 / 3200);
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);
if (UINT32_MAX - this->phase_[1].cumulative_forward_active_energy_ > val) {
this->phase_[1].cumulative_forward_active_energy_ += val;
} else {
this->phase_[1].cumulative_forward_active_energy_ = val;
}
return ((float) this->phase_[1].cumulative_forward_active_energy_ * 10 / 3200);
return (float) val * 10 / 3200;
}
float ATM90E32Component::get_forward_active_energy_c_() {
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYC);
if (UINT32_MAX - this->phase_[2].cumulative_forward_active_energy_ > val) {
this->phase_[2].cumulative_forward_active_energy_ += val;
} else {
this->phase_[2].cumulative_forward_active_energy_ = val;
}
return ((float) this->phase_[2].cumulative_forward_active_energy_ * 10 / 3200);
return (float) val * 10 / 3200;
}
float ATM90E32Component::get_reverse_active_energy_a_() {
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYA);
if (UINT32_MAX - this->phase_[0].cumulative_reverse_active_energy_ > val) {
this->phase_[0].cumulative_reverse_active_energy_ += val;
} else {
this->phase_[0].cumulative_reverse_active_energy_ = val;
}
return ((float) this->phase_[0].cumulative_reverse_active_energy_ * 10 / 3200);
return (float) val * 10 / 3200;
}
float ATM90E32Component::get_reverse_active_energy_b_() {
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYB);
if (UINT32_MAX - this->phase_[1].cumulative_reverse_active_energy_ > val) {
this->phase_[1].cumulative_reverse_active_energy_ += val;
} else {
this->phase_[1].cumulative_reverse_active_energy_ = val;
}
return ((float) this->phase_[1].cumulative_reverse_active_energy_ * 10 / 3200);
return (float) val * 10 / 3200;
}
float ATM90E32Component::get_reverse_active_energy_c_() {
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYC);
if (UINT32_MAX - this->phase_[2].cumulative_reverse_active_energy_ > val) {
this->phase_[2].cumulative_reverse_active_energy_ += val;
} else {
this->phase_[2].cumulative_reverse_active_energy_ = val;
}
return ((float) this->phase_[2].cumulative_reverse_active_energy_ * 10 / 3200);
return (float) val * 10 / 3200;
}
float ATM90E32Component::get_frequency_() {
uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ);

View File

@@ -77,8 +77,6 @@ class ATM90E32Component : public PollingComponent,
sensor::Sensor *power_factor_sensor_{nullptr};
sensor::Sensor *forward_active_energy_sensor_{nullptr};
sensor::Sensor *reverse_active_energy_sensor_{nullptr};
uint32_t cumulative_forward_active_energy_{0};
uint32_t cumulative_reverse_active_energy_{0};
} phase_[3];
sensor::Sensor *freq_sensor_{nullptr};
sensor::Sensor *chip_temperature_sensor_{nullptr};

View File

@@ -17,11 +17,10 @@ from esphome.const import (
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_LIGHTBULB,
ICON_CURRENT_AC,
LAST_RESET_TYPE_AUTO,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_HERTZ,
UNIT_VOLT,
UNIT_AMPERE,
@@ -95,13 +94,15 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
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_TOTAL_INCREASING,
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,
@@ -126,7 +127,6 @@ CONFIG_SCHEMA = (
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum(

View File

@@ -1,7 +1,7 @@
#include "b_parasite.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace b_parasite {
@@ -14,7 +14,6 @@ void BParasite::dump_config() {
LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Humidity", this->humidity_);
LOG_SENSOR(" ", "Soil Moisture", this->soil_moisture_);
LOG_SENSOR(" ", "Illuminance", this->illuminance_);
}
bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
@@ -37,15 +36,6 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
const auto &data = service_data.data;
const uint8_t protocol_version = data[0] >> 4;
if (protocol_version != 1) {
ESP_LOGE(TAG, "Unsupported protocol version: %u", protocol_version);
return false;
}
// Some b-parasite versions have an (optional) illuminance sensor.
bool has_illuminance = data[0] & 0x1;
// Counter for deduplicating messages.
uint8_t counter = data[1] & 0x0f;
if (last_processed_counter_ == counter) {
@@ -69,9 +59,6 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
uint16_t soil_moisture = data[8] << 8 | data[9];
float moisture_percent = (100.0f * soil_moisture) / (1 << 16);
// Ambient light in lux.
float illuminance = has_illuminance ? data[16] << 8 | data[17] : 0.0f;
if (battery_voltage_ != nullptr) {
battery_voltage_->publish_state(battery_voltage);
}
@@ -84,13 +71,6 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
if (soil_moisture_ != nullptr) {
soil_moisture_->publish_state(moisture_percent);
}
if (illuminance_ != nullptr) {
if (has_illuminance) {
illuminance_->publish_state(illuminance);
} else {
ESP_LOGE(TAG, "No lux information is present in the BLE packet");
}
}
last_processed_counter_ = counter;
return true;
@@ -99,4 +79,4 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
} // namespace b_parasite
} // namespace esphome
#endif // USE_ESP32
#endif // ARDUINO_ARCH_ESP32

View File

@@ -4,7 +4,7 @@
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace b_parasite {
@@ -22,7 +22,6 @@ class BParasite : public Component, public esp32_ble_tracker::ESPBTDeviceListene
void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
void set_soil_moisture(sensor::Sensor *soil_moisture) { soil_moisture_ = soil_moisture; }
void set_illuminance(sensor::Sensor *illuminance) { illuminance_ = illuminance; }
protected:
// The received advertisement packet contains an unsigned 4 bits wrap-around counter
@@ -33,10 +32,9 @@ class BParasite : public Component, public esp32_ble_tracker::ESPBTDeviceListene
sensor::Sensor *temperature_{nullptr};
sensor::Sensor *humidity_{nullptr};
sensor::Sensor *soil_moisture_{nullptr};
sensor::Sensor *illuminance_{nullptr};
};
} // namespace b_parasite
} // namespace esphome
#endif // USE_ESP32
#endif // ARDUINO_ARCH_ESP32

View File

@@ -5,18 +5,14 @@ from esphome.const import (
CONF_BATTERY_VOLTAGE,
CONF_HUMIDITY,
CONF_ID,
CONF_ILLUMINANCE,
CONF_MOISTURE,
CONF_MAC_ADDRESS,
CONF_TEMPERATURE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_LUX,
UNIT_PERCENT,
UNIT_VOLT,
)
@@ -52,7 +48,6 @@ CONFIG_SCHEMA = (
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_MOISTURE): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
@@ -60,12 +55,6 @@ CONFIG_SCHEMA = (
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
unit_of_measurement=UNIT_LUX,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
@@ -85,7 +74,6 @@ async def to_code(config):
(CONF_HUMIDITY, var.set_humidity),
(CONF_BATTERY_VOLTAGE, var.set_battery_voltage),
(CONF_MOISTURE, var.set_soil_moisture),
(CONF_ILLUMINANCE, var.set_illuminance),
]:
if config_key in config:
sens = await sensor.new_sensor(config[config_key])

View File

@@ -69,8 +69,7 @@ void BangBangClimate::compute_state_() {
this->switch_to_action_(climate::CLIMATE_ACTION_OFF);
return;
}
if (std::isnan(this->current_temperature) || std::isnan(this->target_temperature_low) ||
std::isnan(this->target_temperature_high)) {
if (isnan(this->current_temperature) || isnan(this->target_temperature_low) || isnan(this->target_temperature_high)) {
// if any control parameters are nan, go to OFF action (not IDLE!)
this->switch_to_action_(climate::CLIMATE_ACTION_OFF);
return;

View File

@@ -71,17 +71,13 @@ void BH1750Sensor::update() {
float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; }
void BH1750Sensor::read_data_() {
uint16_t raw_value;
if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
if (!this->parent_->raw_receive_16(this->address_, &raw_value, 1)) {
this->status_set_warning();
return;
}
raw_value = i2c::i2ctohs(raw_value);
float lx = float(raw_value) / 1.2f;
lx *= 69.0f / this->measurement_duration_;
if (this->resolution_ == BH1750_RESOLUTION_0P5_LX) {
lx /= 2.0f;
}
ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx);
this->publish_state(lx);
this->status_clear_warning();

View File

@@ -55,10 +55,7 @@ void BinaryFan::loop() {
ESP_LOGD(TAG, "Setting reverse direction: %s", ONOFF(enable));
}
}
// We need a higher priority than the FanState component to make sure that the traits are set
// when that component sets itself up.
float BinaryFan::get_setup_priority() const { return fan_->get_setup_priority() + 1.0f; }
float BinaryFan::get_setup_priority() const { return setup_priority::DATA; }
} // namespace binary
} // namespace esphome

View File

@@ -1,14 +1,15 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.cpp_helpers import setup_entity
from esphome import automation, core
from esphome.automation import Condition, maybe_simple_id
from esphome.components import mqtt
from esphome.const import (
CONF_DELAY,
CONF_DEVICE_CLASS,
CONF_DISABLED_BY_DEFAULT,
CONF_FILTERS,
CONF_ID,
CONF_INTERNAL,
CONF_INVALID_COOLDOWN,
CONF_INVERTED,
CONF_MAX_LENGTH,
@@ -44,12 +45,9 @@ from esphome.const import (
DEVICE_CLASS_POWER,
DEVICE_CLASS_PRESENCE,
DEVICE_CLASS_PROBLEM,
DEVICE_CLASS_RUNNING,
DEVICE_CLASS_SAFETY,
DEVICE_CLASS_SMOKE,
DEVICE_CLASS_SOUND,
DEVICE_CLASS_TAMPER,
DEVICE_CLASS_UPDATE,
DEVICE_CLASS_VIBRATION,
DEVICE_CLASS_WINDOW,
)
@@ -78,12 +76,9 @@ DEVICE_CLASSES = [
DEVICE_CLASS_POWER,
DEVICE_CLASS_PRESENCE,
DEVICE_CLASS_PROBLEM,
DEVICE_CLASS_RUNNING,
DEVICE_CLASS_SAFETY,
DEVICE_CLASS_SMOKE,
DEVICE_CLASS_SOUND,
DEVICE_CLASS_TAMPER,
DEVICE_CLASS_UPDATE,
DEVICE_CLASS_VIBRATION,
DEVICE_CLASS_WINDOW,
]
@@ -91,7 +86,7 @@ DEVICE_CLASSES = [
IS_PLATFORM_COMPONENT = True
binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor")
BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.EntityBase)
BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.Nameable)
BinarySensorInitiallyOff = binary_sensor_ns.class_(
"BinarySensorInitiallyOff", BinarySensor
)
@@ -234,16 +229,17 @@ def parse_multi_click_timing_str(value):
parts = value.lower().split(" ")
if len(parts) != 5:
raise cv.Invalid(
f"Multi click timing grammar consists of exactly 5 words, not {len(parts)}"
"Multi click timing grammar consists of exactly 5 words, not {}"
"".format(len(parts))
)
try:
state = cv.boolean(parts[0])
except cv.Invalid:
# pylint: disable=raise-missing-from
raise cv.Invalid(f"First word must either be ON or OFF, not {parts[0]}")
raise cv.Invalid("First word must either be ON or OFF, not {}".format(parts[0]))
if parts[1] != "for":
raise cv.Invalid(f"Second word must be 'for', got {parts[1]}")
raise cv.Invalid("Second word must be 'for', got {}".format(parts[1]))
if parts[2] == "at":
if parts[3] == "least":
@@ -252,7 +248,8 @@ def parse_multi_click_timing_str(value):
key = CONF_MAX_LENGTH
else:
raise cv.Invalid(
f"Third word after at must either be 'least' or 'most', got {parts[3]}"
"Third word after at must either be 'least' or 'most', got {}"
"".format(parts[3])
)
try:
length = cv.positive_time_period_milliseconds(parts[4])
@@ -297,11 +294,13 @@ def validate_multi_click_timing(value):
new_state = v_.get(CONF_STATE, not state)
if new_state == state:
raise cv.Invalid(
f"Timings must have alternating state. Indices {i} and {i + 1} have the same state {state}"
"Timings must have alternating state. Indices {} and {} have "
"the same state {}".format(i, i + 1, state)
)
if max_length is not None and max_length < min_length:
raise cv.Invalid(
f"Max length ({max_length}) must be larger than min length ({min_length})."
"Max length ({}) must be larger than min length ({})."
"".format(max_length, min_length)
)
state = new_state
@@ -317,8 +316,7 @@ def validate_multi_click_timing(value):
device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
BINARY_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(BinarySensor),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
@@ -379,8 +377,10 @@ BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).ex
async def setup_binary_sensor_core_(var, config):
await setup_entity(var, config)
cg.add(var.set_name(config[CONF_NAME]))
cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
if CONF_INTERNAL in config:
cg.add(var.set_internal(config[CONF_INTERNAL]))
if CONF_DEVICE_CLASS in config:
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
if CONF_INVERTED in config:

View File

@@ -4,7 +4,6 @@
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/core/hal.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
namespace esphome {

View File

@@ -42,7 +42,7 @@ void BinarySensor::send_state_internal(bool state, bool is_initial) {
}
}
std::string BinarySensor::device_class() { return ""; }
BinarySensor::BinarySensor(const std::string &name) : EntityBase(name), state(false) {}
BinarySensor::BinarySensor(const std::string &name) : Nameable(name), state(false) {}
BinarySensor::BinarySensor() : BinarySensor("") {}
void BinarySensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
std::string BinarySensor::get_device_class() {

View File

@@ -1,7 +1,6 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/helpers.h"
#include "esphome/components/binary_sensor/filter.h"
@@ -11,7 +10,7 @@ namespace binary_sensor {
#define LOG_BINARY_SENSOR(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \
if (!(obj)->get_device_class().empty()) { \
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
} \
@@ -23,7 +22,7 @@ namespace binary_sensor {
* The sub classes should notify the front-end of new states via the publish_state() method which
* handles inverted inputs for you.
*/
class BinarySensor : public EntityBase {
class BinarySensor : public Nameable {
public:
explicit BinarySensor();
/** Construct a binary sensor with the specified name

View File

@@ -3,7 +3,7 @@
#include "esphome/core/automation.h"
#include "esphome/components/ble_client/ble_client.h"
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace ble_client {
@@ -11,12 +11,11 @@ class BLEClientConnectTrigger : public Trigger<>, public BLEClientNode {
public:
explicit BLEClientConnectTrigger(BLEClient *parent) { parent->register_ble_node(this); }
void loop() override {}
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override {
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
if (event == ESP_GATTC_OPEN_EVT && param->open.status == ESP_GATT_OK)
this->trigger();
if (event == ESP_GATTC_SEARCH_CMPL_EVT)
this->node_state = espbt::ClientState::ESTABLISHED;
this->node_state = espbt::ClientState::Established;
}
};
@@ -24,12 +23,11 @@ class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode {
public:
explicit BLEClientDisconnectTrigger(BLEClient *parent) { parent->register_ble_node(this); }
void loop() override {}
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override {
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
if (event == ESP_GATTC_DISCONNECT_EVT && memcmp(param->disconnect.remote_bda, this->parent_->remote_bda, 6) == 0)
this->trigger();
if (event == ESP_GATTC_SEARCH_CMPL_EVT)
this->node_state = espbt::ClientState::ESTABLISHED;
this->node_state = espbt::ClientState::Established;
}
};

View File

@@ -4,27 +4,25 @@
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "ble_client.h"
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace ble_client {
static const char *const TAG = "ble_client";
float BLEClient::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
void BLEClient::setup() {
auto ret = esp_ble_gattc_app_register(this->app_id);
if (ret) {
ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
this->mark_failed();
}
this->set_states_(espbt::ClientState::IDLE);
this->set_states(espbt::ClientState::Idle);
this->enabled = true;
}
void BLEClient::loop() {
if (this->state() == espbt::ClientState::DISCOVERED) {
if (this->state() == espbt::ClientState::Discovered) {
this->connect();
}
for (auto *node : this->nodes_)
@@ -41,11 +39,11 @@ bool BLEClient::parse_device(const espbt::ESPBTDevice &device) {
return false;
if (device.address_uint64() != this->address)
return false;
if (this->state() != espbt::ClientState::IDLE)
if (this->state() != espbt::ClientState::Idle)
return false;
ESP_LOGD(TAG, "Found device at MAC address [%s]", device.address_str().c_str());
this->set_states_(espbt::ClientState::DISCOVERED);
this->set_states(espbt::ClientState::Discovered);
auto addr = device.address_uint64();
this->remote_bda[0] = (addr >> 40) & 0xFF;
@@ -71,7 +69,7 @@ std::string BLEClient::address_str() const {
void BLEClient::set_enabled(bool enabled) {
if (enabled == this->enabled)
return;
if (!enabled && this->state() != espbt::ClientState::IDLE) {
if (!enabled && this->state() != espbt::ClientState::Idle) {
ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str());
auto ret = esp_ble_gattc_close(this->gattc_if, this->conn_id);
if (ret) {
@@ -86,9 +84,9 @@ void BLEClient::connect() {
auto ret = esp_ble_gattc_open(this->gattc_if, this->remote_bda, BLE_ADDR_TYPE_PUBLIC, true);
if (ret) {
ESP_LOGW(TAG, "esp_ble_gattc_open error, address=%s status=%d", this->address_str().c_str(), ret);
this->set_states_(espbt::ClientState::IDLE);
this->set_states(espbt::ClientState::Idle);
} else {
this->set_states_(espbt::ClientState::CONNECTING);
this->set_states(espbt::ClientState::Connecting);
}
}
@@ -99,7 +97,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if)
return;
bool all_established = this->all_nodes_established_();
bool all_established = this->all_nodes_established();
switch (event) {
case ESP_GATTC_REG_EVT: {
@@ -115,7 +113,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
ESP_LOGV(TAG, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str());
if (param->open.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status);
this->set_states_(espbt::ClientState::IDLE);
this->set_states(espbt::ClientState::Idle);
break;
}
this->conn_id = param->open.conn_id;
@@ -128,11 +126,11 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
case ESP_GATTC_CFG_MTU_EVT: {
if (param->cfg_mtu.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "cfg_mtu to %s failed, status %d", this->address_str().c_str(), param->cfg_mtu.status);
this->set_states_(espbt::ClientState::IDLE);
this->set_states(espbt::ClientState::Idle);
break;
}
ESP_LOGV(TAG, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, param->cfg_mtu.mtu);
esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, NULL);
break;
}
case ESP_GATTC_DISCONNECT_EVT: {
@@ -141,13 +139,13 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
}
ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT", this->address_str().c_str());
for (auto &svc : this->services_)
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
delete svc;
this->services_.clear();
this->set_states_(espbt::ClientState::IDLE);
this->set_states(espbt::ClientState::Idle);
break;
}
case ESP_GATTC_SEARCH_RES_EVT: {
BLEService *ble_service = new BLEService(); // NOLINT(cppcoreguidelines-owning-memory)
BLEService *ble_service = new BLEService();
ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid);
ble_service->start_handle = param->search_res.start_handle;
ble_service->end_handle = param->search_res.end_handle;
@@ -162,8 +160,8 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
ESP_LOGI(TAG, " start_handle: 0x%x end_handle: 0x%x", svc->start_handle, svc->end_handle);
svc->parse_characteristics();
}
this->set_states_(espbt::ClientState::CONNECTED);
this->set_state(espbt::ClientState::ESTABLISHED);
this->set_states(espbt::ClientState::Connected);
this->set_state(espbt::ClientState::Established);
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
@@ -194,9 +192,9 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
node->gattc_event_handler(event, esp_gattc_if, param);
// Delete characteristics after clients have used them to save RAM.
if (!all_established && this->all_nodes_established_()) {
if (!all_established && this->all_nodes_established()) {
for (auto &svc : this->services_)
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
delete svc;
this->services_.clear();
}
}
@@ -309,7 +307,7 @@ BLEDescriptor *BLEClient::get_descriptor(uint16_t service, uint16_t chr, uint16_
BLEService::~BLEService() {
for (auto &chr : this->characteristics)
delete chr; // NOLINT(cppcoreguidelines-owning-memory)
delete chr;
}
void BLEService::parse_characteristics() {
@@ -331,7 +329,7 @@ void BLEService::parse_characteristics() {
break;
}
BLECharacteristic *characteristic = new BLECharacteristic(); // NOLINT(cppcoreguidelines-owning-memory)
BLECharacteristic *characteristic = new BLECharacteristic();
characteristic->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
characteristic->properties = result.properties;
characteristic->handle = result.char_handle;
@@ -346,7 +344,7 @@ void BLEService::parse_characteristics() {
BLECharacteristic::~BLECharacteristic() {
for (auto &desc : this->descriptors)
delete desc; // NOLINT(cppcoreguidelines-owning-memory)
delete desc;
}
void BLECharacteristic::parse_descriptors() {
@@ -368,7 +366,7 @@ void BLECharacteristic::parse_descriptors() {
break;
}
BLEDescriptor *desc = new BLEDescriptor(); // NOLINT(cppcoreguidelines-owning-memory)
BLEDescriptor *desc = new BLEDescriptor();
desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
desc->handle = result.handle;
desc->characteristic = this;
@@ -388,15 +386,6 @@ BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
}
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
auto client = this->service->client;
auto status = esp_ble_gattc_write_char(client->gattc_if, client->conn_id, this->handle, new_val_size, new_val,
ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status);
}
}
} // namespace ble_client
} // namespace esphome

View File

@@ -4,7 +4,7 @@
#include "esphome/core/helpers.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
#include <string>
#include <array>
@@ -25,7 +25,7 @@ class BLEClientNode {
public:
virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) = 0;
virtual void loop(){};
virtual void loop() = 0;
void set_address(uint64_t address) { address_ = address; }
espbt::ESPBTClient *client;
// This should be transitioned to Established once the node no longer needs
@@ -59,7 +59,7 @@ class BLECharacteristic {
void parse_descriptors();
BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
BLEDescriptor *get_descriptor(uint16_t uuid);
void write_value(uint8_t *new_val, int16_t new_val_size);
BLEService *service;
};
@@ -81,13 +81,11 @@ class BLEClient : public espbt::ESPBTClient, public Component {
void setup() override;
void dump_config() override;
void loop() override;
float get_setup_priority() const 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 gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
bool parse_device(const espbt::ESPBTDevice &device) override;
void on_scan_end() override {}
void connect() override;
void connect();
void set_address(uint64_t address) { this->address = address; }
@@ -118,16 +116,16 @@ class BLEClient : public espbt::ESPBTClient, public Component {
std::string address_str() const;
protected:
void set_states_(espbt::ClientState st) {
void set_states(espbt::ClientState st) {
this->set_state(st);
for (auto &node : nodes_)
node->node_state = st;
}
bool all_nodes_established_() {
if (this->state() != espbt::ClientState::ESTABLISHED)
bool all_nodes_established() {
if (this->state() != espbt::ClientState::Established)
return false;
for (auto &node : nodes_)
if (node->node_state != espbt::ClientState::ESTABLISHED)
if (node->node_state != espbt::ClientState::Established)
return false;
return true;
}

View File

@@ -1,67 +0,0 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import output, ble_client, esp32_ble_tracker
from esphome.const import CONF_ID, CONF_SERVICE_UUID
from .. import ble_client_ns
DEPENDENCIES = ["ble_client"]
CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
BLEBinaryOutput = ble_client_ns.class_(
"BLEBinaryOutput", output.BinaryOutput, ble_client.BLEClientNode, cg.Component
)
CONFIG_SCHEMA = cv.All(
output.BINARY_OUTPUT_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(BLEBinaryOutput),
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(ble_client.BLE_CLIENT_SCHEMA)
)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(
var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
cg.add(
var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
cg.add(var.set_service_uuid128(uuid128))
if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(
var.set_char_uuid16(
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
)
)
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
esp32_ble_tracker.bt_uuid32_format
):
cg.add(
var.set_char_uuid32(
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
)
)
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format
):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
config[CONF_CHARACTERISTIC_UUID]
)
cg.add(var.set_char_uuid128(uuid128))
yield output.register_output(var, config)
yield ble_client.register_ble_node(var, config)
yield cg.register_component(var, config)

View File

@@ -1,71 +0,0 @@
#include "ble_binary_output.h"
#include "esphome/core/log.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#ifdef USE_ESP32
namespace esphome {
namespace ble_client {
static const char *const TAG = "ble_binary_output";
void BLEBinaryOutput::dump_config() {
ESP_LOGCONFIG(TAG, "BLE Binary Output:");
ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent_->address_str().c_str());
ESP_LOGCONFIG(TAG, " Service UUID : %s", this->service_uuid_.to_string().c_str());
ESP_LOGCONFIG(TAG, " Characteristic UUID: %s", this->char_uuid_.to_string().c_str());
LOG_BINARY_OUTPUT(this);
}
void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) {
switch (event) {
case ESP_GATTC_OPEN_EVT:
this->client_state_ = espbt::ClientState::ESTABLISHED;
ESP_LOGW(TAG, "[%s] Connected successfully!", this->char_uuid_.to_string().c_str());
break;
case ESP_GATTC_DISCONNECT_EVT:
ESP_LOGW(TAG, "[%s] Disconnected", this->char_uuid_.to_string().c_str());
this->client_state_ = espbt::ClientState::IDLE;
break;
case ESP_GATTC_WRITE_CHAR_EVT: {
if (param->write.status == 0) {
break;
}
auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "[%s] Characteristic not found.", this->char_uuid_.to_string().c_str());
break;
}
if (param->write.handle == chr->handle) {
ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status);
}
break;
}
default:
break;
}
}
void BLEBinaryOutput::write_state(bool state) {
if (this->client_state_ != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "[%s] Not connected to BLE client. State update can not be written.",
this->char_uuid_.to_string().c_str());
return;
}
auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "[%s] Characteristic not found. State update can not be written.",
this->char_uuid_.to_string().c_str());
return;
}
uint8_t state_as_uint = (uint8_t) state;
ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint);
chr->write_value(&state_as_uint, sizeof(state_as_uint));
}
} // namespace ble_client
} // namespace esphome
#endif

View File

@@ -1,39 +0,0 @@
#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/output/binary_output.h"
#ifdef USE_ESP32
#include <esp_gattc_api.h>
namespace esphome {
namespace ble_client {
namespace espbt = esphome::esp32_ble_tracker;
class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, public Component {
public:
void dump_config() override;
void loop() override {}
float get_setup_priority() const override { return setup_priority::DATA; }
void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
protected:
void write_state(bool state) override;
espbt::ESPBTUUID service_uuid_;
espbt::ESPBTUUID char_uuid_;
espbt::ClientState client_state_;
};
} // namespace ble_client
} // namespace esphome
#endif

View File

@@ -67,7 +67,7 @@ async def to_code(config):
var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
cg.add(var.set_service_uuid128(uuid128))
if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
@@ -87,9 +87,7 @@ async def to_code(config):
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format
):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
config[CONF_CHARACTERISTIC_UUID]
)
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_CHARACTERISTIC_UUID])
cg.add(var.set_char_uuid128(uuid128))
if CONF_DESCRIPTOR_UUID in config:
@@ -110,9 +108,7 @@ async def to_code(config):
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format
):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
config[CONF_DESCRIPTOR_UUID]
)
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_DESCRIPTOR_UUID])
cg.add(var.set_descr_uuid128(uuid128))
if CONF_LAMBDA in config:

View File

@@ -3,7 +3,7 @@
#include "esphome/core/automation.h"
#include "esphome/components/ble_client/sensor/ble_sensor.h"
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace ble_client {
@@ -11,11 +11,10 @@ namespace ble_client {
class BLESensorNotifyTrigger : public Trigger<float>, public BLESensor {
public:
explicit BLESensorNotifyTrigger(BLESensor *sensor) { sensor_ = sensor; }
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 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_SEARCH_CMPL_EVT: {
this->sensor_->node_state = espbt::ClientState::ESTABLISHED;
this->sensor_->node_state = espbt::ClientState::Established;
break;
}
case ESP_GATTC_NOTIFY_EVT: {

View File

@@ -4,7 +4,7 @@
#include "esphome/core/helpers.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace ble_client {
@@ -71,7 +71,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status);
}
} else {
this->node_state = espbt::ClientState::ESTABLISHED;
this->node_state = espbt::ClientState::Established;
}
break;
}
@@ -84,7 +84,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
}
if (param->read.handle == this->handle) {
this->status_clear_warning();
this->publish_state(this->parse_data_(param->read.value, param->read.value_len));
this->publish_state(this->parse_data(param->read.value, param->read.value_len));
}
break;
}
@@ -93,11 +93,11 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
break;
ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(),
param->notify.handle, param->notify.value[0]);
this->publish_state(this->parse_data_(param->notify.value, param->notify.value_len));
this->publish_state(this->parse_data(param->notify.value, param->notify.value_len));
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
this->node_state = espbt::ClientState::ESTABLISHED;
this->node_state = espbt::ClientState::Established;
break;
}
default:
@@ -105,7 +105,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
}
}
float BLESensor::parse_data_(uint8_t *value, uint16_t value_len) {
float BLESensor::parse_data(uint8_t *value, uint16_t value_len) {
if (this->data_to_value_func_.has_value()) {
std::vector<uint8_t> data(value, value + value_len);
return (*this->data_to_value_func_)(data);
@@ -115,7 +115,7 @@ float BLESensor::parse_data_(uint8_t *value, uint16_t value_len) {
}
void BLESensor::update() {
if (this->node_state != espbt::ClientState::ESTABLISHED) {
if (this->node_state != espbt::ClientState::Established) {
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str());
return;
}

View File

@@ -5,7 +5,7 @@
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/sensor/sensor.h"
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
#include <esp_gattc_api.h>
namespace esphome {
@@ -32,13 +32,13 @@ class BLESensor : public sensor::Sensor, public PollingComponent, public BLEClie
void set_descr_uuid16(uint16_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
void set_descr_uuid32(uint32_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
void set_descr_uuid128(uint8_t *uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
void set_data_to_value(data_to_value_t &&lambda) { this->data_to_value_func_ = lambda; }
void set_data_to_value(data_to_value_t &&lambda_) { this->data_to_value_func_ = lambda_; }
void set_enable_notify(bool notify) { this->notify_ = notify; }
uint16_t handle;
protected:
uint32_t hash_base() override;
float parse_data_(uint8_t *value, uint16_t value_len);
float parse_data(uint8_t *value, uint16_t value_len);
optional<data_to_value_t> data_to_value_func_{};
bool notify_;
espbt::ESPBTUUID service_uuid_;

View File

@@ -2,7 +2,7 @@
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace ble_client {
@@ -21,10 +21,10 @@ void BLEClientSwitch::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
this->publish_state(this->parent_->enabled);
break;
case ESP_GATTC_OPEN_EVT:
this->node_state = espbt::ClientState::ESTABLISHED;
this->node_state = espbt::ClientState::Established;
break;
case ESP_GATTC_DISCONNECT_EVT:
this->node_state = espbt::ClientState::IDLE;
this->node_state = espbt::ClientState::Idle;
this->publish_state(this->parent_->enabled);
break;
default:

View File

@@ -5,7 +5,7 @@
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/switch/switch.h"
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
#include <esp_gattc_api.h>
namespace esphome {

View File

@@ -1,14 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor, esp32_ble_tracker
from esphome.const import (
CONF_MAC_ADDRESS,
CONF_SERVICE_UUID,
CONF_IBEACON_MAJOR,
CONF_IBEACON_MINOR,
CONF_IBEACON_UUID,
CONF_ID,
)
from esphome.const import CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_ID
DEPENDENCIES = ["esp32_ble_tracker"]
@@ -20,30 +13,17 @@ BLEPresenceDevice = ble_presence_ns.class_(
esp32_ble_tracker.ESPBTDeviceListener,
)
def _validate(config):
if CONF_IBEACON_MAJOR in config and CONF_IBEACON_UUID not in config:
raise cv.Invalid("iBeacon major identifier requires iBeacon UUID")
if CONF_IBEACON_MINOR in config and CONF_IBEACON_UUID not in config:
raise cv.Invalid("iBeacon minor identifier requires iBeacon UUID")
return config
CONFIG_SCHEMA = cv.All(
binary_sensor.BINARY_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(BLEPresenceDevice),
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t,
cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t,
cv.Optional(CONF_IBEACON_UUID): cv.uuid,
}
)
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA),
cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_IBEACON_UUID),
_validate,
cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID),
)
@@ -70,15 +50,5 @@ async def to_code(config):
)
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
cg.add(var.set_service_uuid128(uuid128))
if CONF_IBEACON_UUID in config:
ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(config[CONF_IBEACON_UUID]))
cg.add(var.set_ibeacon_uuid(ibeacon_uuid))
if CONF_IBEACON_MAJOR in config:
cg.add(var.set_ibeacon_major(config[CONF_IBEACON_MAJOR]))
if CONF_IBEACON_MINOR in config:
cg.add(var.set_ibeacon_minor(config[CONF_IBEACON_MINOR]))

View File

@@ -1,7 +1,7 @@
#include "ble_presence_device.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace ble_presence {

View File

@@ -4,7 +4,7 @@
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace ble_presence {
@@ -14,78 +14,41 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
public Component {
public:
void set_address(uint64_t address) {
this->match_by_ = MATCH_BY_MAC_ADDRESS;
this->by_address_ = true;
this->address_ = address;
}
void set_service_uuid16(uint16_t uuid) {
this->match_by_ = MATCH_BY_SERVICE_UUID;
this->by_address_ = false;
this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint16(uuid);
}
void set_service_uuid32(uint32_t uuid) {
this->match_by_ = MATCH_BY_SERVICE_UUID;
this->by_address_ = false;
this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint32(uuid);
}
void set_service_uuid128(uint8_t *uuid) {
this->match_by_ = MATCH_BY_SERVICE_UUID;
this->by_address_ = false;
this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(uuid);
}
void set_ibeacon_uuid(uint8_t *uuid) {
this->match_by_ = MATCH_BY_IBEACON_UUID;
this->ibeacon_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(uuid);
}
void set_ibeacon_major(uint16_t major) {
this->check_ibeacon_major_ = true;
this->ibeacon_major_ = major;
}
void set_ibeacon_minor(uint16_t minor) {
this->check_ibeacon_minor_ = true;
this->ibeacon_minor_ = minor;
}
void on_scan_end() override {
if (!this->found_)
this->publish_state(false);
this->found_ = false;
}
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
switch (this->match_by_) {
case MATCH_BY_MAC_ADDRESS:
if (device.address_uint64() == this->address_) {
this->publish_state(true);
if (this->by_address_) {
if (device.address_uint64() == this->address_) {
this->publish_state(true);
this->found_ = true;
return true;
}
} else {
for (auto uuid : device.get_service_uuids()) {
if (this->uuid_ == uuid) {
this->publish_state(device.get_rssi());
this->found_ = true;
return true;
}
break;
case MATCH_BY_SERVICE_UUID:
for (auto uuid : device.get_service_uuids()) {
if (this->uuid_ == uuid) {
this->publish_state(device.get_rssi());
this->found_ = true;
return true;
}
}
break;
case MATCH_BY_IBEACON_UUID:
if (!device.get_ibeacon().has_value()) {
return false;
}
auto ibeacon = device.get_ibeacon().value();
if (this->ibeacon_uuid_ != ibeacon.get_uuid()) {
return false;
}
if (this->check_ibeacon_major_ && this->ibeacon_major_ != ibeacon.get_major()) {
return false;
}
if (this->check_ibeacon_minor_ && this->ibeacon_minor_ != ibeacon.get_minor()) {
return false;
}
this->publish_state(device.get_rssi());
this->found_ = true;
return true;
}
}
return false;
}
@@ -93,20 +56,10 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
float get_setup_priority() const override { return setup_priority::DATA; }
protected:
enum MatchType { MATCH_BY_MAC_ADDRESS, MATCH_BY_SERVICE_UUID, MATCH_BY_IBEACON_UUID };
MatchType match_by_;
bool found_{false};
bool by_address_{false};
uint64_t address_;
esp32_ble_tracker::ESPBTUUID uuid_;
esp32_ble_tracker::ESPBTUUID ibeacon_uuid_;
uint16_t ibeacon_major_;
bool check_ibeacon_major_;
uint16_t ibeacon_minor_;
bool check_ibeacon_minor_;
};
} // namespace ble_presence

View File

@@ -1,7 +1,7 @@
#include "ble_rssi_sensor.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace ble_rssi {

View File

@@ -4,7 +4,7 @@
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/sensor/sensor.h"
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace ble_rssi {

View File

@@ -60,5 +60,5 @@ async def to_code(config):
)
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
cg.add(var.set_service_uuid128(uuid128))

View File

@@ -1,7 +1,7 @@
#include "ble_scanner.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace ble_scanner {

View File

@@ -7,7 +7,7 @@
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/text_sensor/text_sensor.h"
#ifdef USE_ESP32
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace ble_scanner {
@@ -15,7 +15,7 @@ namespace ble_scanner {
class BLEScanner : public text_sensor::TextSensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component {
public:
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
this->publish_state("{\"timestamp\":" + to_string(::time(nullptr)) +
this->publish_state("{\"timestamp\":" + to_string(::time(NULL)) +
","
"\"address\":\"" +
device.address_str() +

View File

@@ -33,7 +33,6 @@ static const uint8_t BME280_REGISTER_CONTROLHUMID = 0xF2;
static const uint8_t BME280_REGISTER_STATUS = 0xF3;
static const uint8_t BME280_REGISTER_CONTROL = 0xF4;
static const uint8_t BME280_REGISTER_CONFIG = 0xF5;
static const uint8_t BME280_REGISTER_MEASUREMENTS = 0xF7;
static const uint8_t BME280_REGISTER_PRESSUREDATA = 0xF7;
static const uint8_t BME280_REGISTER_TEMPDATA = 0xFA;
static const uint8_t BME280_REGISTER_HUMIDDATA = 0xFD;
@@ -114,14 +113,14 @@ void BME280Component::setup() {
this->calibration_.h5 = read_u8_(BME280_REGISTER_DIG_H5 + 1) << 4 | (read_u8_(BME280_REGISTER_DIG_H5) >> 4);
this->calibration_.h6 = read_u8_(BME280_REGISTER_DIG_H6);
uint8_t humid_control_val = 0;
if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_control_val)) {
uint8_t humid_register = 0;
if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_register)) {
this->mark_failed();
return;
}
humid_control_val &= ~0b00000111;
humid_control_val |= this->humidity_oversampling_ & 0b111;
if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_control_val)) {
humid_register &= ~0b00000111;
humid_register |= this->humidity_oversampling_ & 0b111;
if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_register)) {
this->mark_failed();
return;
}
@@ -170,36 +169,30 @@ inline uint8_t oversampling_to_time(BME280Oversampling over_sampling) { return (
void BME280Component::update() {
// Enable sensor
ESP_LOGV(TAG, "Sending conversion request...");
uint8_t meas_value = 0;
meas_value |= (this->temperature_oversampling_ & 0b111) << 5;
meas_value |= (this->pressure_oversampling_ & 0b111) << 2;
meas_value |= BME280_MODE_FORCED;
if (!this->write_byte(BME280_REGISTER_CONTROL, meas_value)) {
uint8_t meas_register = 0;
meas_register |= (this->temperature_oversampling_ & 0b111) << 5;
meas_register |= (this->pressure_oversampling_ & 0b111) << 2;
meas_register |= BME280_MODE_FORCED;
if (!this->write_byte(BME280_REGISTER_CONTROL, meas_register)) {
this->status_set_warning();
return;
}
float meas_time = 1.5f;
float meas_time = 1.5;
meas_time += 2.3f * oversampling_to_time(this->temperature_oversampling_);
meas_time += 2.3f * oversampling_to_time(this->pressure_oversampling_) + 0.575f;
meas_time += 2.3f * oversampling_to_time(this->humidity_oversampling_) + 0.575f;
this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
uint8_t data[8];
if (!this->read_bytes(BME280_REGISTER_MEASUREMENTS, data, 8)) {
ESP_LOGW(TAG, "Error reading registers.");
this->status_set_warning();
return;
}
int32_t t_fine = 0;
float temperature = this->read_temperature_(data, &t_fine);
if (std::isnan(temperature)) {
float temperature = this->read_temperature_(&t_fine);
if (isnan(temperature)) {
ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values.");
this->status_set_warning();
return;
}
float pressure = this->read_pressure_(data, t_fine);
float humidity = this->read_humidity_(data, t_fine);
float pressure = this->read_pressure_(t_fine);
float humidity = this->read_humidity_(t_fine);
ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity);
if (this->temperature_sensor_ != nullptr)
@@ -211,8 +204,11 @@ void BME280Component::update() {
this->status_clear_warning();
});
}
float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) {
int32_t adc = ((data[3] & 0xFF) << 16) | ((data[4] & 0xFF) << 8) | (data[5] & 0xFF);
float BME280Component::read_temperature_(int32_t *t_fine) {
uint8_t data[3];
if (!this->read_bytes(BME280_REGISTER_TEMPDATA, data, 3))
return NAN;
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
adc >>= 4;
if (adc == 0x80000)
// temperature was disabled
@@ -230,7 +226,10 @@ float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) {
return temperature / 100.0f;
}
float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) {
float BME280Component::read_pressure_(int32_t t_fine) {
uint8_t data[3];
if (!this->read_bytes(BME280_REGISTER_PRESSUREDATA, data, 3))
return NAN;
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
adc >>= 4;
if (adc == 0x80000)
@@ -266,9 +265,9 @@ float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) {
return (p / 256.0f) / 100.0f;
}
float BME280Component::read_humidity_(const uint8_t *data, int32_t t_fine) {
uint16_t raw_adc = ((data[6] & 0xFF) << 8) | (data[7] & 0xFF);
if (raw_adc == 0x8000)
float BME280Component::read_humidity_(int32_t t_fine) {
uint16_t raw_adc;
if (!this->read_byte_16(BME280_REGISTER_HUMIDDATA, &raw_adc) || raw_adc == 0x8000)
return NAN;
int32_t adc = raw_adc;

View File

@@ -82,11 +82,11 @@ class BME280Component : public PollingComponent, public i2c::I2CDevice {
protected:
/// Read the temperature value and store the calculated ambient temperature in t_fine.
float read_temperature_(const uint8_t *data, int32_t *t_fine);
float read_temperature_(int32_t *t_fine);
/// Read the pressure value in hPa using the provided t_fine value.
float read_pressure_(const uint8_t *data, int32_t t_fine);
float read_pressure_(int32_t t_fine);
/// Read the humidity value in % using the provided t_fine value.
float read_humidity_(const uint8_t *data, int32_t t_fine);
float read_humidity_(int32_t t_fine);
uint8_t read_u8_(uint8_t a_register);
uint16_t read_u16_le_(uint8_t a_register);
int16_t read_s16_le_(uint8_t a_register);

View File

@@ -1,6 +1,5 @@
#include "bme680.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace bme680 {

View File

@@ -44,8 +44,7 @@ CONFIG_SCHEMA = cv.Schema(
cv.Optional(
CONF_STATE_SAVE_INTERVAL, default="6hours"
): cv.positive_time_period_minutes,
},
cv.only_with_arduino,
}
).extend(i2c.i2c_device_schema(0x76))
@@ -61,8 +60,5 @@ async def to_code(config):
var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds)
)
# Although this component does not use SPI, the BSEC library requires the SPI library
cg.add_library("SPI", None)
cg.add_define("USE_BSEC")
cg.add_library("boschsensortec/BSEC Software Library", "1.6.1480")
cg.add_library("BSEC Software Library", "1.6.1480")

View File

@@ -10,7 +10,7 @@ static const char *const TAG = "bme680_bsec.sensor";
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
BME680BSECComponent *BME680BSECComponent::instance; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
BME680BSECComponent *BME680BSECComponent::instance;
void BME680BSECComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up BME680 via BSEC...");
@@ -359,7 +359,7 @@ void BME680BSECComponent::publish_sensor_state_(sensor::Sensor *sensor, float va
sensor->publish_state(value);
}
void BME680BSECComponent::publish_sensor_state_(text_sensor::TextSensor *sensor, const std::string &value) {
void BME680BSECComponent::publish_sensor_state_(text_sensor::TextSensor *sensor, std::string value) {
if (!sensor || (sensor->has_state() && sensor->state == value)) {
return;
}
@@ -381,7 +381,7 @@ void BME680BSECComponent::delay_ms(uint32_t period) {
void BME680BSECComponent::load_state_() {
uint32_t hash = fnv1_hash("bme680_bsec_state_" + to_string(this->address_));
this->bsec_state_ = global_preferences->make_preference<uint8_t[BSEC_MAX_STATE_BLOB_SIZE]>(hash, true);
this->bsec_state_ = global_preferences.make_preference<uint8_t[BSEC_MAX_STATE_BLOB_SIZE]>(hash, true);
uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
if (this->bsec_state_.load(&state)) {

View File

@@ -5,7 +5,6 @@
#include "esphome/components/text_sensor/text_sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/core/preferences.h"
#include "esphome/core/defines.h"
#include <map>
#ifdef USE_BSEC
@@ -71,7 +70,7 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice {
int64_t get_time_ns_();
void publish_sensor_state_(sensor::Sensor *sensor, float value, bool change_only = false);
void publish_sensor_state_(text_sensor::TextSensor *sensor, const std::string &value);
void publish_sensor_state_(text_sensor::TextSensor *sensor, std::string value);
void load_state_();
void save_state_(uint8_t accuracy);

View File

@@ -123,11 +123,11 @@ inline uint8_t oversampling_to_time(BMP280Oversampling over_sampling) { return (
void BMP280Component::update() {
// Enable sensor
ESP_LOGV(TAG, "Sending conversion request...");
uint8_t meas_value = 0;
meas_value |= (this->temperature_oversampling_ & 0b111) << 5;
meas_value |= (this->pressure_oversampling_ & 0b111) << 2;
meas_value |= 0b01; // Forced mode
if (!this->write_byte(BMP280_REGISTER_CONTROL, meas_value)) {
uint8_t meas_register = 0;
meas_register |= (this->temperature_oversampling_ & 0b111) << 5;
meas_register |= (this->pressure_oversampling_ & 0b111) << 2;
meas_register |= 0b01; // Forced mode
if (!this->write_byte(BMP280_REGISTER_CONTROL, meas_register)) {
this->status_set_warning();
return;
}
@@ -139,7 +139,7 @@ void BMP280Component::update() {
this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
int32_t t_fine = 0;
float temperature = this->read_temperature_(&t_fine);
if (std::isnan(temperature)) {
if (isnan(temperature)) {
ESP_LOGW(TAG, "Invalid temperature, cannot read pressure values.");
this->status_set_warning();
return;

View File

@@ -1,127 +0,0 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import maybe_simple_id
from esphome.components import mqtt
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_ID,
CONF_ON_PRESS,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
DEVICE_CLASS_RESTART,
DEVICE_CLASS_UPDATE,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@esphome/core"]
IS_PLATFORM_COMPONENT = True
DEVICE_CLASSES = [
DEVICE_CLASS_RESTART,
DEVICE_CLASS_UPDATE,
]
button_ns = cg.esphome_ns.namespace("button")
Button = button_ns.class_("Button", cg.EntityBase)
ButtonPtr = Button.operator("ptr")
PressAction = button_ns.class_("PressAction", automation.Action)
ButtonPressTrigger = button_ns.class_(
"ButtonPressTrigger", automation.Trigger.template()
)
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
BUTTON_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger),
}
),
}
)
_UNDEF = object()
def button_schema(
icon: str = _UNDEF,
entity_category: str = _UNDEF,
device_class: str = _UNDEF,
) -> cv.Schema:
schema = BUTTON_SCHEMA
if icon is not _UNDEF:
schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon})
if entity_category is not _UNDEF:
schema = schema.extend(
{
cv.Optional(
CONF_ENTITY_CATEGORY, default=entity_category
): cv.entity_category
}
)
if device_class is not _UNDEF:
schema = schema.extend(
{
cv.Optional(
CONF_DEVICE_CLASS, default=device_class
): validate_device_class
}
)
return schema
async def setup_button_core_(var, config):
await setup_entity(var, config)
for conf in config.get(CONF_ON_PRESS, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
if CONF_DEVICE_CLASS in config:
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
if CONF_MQTT_ID in config:
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
await mqtt.register_mqtt_component(mqtt_, config)
async def register_button(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_button(var))
await setup_button_core_(var, config)
async def new_button(config):
var = cg.new_Pvariable(config[CONF_ID])
await register_button(var, config)
return var
BUTTON_PRESS_SCHEMA = maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(Button),
}
)
@automation.register_action("button.press", PressAction, BUTTON_PRESS_SCHEMA)
async def button_press_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(button_ns.using)
cg.add_define("USE_BUTTON")

View File

@@ -1,28 +0,0 @@
#pragma once
#include "esphome/components/button/button.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
namespace esphome {
namespace button {
template<typename... Ts> class PressAction : public Action<Ts...> {
public:
explicit PressAction(Button *button) : button_(button) {}
void play(Ts... x) override { this->button_->press(); }
protected:
Button *button_;
};
class ButtonPressTrigger : public Trigger<> {
public:
ButtonPressTrigger(Button *button) {
button->add_on_press_callback([this]() { this->trigger(); });
}
};
} // namespace button
} // namespace esphome

View File

@@ -1,28 +0,0 @@
#include "button.h"
#include "esphome/core/log.h"
namespace esphome {
namespace button {
static const char *const TAG = "button";
Button::Button(const std::string &name) : EntityBase(name) {}
Button::Button() : Button("") {}
void Button::press() {
ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str());
this->press_action();
this->press_callback_.call();
}
void Button::add_on_press_callback(std::function<void()> &&callback) { this->press_callback_.add(std::move(callback)); }
uint32_t Button::hash_base() { return 1495763804UL; }
void Button::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
std::string Button::get_device_class() {
if (this->device_class_.has_value())
return *this->device_class_;
return "";
}
} // namespace button
} // namespace esphome

View File

@@ -1,57 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace button {
#define LOG_BUTTON(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
if (!(obj)->get_icon().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
} \
}
/** Base class for all buttons.
*
* A button is just a momentary switch that does not have a state, only a trigger.
*/
class Button : public EntityBase {
public:
explicit Button();
explicit Button(const std::string &name);
/** Press this button. This is called by the front-end.
*
* For implementing buttons, please override press_action.
*/
void press();
/** Set callback for state changes.
*
* @param callback The void() callback.
*/
void add_on_press_callback(std::function<void()> &&callback);
/// Set the Home Assistant device class (see button::device_class).
void set_device_class(const std::string &device_class);
/// Get the device class for this button.
std::string get_device_class();
protected:
/** You should implement this virtual method if you want to create your own button.
*/
virtual void press_action(){};
uint32_t hash_base() override;
CallbackManager<void()> press_callback_{};
optional<std::string> device_class_{};
};
} // namespace button
} // namespace esphome

View File

@@ -1,45 +0,0 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c
from esphome.const import CONF_ID, CONF_RESET_PIN
from esphome import pins
CONF_TOUCH_THRESHOLD = "touch_threshold"
CONF_ALLOW_MULTIPLE_TOUCHES = "allow_multiple_touches"
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["binary_sensor", "output"]
CODEOWNERS = ["@MrEditor97"]
cap1188_ns = cg.esphome_ns.namespace("cap1188")
CONF_CAP1188_ID = "cap1188_id"
CAP1188Component = cap1188_ns.class_("CAP1188Component", cg.Component, i2c.I2CDevice)
MULTI_CONF = True
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(CAP1188Component),
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_TOUCH_THRESHOLD, default=0x20): cv.int_range(
min=0x01, max=0x80
),
cv.Optional(CONF_ALLOW_MULTIPLE_TOUCHES, default=False): cv.boolean,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(0x29))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_touch_threshold(config[CONF_TOUCH_THRESHOLD]))
cg.add(var.set_allow_multiple_touches(config[CONF_ALLOW_MULTIPLE_TOUCHES]))
if CONF_RESET_PIN in config:
pin = await cg.gpio_pin_expression(config[CONF_RESET_PIN])
cg.add(var.set_reset_pin(pin))
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)

View File

@@ -1,25 +0,0 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import CONF_CHANNEL, CONF_ID
from . import cap1188_ns, CAP1188Component, CONF_CAP1188_ID
DEPENDENCIES = ["cap1188"]
CAP1188Channel = cap1188_ns.class_("CAP1188Channel", binary_sensor.BinarySensor)
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(CAP1188Channel),
cv.GenerateID(CONF_CAP1188_ID): cv.use_id(CAP1188Component),
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7),
}
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await binary_sensor.register_binary_sensor(var, config)
hub = await cg.get_variable(config[CONF_CAP1188_ID])
cg.add(var.set_channel(config[CONF_CHANNEL]))
cg.add(hub.register_channel(var))

View File

@@ -1,88 +0,0 @@
#include "cap1188.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace cap1188 {
static const char *const TAG = "cap1188";
void CAP1188Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up CAP1188...");
// Reset device using the reset pin
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(false);
delay(100); // NOLINT
this->reset_pin_->digital_write(true);
delay(100); // NOLINT
this->reset_pin_->digital_write(false);
delay(100); // NOLINT
}
// Check if CAP1188 is actually connected
this->read_byte(CAP1188_PRODUCT_ID, &this->cap1188_product_id_);
this->read_byte(CAP1188_MANUFACTURE_ID, &this->cap1188_manufacture_id_);
this->read_byte(CAP1188_REVISION, &this->cap1188_revision_);
if ((this->cap1188_product_id_ != 0x50) || (this->cap1188_manufacture_id_ != 0x5D)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed();
return;
}
// Set sensitivity
uint8_t sensitivity = 0;
this->read_byte(CAP1188_SENSITVITY, &sensitivity);
sensitivity = sensitivity & 0x0f;
this->write_byte(CAP1188_SENSITVITY, sensitivity | this->touch_threshold_);
// Allow multiple touches
this->write_byte(CAP1188_MULTI_TOUCH, this->allow_multiple_touches_);
// Have LEDs follow touches
this->write_byte(CAP1188_LED_LINK, 0xFF);
// Speed up a bit
this->write_byte(CAP1188_STAND_BY_CONFIGURATION, 0x30);
}
void CAP1188Component::dump_config() {
ESP_LOGCONFIG(TAG, "CAP1188:");
LOG_I2C_DEVICE(this);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
ESP_LOGCONFIG(TAG, " Product ID: 0x%x", this->cap1188_product_id_);
ESP_LOGCONFIG(TAG, " Manufacture ID: 0x%x", this->cap1188_manufacture_id_);
ESP_LOGCONFIG(TAG, " Revision ID: 0x%x", this->cap1188_revision_);
switch (this->error_code_) {
case COMMUNICATION_FAILED:
ESP_LOGE(TAG, "Product ID or Manufacture ID of the connected device does not match a known CAP1188.");
break;
case NONE:
default:
break;
}
}
void CAP1188Component::loop() {
uint8_t touched = 0;
this->read_register(CAP1188_SENSOR_INPUT_STATUS, &touched, 1);
if (touched) {
uint8_t data = 0;
this->read_register(CAP1188_MAIN, &data, 1);
data = data & ~CAP1188_MAIN_INT;
this->write_register(CAP1188_MAIN, &data, 2);
}
for (auto *channel : this->channels_) {
channel->process(touched);
}
}
} // namespace cap1188
} // namespace esphome

View File

@@ -1,68 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/output/binary_output.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
namespace esphome {
namespace cap1188 {
enum {
CAP1188_I2CADDR = 0x29,
CAP1188_SENSOR_INPUT_STATUS = 0x3,
CAP1188_MULTI_TOUCH = 0x2A,
CAP1188_LED_LINK = 0x72,
CAP1188_PRODUCT_ID = 0xFD,
CAP1188_MANUFACTURE_ID = 0xFE,
CAP1188_STAND_BY_CONFIGURATION = 0x41,
CAP1188_REVISION = 0xFF,
CAP1188_MAIN = 0x00,
CAP1188_MAIN_INT = 0x01,
CAP1188_LEDPOL = 0x73,
CAP1188_INTERUPT_REPEAT = 0x28,
CAP1188_SENSITVITY = 0x1f,
};
class CAP1188Channel : public binary_sensor::BinarySensor {
public:
void set_channel(uint8_t channel) { channel_ = channel; }
void process(uint8_t data) { this->publish_state(static_cast<bool>(data & (1 << this->channel_))); }
protected:
uint8_t channel_{0};
};
class CAP1188Component : public Component, public i2c::I2CDevice {
public:
void register_channel(CAP1188Channel *channel) { this->channels_.push_back(channel); }
void set_touch_threshold(uint8_t touch_threshold) { this->touch_threshold_ = touch_threshold; };
void set_allow_multiple_touches(bool allow_multiple_touches) {
this->allow_multiple_touches_ = allow_multiple_touches ? 0x41 : 0x80;
};
void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void loop() override;
protected:
std::vector<CAP1188Channel *> channels_{};
uint8_t touch_threshold_{0x20};
uint8_t allow_multiple_touches_{0x80};
GPIOPin *reset_pin_{nullptr};
uint8_t cap1188_product_id_{0};
uint8_t cap1188_manufacture_id_{0};
uint8_t cap1188_revision_{0};
enum ErrorCode {
NONE = 0,
COMMUNICATION_FAILED,
} error_code_{NONE};
};
} // namespace cap1188
} // namespace esphome

View File

@@ -3,7 +3,7 @@ import esphome.config_validation as cv
from esphome.components import web_server_base
from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
from esphome.const import CONF_ID
from esphome.core import coroutine_with_priority, CORE
from esphome.core import coroutine_with_priority
AUTO_LOAD = ["web_server_base"]
DEPENDENCIES = ["wifi"]
@@ -12,17 +12,14 @@ CODEOWNERS = ["@OttoWinter"]
captive_portal_ns = cg.esphome_ns.namespace("captive_portal")
CaptivePortal = captive_portal_ns.class_("CaptivePortal", cg.Component)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(CaptivePortal),
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
web_server_base.WebServerBase
),
}
).extend(cv.COMPONENT_SCHEMA),
cv.only_with_arduino,
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(CaptivePortal),
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
web_server_base.WebServerBase
),
}
).extend(cv.COMPONENT_SCHEMA)
@coroutine_with_priority(64.0)
@@ -32,9 +29,3 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID], paren)
await cg.register_component(var, config)
cg.add_define("USE_CAPTIVE_PORTAL")
if CORE.is_esp32:
cg.add_library("DNSServer", None)
cg.add_library("WiFi", None)
if CORE.is_esp8266:
cg.add_library("DNSServer", None)

View File

@@ -1,5 +1,3 @@
#ifdef USE_ARDUINO
#include "captive_portal.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
@@ -67,7 +65,6 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
ESP_LOGI(TAG, " SSID='%s'", ssid.c_str());
ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str());
wifi::global_wifi_component->save_wifi_sta(ssid, psk);
wifi::global_wifi_component->start_scanning();
request->redirect("/?save=true");
}
@@ -79,19 +76,26 @@ void CaptivePortal::start() {
this->base_->add_ota_handler();
}
this->dns_server_ = make_unique<DNSServer>();
this->dns_server_ = new DNSServer();
this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError);
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
this->dns_server_->start(53, "*", (uint32_t) ip);
IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
this->dns_server_->start(53, "*", ip);
this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) {
if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) {
bool not_found = false;
if (!this->active_) {
not_found = true;
} else if (req->host() == wifi::global_wifi_component->wifi_soft_ap_ip().toString()) {
not_found = true;
}
if (not_found) {
req->send(404, "text/html", "File not found");
return;
}
auto url = "http://" + wifi::global_wifi_component->wifi_soft_ap_ip().str();
req->redirect(url.c_str());
auto url = "http://" + wifi::global_wifi_component->wifi_soft_ap_ip().toString();
req->redirect(url);
});
this->initialized_ = true;
@@ -147,5 +151,3 @@ CaptivePortal *global_captive_portal = nullptr; // NOLINT(cppcoreguidelines-avo
} // namespace captive_portal
} // namespace esphome
#endif // USE_ARDUINO

View File

@@ -1,8 +1,5 @@
#pragma once
#ifdef USE_ARDUINO
#include <memory>
#include <DNSServer.h>
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
@@ -29,7 +26,7 @@ class CaptivePortal : public AsyncWebHandler, public Component {
this->active_ = false;
this->base_->deinit();
this->dns_server_->stop();
this->dns_server_ = nullptr;
delete this->dns_server_;
}
bool canHandle(AsyncWebServerRequest *request) override {
@@ -68,12 +65,10 @@ class CaptivePortal : public AsyncWebHandler, public Component {
web_server_base::WebServerBase *base_;
bool initialized_{false};
bool active_{false};
std::unique_ptr<DNSServer> dns_server_{nullptr};
DNSServer *dns_server_{nullptr};
};
extern CaptivePortal *global_captive_portal; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace captive_portal
} // namespace esphome
#endif // USE_ARDUINO

View File

@@ -1,6 +1,5 @@
#include "ccs811.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace ccs811 {
@@ -17,7 +16,7 @@ static const char *const TAG = "ccs811";
return; \
}
#define CHECKED_IO(f) CHECK_TRUE(f, COMMUNICATION_FAILED)
#define CHECKED_IO(f) CHECK_TRUE(f, COMMUNICAITON_FAILED)
void CCS811Component::setup() {
// page 9 programming guide - hwid is always 0x81
@@ -39,58 +38,23 @@ void CCS811Component::setup() {
// set MEAS_MODE (page 5)
uint8_t meas_mode = 0;
uint32_t interval = this->get_update_interval();
if (interval >= 60 * 1000)
meas_mode = 3 << 4; // sensor takes a reading every 60 seconds
else if (interval >= 10 * 1000)
meas_mode = 2 << 4; // sensor takes a reading every 10 seconds
else if (interval >= 1 * 1000)
meas_mode = 1 << 4; // sensor takes a reading every second
if (interval <= 1000)
meas_mode = 1 << 4;
else if (interval <= 10000)
meas_mode = 2 << 4;
else
meas_mode = 4 << 4; // sensor takes a reading every 250ms
meas_mode = 3 << 4;
CHECKED_IO(this->write_byte(0x01, meas_mode))
if (this->baseline_.has_value()) {
// baseline available, write to sensor
this->write_bytes(0x11, decode_value(*this->baseline_));
}
auto hardware_version_data = this->read_bytes<1>(0x21);
auto bootloader_version_data = this->read_bytes<2>(0x23);
auto application_version_data = this->read_bytes<2>(0x24);
uint8_t hardware_version = 0;
uint16_t bootloader_version = 0;
uint16_t application_version = 0;
if (hardware_version_data.has_value()) {
hardware_version = (*hardware_version_data)[0];
}
if (bootloader_version_data.has_value()) {
bootloader_version = encode_uint16((*bootloader_version_data)[0], (*bootloader_version_data)[1]);
}
if (application_version_data.has_value()) {
application_version = encode_uint16((*application_version_data)[0], (*application_version_data)[1]);
}
ESP_LOGD(TAG, "hardware_version=0x%x bootloader_version=0x%x application_version=0x%x\n", hardware_version,
bootloader_version, application_version);
if (this->version_ != nullptr) {
char version[20]; // "15.15.15 (0xffff)" is 17 chars, plus NUL, plus wiggle room
sprintf(version, "%d.%d.%d (0x%02x)", (application_version >> 12 & 15), (application_version >> 8 & 15),
(application_version >> 4 & 15), application_version);
ESP_LOGD(TAG, "publishing version state: %s", version);
this->version_->publish_state(version);
this->write_bytes(0x11, decode_uint16(*this->baseline_));
}
}
void CCS811Component::update() {
if (!this->status_has_data_()) {
ESP_LOGD(TAG, "Status indicates no data ready!");
if (!this->status_has_data_())
this->status_set_warning();
return;
}
// page 12 - alg result data
auto alg_data = this->read_bytes<4>(0x02);
@@ -128,12 +92,12 @@ void CCS811Component::send_env_data_() {
float humidity = NAN;
if (this->humidity_ != nullptr)
humidity = this->humidity_->state;
if (std::isnan(humidity) || humidity < 0 || humidity > 100)
if (isnan(humidity) || humidity < 0 || humidity > 100)
humidity = 50;
float temperature = NAN;
if (this->temperature_ != nullptr)
temperature = this->temperature_->state;
if (std::isnan(temperature) || temperature < -25 || temperature > 50)
if (isnan(temperature) || temperature < -25 || temperature > 50)
temperature = 25;
// temperature has a 25° offset to allow negative temperatures
temperature += 25;
@@ -153,7 +117,6 @@ void CCS811Component::dump_config() {
LOG_UPDATE_INTERVAL(this)
LOG_SENSOR(" ", "CO2 Sensor", this->co2_)
LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_)
LOG_TEXT_SENSOR(" ", "Firmware Version Sensor", this->version_)
if (this->baseline_) {
ESP_LOGCONFIG(TAG, " Baseline: %04X", *this->baseline_);
} else {
@@ -161,7 +124,7 @@ void CCS811Component::dump_config() {
}
if (this->is_failed()) {
switch (this->error_code_) {
case COMMUNICATION_FAILED:
case COMMUNICAITON_FAILED:
ESP_LOGW(TAG, "Communication failed! Is the sensor connected?");
break;
case INVALID_ID:

View File

@@ -3,7 +3,6 @@
#include "esphome/core/component.h"
#include "esphome/core/preferences.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/text_sensor/text_sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
@@ -13,7 +12,6 @@ class CCS811Component : public PollingComponent, public i2c::I2CDevice {
public:
void set_co2(sensor::Sensor *co2) { co2_ = co2; }
void set_tvoc(sensor::Sensor *tvoc) { tvoc_ = tvoc; }
void set_version(text_sensor::TextSensor *version) { version_ = version; }
void set_baseline(uint16_t baseline) { baseline_ = baseline; }
void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
@@ -36,7 +34,7 @@ class CCS811Component : public PollingComponent, public i2c::I2CDevice {
enum ErrorCode {
UNKNOWN,
COMMUNICATION_FAILED,
COMMUNICAITON_FAILED,
INVALID_ID,
SENSOR_REPORTED_ERROR,
APP_INVALID,
@@ -45,7 +43,6 @@ class CCS811Component : public PollingComponent, public i2c::I2CDevice {
sensor::Sensor *co2_{nullptr};
sensor::Sensor *tvoc_{nullptr};
text_sensor::TextSensor *version_{nullptr};
optional<uint16_t> baseline_{};
/// Input sensor for humidity reading.
sensor::Sensor *humidity_{nullptr};

View File

@@ -1,13 +1,9 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor, text_sensor
from esphome.components import i2c, sensor
from esphome.const import (
CONF_ICON,
CONF_ID,
ICON_RADIATOR,
ICON_RESTART,
DEVICE_CLASS_CARBON_DIOXIDE,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
STATE_CLASS_MEASUREMENT,
UNIT_PARTS_PER_MILLION,
UNIT_PARTS_PER_BILLION,
@@ -16,12 +12,9 @@ from esphome.const import (
CONF_TEMPERATURE,
CONF_TVOC,
CONF_HUMIDITY,
CONF_VERSION,
ICON_MOLECULE_CO2,
)
AUTO_LOAD = ["text_sensor"]
CODEOWNERS = ["@habbie"]
DEPENDENCIES = ["i2c"]
ccs811_ns = cg.esphome_ns.namespace("ccs811")
@@ -37,22 +30,14 @@ CONFIG_SCHEMA = (
unit_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_MOLECULE_CO2,
accuracy_decimals=0,
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Required(CONF_TVOC): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_BILLION,
icon=ICON_RADIATOR,
accuracy_decimals=0,
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_VERSION): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
cv.Optional(CONF_ICON, default=ICON_RESTART): cv.icon,
}
),
cv.Optional(CONF_BASELINE): cv.hex_uint16_t,
cv.Optional(CONF_TEMPERATURE): cv.use_id(sensor.Sensor),
cv.Optional(CONF_HUMIDITY): cv.use_id(sensor.Sensor),
@@ -73,11 +58,6 @@ async def to_code(config):
sens = await sensor.new_sensor(config[CONF_TVOC])
cg.add(var.set_tvoc(sens))
if CONF_VERSION in config:
sens = cg.new_Pvariable(config[CONF_VERSION][CONF_ID])
await text_sensor.register_text_sensor(sens, config[CONF_VERSION])
cg.add(var.set_version(sens))
if CONF_BASELINE in config:
cg.add(var.set_baseline(config[CONF_BASELINE]))

View File

@@ -1,43 +1,27 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.cpp_helpers import setup_entity
from esphome import automation
from esphome.components import mqtt
from esphome.const import (
CONF_ACTION_STATE_TOPIC,
CONF_AWAY,
CONF_AWAY_COMMAND_TOPIC,
CONF_AWAY_STATE_TOPIC,
CONF_CURRENT_TEMPERATURE_STATE_TOPIC,
CONF_CUSTOM_FAN_MODE,
CONF_CUSTOM_PRESET,
CONF_FAN_MODE,
CONF_FAN_MODE_COMMAND_TOPIC,
CONF_FAN_MODE_STATE_TOPIC,
CONF_DISABLED_BY_DEFAULT,
CONF_ID,
CONF_INTERNAL,
CONF_MAX_TEMPERATURE,
CONF_MIN_TEMPERATURE,
CONF_MODE,
CONF_MODE_COMMAND_TOPIC,
CONF_MODE_STATE_TOPIC,
CONF_ON_STATE,
CONF_PRESET,
CONF_SWING_MODE,
CONF_SWING_MODE_COMMAND_TOPIC,
CONF_SWING_MODE_STATE_TOPIC,
CONF_TARGET_TEMPERATURE,
CONF_TARGET_TEMPERATURE_COMMAND_TOPIC,
CONF_TARGET_TEMPERATURE_STATE_TOPIC,
CONF_TARGET_TEMPERATURE_HIGH,
CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC,
CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC,
CONF_TARGET_TEMPERATURE_LOW,
CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC,
CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC,
CONF_TEMPERATURE_STEP,
CONF_TRIGGER_ID,
CONF_VISUAL,
CONF_MQTT_ID,
CONF_NAME,
CONF_FAN_MODE,
CONF_SWING_MODE,
)
from esphome.core import CORE, coroutine_with_priority
@@ -46,7 +30,7 @@ IS_PLATFORM_COMPONENT = True
CODEOWNERS = ["@esphome/core"]
climate_ns = cg.esphome_ns.namespace("climate")
Climate = climate_ns.class_("Climate", cg.EntityBase)
Climate = climate_ns.class_("Climate", cg.Nameable)
ClimateCall = climate_ns.class_("ClimateCall")
ClimateTraits = climate_ns.class_("ClimateTraits")
@@ -79,7 +63,6 @@ validate_climate_fan_mode = cv.enum(CLIMATE_FAN_MODES, upper=True)
ClimatePreset = climate_ns.enum("ClimatePreset")
CLIMATE_PRESETS = {
"NONE": ClimatePreset.CLIMATE_PRESET_NONE,
"ECO": ClimatePreset.CLIMATE_PRESET_ECO,
"AWAY": ClimatePreset.CLIMATE_PRESET_AWAY,
"BOOST": ClimatePreset.CLIMATE_PRESET_BOOST,
@@ -103,9 +86,8 @@ validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
# Actions
ControlAction = climate_ns.class_("ControlAction", automation.Action)
StateTrigger = climate_ns.class_("StateTrigger", automation.Trigger.template())
CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
CLIMATE_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(Climate),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent),
@@ -116,66 +98,16 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).
cv.Optional(CONF_TEMPERATURE_STEP): cv.temperature,
}
),
cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_AWAY_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_AWAY_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_CURRENT_TEMPERATURE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_FAN_MODE_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_FAN_MODE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_MODE_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_MODE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_SWING_MODE_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_SWING_MODE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
}
),
# TODO: MQTT topic options
}
)
async def setup_climate_core_(var, config):
await setup_entity(var, config)
cg.add(var.set_name(config[CONF_NAME]))
cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
if CONF_INTERNAL in config:
cg.add(var.set_internal(config[CONF_INTERNAL]))
visual = config[CONF_VISUAL]
if CONF_MIN_TEMPERATURE in visual:
cg.add(var.set_visual_min_temperature_override(visual[CONF_MIN_TEMPERATURE]))
@@ -188,86 +120,6 @@ async def setup_climate_core_(var, config):
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
await mqtt.register_mqtt_component(mqtt_, config)
if CONF_ACTION_STATE_TOPIC in config:
cg.add(mqtt_.set_custom_action_state_topic(config[CONF_ACTION_STATE_TOPIC]))
if CONF_AWAY_COMMAND_TOPIC in config:
cg.add(mqtt_.set_custom_away_command_topic(config[CONF_AWAY_COMMAND_TOPIC]))
if CONF_AWAY_STATE_TOPIC in config:
cg.add(mqtt_.set_custom_away_state_topic(config[CONF_AWAY_STATE_TOPIC]))
if CONF_CURRENT_TEMPERATURE_STATE_TOPIC in config:
cg.add(
mqtt_.set_custom_current_temperature_state_topic(
config[CONF_CURRENT_TEMPERATURE_STATE_TOPIC]
)
)
if CONF_FAN_MODE_COMMAND_TOPIC in config:
cg.add(
mqtt_.set_custom_fan_mode_command_topic(
config[CONF_FAN_MODE_COMMAND_TOPIC]
)
)
if CONF_FAN_MODE_STATE_TOPIC in config:
cg.add(
mqtt_.set_custom_fan_mode_state_topic(config[CONF_FAN_MODE_STATE_TOPIC])
)
if CONF_MODE_COMMAND_TOPIC in config:
cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC]))
if CONF_MODE_STATE_TOPIC in config:
cg.add(mqtt_.set_custom_mode_state_topic(config[CONF_MODE_STATE_TOPIC]))
if CONF_SWING_MODE_COMMAND_TOPIC in config:
cg.add(
mqtt_.set_custom_swing_mode_command_topic(
config[CONF_SWING_MODE_COMMAND_TOPIC]
)
)
if CONF_SWING_MODE_STATE_TOPIC in config:
cg.add(
mqtt_.set_custom_swing_mode_state_topic(
config[CONF_SWING_MODE_STATE_TOPIC]
)
)
if CONF_TARGET_TEMPERATURE_COMMAND_TOPIC in config:
cg.add(
mqtt_.set_custom_target_temperature_command_topic(
config[CONF_TARGET_TEMPERATURE_COMMAND_TOPIC]
)
)
if CONF_TARGET_TEMPERATURE_STATE_TOPIC in config:
cg.add(
mqtt_.set_custom_target_temperature_state_topic(
config[CONF_TARGET_TEMPERATURE_STATE_TOPIC]
)
)
if CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC in config:
cg.add(
mqtt_.set_custom_target_temperature_high_command_topic(
config[CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC]
)
)
if CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC in config:
cg.add(
mqtt_.set_custom_target_temperature_high_state_topic(
config[CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC]
)
)
if CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC in config:
cg.add(
mqtt_.set_custom_target_temperature_low_command_topic(
config[CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC]
)
)
if CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC in config:
cg.add(
mqtt_.set_custom_target_temperature_state_topic(
config[CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC]
)
)
for conf in config.get(CONF_ON_STATE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
async def register_climate(var, config):
if not CORE.has_id(config[CONF_ID]):

View File

@@ -42,12 +42,5 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
Climate *climate_;
};
class StateTrigger : public Trigger<> {
public:
StateTrigger(Climate *climate) {
climate->add_on_state_callback([this]() { this->trigger(); });
}
};
} // namespace climate
} // namespace esphome

View File

@@ -9,8 +9,8 @@ void ClimateCall::perform() {
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
this->validate_();
if (this->mode_.has_value()) {
const LogString *mode_s = climate_mode_to_string(*this->mode_);
ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(mode_s));
const char *mode_s = climate_mode_to_string(*this->mode_);
ESP_LOGD(TAG, " Mode: %s", mode_s);
}
if (this->custom_fan_mode_.has_value()) {
this->fan_mode_.reset();
@@ -18,8 +18,8 @@ void ClimateCall::perform() {
}
if (this->fan_mode_.has_value()) {
this->custom_fan_mode_.reset();
const LogString *fan_mode_s = climate_fan_mode_to_string(*this->fan_mode_);
ESP_LOGD(TAG, " Fan: %s", LOG_STR_ARG(fan_mode_s));
const char *fan_mode_s = climate_fan_mode_to_string(*this->fan_mode_);
ESP_LOGD(TAG, " Fan: %s", fan_mode_s);
}
if (this->custom_preset_.has_value()) {
this->preset_.reset();
@@ -27,12 +27,12 @@ void ClimateCall::perform() {
}
if (this->preset_.has_value()) {
this->custom_preset_.reset();
const LogString *preset_s = climate_preset_to_string(*this->preset_);
ESP_LOGD(TAG, " Preset: %s", LOG_STR_ARG(preset_s));
const char *preset_s = climate_preset_to_string(*this->preset_);
ESP_LOGD(TAG, " Preset: %s", preset_s);
}
if (this->swing_mode_.has_value()) {
const LogString *swing_mode_s = climate_swing_mode_to_string(*this->swing_mode_);
ESP_LOGD(TAG, " Swing: %s", LOG_STR_ARG(swing_mode_s));
const char *swing_mode_s = climate_swing_mode_to_string(*this->swing_mode_);
ESP_LOGD(TAG, " Swing: %s", swing_mode_s);
}
if (this->target_temperature_.has_value()) {
ESP_LOGD(TAG, " Target Temperature: %.2f", *this->target_temperature_);
@@ -50,7 +50,7 @@ void ClimateCall::validate_() {
if (this->mode_.has_value()) {
auto mode = *this->mode_;
if (!traits.supports_mode(mode)) {
ESP_LOGW(TAG, " Mode %s is not supported by this device!", LOG_STR_ARG(climate_mode_to_string(mode)));
ESP_LOGW(TAG, " Mode %s is not supported by this device!", climate_mode_to_string(mode));
this->mode_.reset();
}
}
@@ -63,8 +63,7 @@ void ClimateCall::validate_() {
} else if (this->fan_mode_.has_value()) {
auto fan_mode = *this->fan_mode_;
if (!traits.supports_fan_mode(fan_mode)) {
ESP_LOGW(TAG, " Fan Mode %s is not supported by this device!",
LOG_STR_ARG(climate_fan_mode_to_string(fan_mode)));
ESP_LOGW(TAG, " Fan Mode %s is not supported by this device!", climate_fan_mode_to_string(fan_mode));
this->fan_mode_.reset();
}
}
@@ -77,15 +76,14 @@ void ClimateCall::validate_() {
} else if (this->preset_.has_value()) {
auto preset = *this->preset_;
if (!traits.supports_preset(preset)) {
ESP_LOGW(TAG, " Preset %s is not supported by this device!", LOG_STR_ARG(climate_preset_to_string(preset)));
ESP_LOGW(TAG, " Preset %s is not supported by this device!", climate_preset_to_string(preset));
this->preset_.reset();
}
}
if (this->swing_mode_.has_value()) {
auto swing_mode = *this->swing_mode_;
if (!traits.supports_swing_mode(swing_mode)) {
ESP_LOGW(TAG, " Swing Mode %s is not supported by this device!",
LOG_STR_ARG(climate_swing_mode_to_string(swing_mode)));
ESP_LOGW(TAG, " Swing Mode %s is not supported by this device!", climate_swing_mode_to_string(swing_mode));
this->swing_mode_.reset();
}
}
@@ -95,7 +93,7 @@ void ClimateCall::validate_() {
ESP_LOGW(TAG, " Cannot set target temperature for climate device "
"with two-point target temperature!");
this->target_temperature_.reset();
} else if (std::isnan(target)) {
} else if (isnan(target)) {
ESP_LOGW(TAG, " Target temperature must not be NAN!");
this->target_temperature_.reset();
}
@@ -107,11 +105,11 @@ void ClimateCall::validate_() {
this->target_temperature_high_.reset();
}
}
if (this->target_temperature_low_.has_value() && std::isnan(*this->target_temperature_low_)) {
if (this->target_temperature_low_.has_value() && isnan(*this->target_temperature_low_)) {
ESP_LOGW(TAG, " Target temperature low must not be NAN!");
this->target_temperature_low_.reset();
}
if (this->target_temperature_high_.has_value() && std::isnan(*this->target_temperature_high_)) {
if (this->target_temperature_high_.has_value() && isnan(*this->target_temperature_high_)) {
ESP_LOGW(TAG, " Target temperature low must not be NAN!");
this->target_temperature_high_.reset();
}
@@ -318,23 +316,17 @@ void Climate::add_on_state_callback(std::function<void()> &&callback) {
static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL;
optional<ClimateDeviceRestoreState> Climate::restore_state_() {
this->rtc_ = global_preferences->make_preference<ClimateDeviceRestoreState>(this->get_object_id_hash() ^
RESTORE_STATE_VERSION);
this->rtc_ =
global_preferences.make_preference<ClimateDeviceRestoreState>(this->get_object_id_hash() ^ RESTORE_STATE_VERSION);
ClimateDeviceRestoreState recovered{};
if (!this->rtc_.load(&recovered))
return {};
return recovered;
}
void Climate::save_state_() {
#if defined(USE_ESP_IDF) && !defined(CLANG_TIDY)
#pragma GCC diagnostic ignored "-Wclass-memaccess"
#endif
ClimateDeviceRestoreState state{};
// initialize as zero to prevent random data on stack triggering erase
memset(&state, 0, sizeof(ClimateDeviceRestoreState));
#if USE_ESP_IDF && !defined(CLANG_TIDY)
#pragma GCC diagnostic pop
#endif
state.mode = this->mode;
auto traits = this->get_traits();
@@ -381,24 +373,24 @@ void Climate::publish_state() {
ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str());
auto traits = this->get_traits();
ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode)));
ESP_LOGD(TAG, " Mode: %s", climate_mode_to_string(this->mode));
if (traits.get_supports_action()) {
ESP_LOGD(TAG, " Action: %s", LOG_STR_ARG(climate_action_to_string(this->action)));
ESP_LOGD(TAG, " Action: %s", climate_action_to_string(this->action));
}
if (traits.get_supports_fan_modes() && this->fan_mode.has_value()) {
ESP_LOGD(TAG, " Fan Mode: %s", LOG_STR_ARG(climate_fan_mode_to_string(this->fan_mode.value())));
ESP_LOGD(TAG, " Fan Mode: %s", climate_fan_mode_to_string(this->fan_mode.value()));
}
if (!traits.get_supported_custom_fan_modes().empty() && this->custom_fan_mode.has_value()) {
ESP_LOGD(TAG, " Custom Fan Mode: %s", this->custom_fan_mode.value().c_str());
}
if (traits.get_supports_presets() && this->preset.has_value()) {
ESP_LOGD(TAG, " Preset: %s", LOG_STR_ARG(climate_preset_to_string(this->preset.value())));
ESP_LOGD(TAG, " Preset: %s", climate_preset_to_string(this->preset.value()));
}
if (!traits.get_supported_custom_presets().empty() && this->custom_preset.has_value()) {
ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset.value().c_str());
}
if (traits.get_supports_swing_modes()) {
ESP_LOGD(TAG, " Swing Mode: %s", LOG_STR_ARG(climate_swing_mode_to_string(this->swing_mode)));
ESP_LOGD(TAG, " Swing Mode: %s", climate_swing_mode_to_string(this->swing_mode));
}
if (traits.get_supports_current_temperature()) {
ESP_LOGD(TAG, " Current Temperature: %.2f°C", this->current_temperature);
@@ -440,11 +432,7 @@ void Climate::set_visual_max_temperature_override(float visual_max_temperature_o
void Climate::set_visual_temperature_step_override(float visual_temperature_step_override) {
this->visual_temperature_step_override_ = visual_temperature_step_override;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
Climate::Climate(const std::string &name) : EntityBase(name) {}
#pragma GCC diagnostic pop
Climate::Climate(const std::string &name) : Nameable(name) {}
Climate::Climate() : Climate("") {}
ClimateCall Climate::make_call() { return ClimateCall(this); }
@@ -506,74 +494,5 @@ void ClimateDeviceRestoreState::apply(Climate *climate) {
climate->publish_state();
}
template<typename T1, typename T2> bool set_alternative(optional<T1> &dst, optional<T2> &alt, const T1 &src) {
bool is_changed = alt.has_value();
alt.reset();
if (is_changed || dst != src) {
dst = src;
is_changed = true;
}
return is_changed;
}
bool Climate::set_fan_mode_(ClimateFanMode mode) {
return set_alternative(this->fan_mode, this->custom_fan_mode, mode);
}
bool Climate::set_custom_fan_mode_(const std::string &mode) {
return set_alternative(this->custom_fan_mode, this->fan_mode, mode);
}
bool Climate::set_preset_(ClimatePreset preset) { return set_alternative(this->preset, this->custom_preset, preset); }
bool Climate::set_custom_preset_(const std::string &preset) {
return set_alternative(this->custom_preset, this->preset, preset);
}
void Climate::dump_traits_(const char *tag) {
auto traits = this->get_traits();
ESP_LOGCONFIG(tag, "ClimateTraits:");
ESP_LOGCONFIG(tag, " [x] Visual settings:");
ESP_LOGCONFIG(tag, " - Min: %.1f", traits.get_visual_min_temperature());
ESP_LOGCONFIG(tag, " - Max: %.1f", traits.get_visual_max_temperature());
ESP_LOGCONFIG(tag, " - Step: %.1f", traits.get_visual_temperature_step());
if (traits.get_supports_current_temperature())
ESP_LOGCONFIG(tag, " [x] Supports current temperature");
if (traits.get_supports_two_point_target_temperature())
ESP_LOGCONFIG(tag, " [x] Supports two-point target temperature");
if (traits.get_supports_action())
ESP_LOGCONFIG(tag, " [x] Supports action");
if (!traits.get_supported_modes().empty()) {
ESP_LOGCONFIG(tag, " [x] Supported modes:");
for (ClimateMode m : traits.get_supported_modes())
ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_mode_to_string(m)));
}
if (!traits.get_supported_fan_modes().empty()) {
ESP_LOGCONFIG(tag, " [x] Supported fan modes:");
for (ClimateFanMode m : traits.get_supported_fan_modes())
ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_fan_mode_to_string(m)));
}
if (!traits.get_supported_custom_fan_modes().empty()) {
ESP_LOGCONFIG(tag, " [x] Supported custom fan modes:");
for (const std::string &s : traits.get_supported_custom_fan_modes())
ESP_LOGCONFIG(tag, " - %s", s.c_str());
}
if (!traits.get_supported_presets().empty()) {
ESP_LOGCONFIG(tag, " [x] Supported presets:");
for (ClimatePreset p : traits.get_supported_presets())
ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_preset_to_string(p)));
}
if (!traits.get_supported_custom_presets().empty()) {
ESP_LOGCONFIG(tag, " [x] Supported custom presets:");
for (const std::string &s : traits.get_supported_custom_presets())
ESP_LOGCONFIG(tag, " - %s", s.c_str());
}
if (!traits.get_supported_swing_modes().empty()) {
ESP_LOGCONFIG(tag, " [x] Supported swing modes:");
for (ClimateSwingMode m : traits.get_supported_swing_modes())
ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_swing_mode_to_string(m)));
}
}
} // namespace climate
} // namespace esphome

View File

@@ -1,7 +1,6 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
#include "esphome/core/log.h"
@@ -13,7 +12,7 @@ namespace climate {
#define LOG_CLIMATE(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \
}
class Climate;
@@ -164,7 +163,7 @@ struct ClimateDeviceRestoreState {
* mode etc). These are read-only for the user and rw for integrations. The reason these are public
* is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_HEAT_COOL) ...`
*/
class Climate : public EntityBase {
class Climate : public Nameable {
public:
/// Construct a climate device with empty name (will be set later).
Climate();
@@ -246,18 +245,6 @@ class Climate : public EntityBase {
protected:
friend ClimateCall;
/// Set fan mode. Reset custom fan mode. Return true if fan mode has been changed.
bool set_fan_mode_(ClimateFanMode mode);
/// Set custom fan mode. Reset primary fan mode. Return true if fan mode has been changed.
bool set_custom_fan_mode_(const std::string &mode);
/// Set preset. Reset custom preset. Return true if preset has been changed.
bool set_preset_(ClimatePreset preset);
/// Set custom preset. Reset primary preset. Return true if preset has been changed.
bool set_custom_preset_(const std::string &preset);
/** Get the default traits of this climate device.
*
* Traits are static data that encode the capabilities and static data for a climate device such as supported
@@ -283,7 +270,6 @@ class Climate : public EntityBase {
void save_state_();
uint32_t hash_base() override;
void dump_traits_(const char *tag);
CallbackManager<void()> state_callback_{};
ESPPreferenceObject rtc_;

View File

@@ -3,105 +3,105 @@
namespace esphome {
namespace climate {
const LogString *climate_mode_to_string(ClimateMode mode) {
const char *climate_mode_to_string(ClimateMode mode) {
switch (mode) {
case CLIMATE_MODE_OFF:
return LOG_STR("OFF");
case CLIMATE_MODE_HEAT_COOL:
return LOG_STR("HEAT_COOL");
return "OFF";
case CLIMATE_MODE_AUTO:
return LOG_STR("AUTO");
return "AUTO";
case CLIMATE_MODE_COOL:
return LOG_STR("COOL");
return "COOL";
case CLIMATE_MODE_HEAT:
return LOG_STR("HEAT");
return "HEAT";
case CLIMATE_MODE_FAN_ONLY:
return LOG_STR("FAN_ONLY");
return "FAN_ONLY";
case CLIMATE_MODE_DRY:
return LOG_STR("DRY");
return "DRY";
case CLIMATE_MODE_HEAT_COOL:
return "HEAT_COOL";
default:
return LOG_STR("UNKNOWN");
return "UNKNOWN";
}
}
const LogString *climate_action_to_string(ClimateAction action) {
const char *climate_action_to_string(ClimateAction action) {
switch (action) {
case CLIMATE_ACTION_OFF:
return LOG_STR("OFF");
return "OFF";
case CLIMATE_ACTION_COOLING:
return LOG_STR("COOLING");
return "COOLING";
case CLIMATE_ACTION_HEATING:
return LOG_STR("HEATING");
return "HEATING";
case CLIMATE_ACTION_IDLE:
return LOG_STR("IDLE");
return "IDLE";
case CLIMATE_ACTION_DRYING:
return LOG_STR("DRYING");
return "DRYING";
case CLIMATE_ACTION_FAN:
return LOG_STR("FAN");
return "FAN";
default:
return LOG_STR("UNKNOWN");
return "UNKNOWN";
}
}
const LogString *climate_fan_mode_to_string(ClimateFanMode fan_mode) {
const char *climate_fan_mode_to_string(ClimateFanMode fan_mode) {
switch (fan_mode) {
case climate::CLIMATE_FAN_ON:
return LOG_STR("ON");
return "ON";
case climate::CLIMATE_FAN_OFF:
return LOG_STR("OFF");
return "OFF";
case climate::CLIMATE_FAN_AUTO:
return LOG_STR("AUTO");
return "AUTO";
case climate::CLIMATE_FAN_LOW:
return LOG_STR("LOW");
return "LOW";
case climate::CLIMATE_FAN_MEDIUM:
return LOG_STR("MEDIUM");
return "MEDIUM";
case climate::CLIMATE_FAN_HIGH:
return LOG_STR("HIGH");
return "HIGH";
case climate::CLIMATE_FAN_MIDDLE:
return LOG_STR("MIDDLE");
return "MIDDLE";
case climate::CLIMATE_FAN_FOCUS:
return LOG_STR("FOCUS");
return "FOCUS";
case climate::CLIMATE_FAN_DIFFUSE:
return LOG_STR("DIFFUSE");
return "DIFFUSE";
default:
return LOG_STR("UNKNOWN");
return "UNKNOWN";
}
}
const LogString *climate_swing_mode_to_string(ClimateSwingMode swing_mode) {
const char *climate_swing_mode_to_string(ClimateSwingMode swing_mode) {
switch (swing_mode) {
case climate::CLIMATE_SWING_OFF:
return LOG_STR("OFF");
return "OFF";
case climate::CLIMATE_SWING_BOTH:
return LOG_STR("BOTH");
return "BOTH";
case climate::CLIMATE_SWING_VERTICAL:
return LOG_STR("VERTICAL");
return "VERTICAL";
case climate::CLIMATE_SWING_HORIZONTAL:
return LOG_STR("HORIZONTAL");
return "HORIZONTAL";
default:
return LOG_STR("UNKNOWN");
return "UNKNOWN";
}
}
const LogString *climate_preset_to_string(ClimatePreset preset) {
const char *climate_preset_to_string(ClimatePreset preset) {
switch (preset) {
case climate::CLIMATE_PRESET_NONE:
return LOG_STR("NONE");
return "NONE";
case climate::CLIMATE_PRESET_HOME:
return LOG_STR("HOME");
return "HOME";
case climate::CLIMATE_PRESET_ECO:
return LOG_STR("ECO");
return "ECO";
case climate::CLIMATE_PRESET_AWAY:
return LOG_STR("AWAY");
return "AWAY";
case climate::CLIMATE_PRESET_BOOST:
return LOG_STR("BOOST");
return "BOOST";
case climate::CLIMATE_PRESET_COMFORT:
return LOG_STR("COMFORT");
return "COMFORT";
case climate::CLIMATE_PRESET_SLEEP:
return LOG_STR("SLEEP");
return "SLEEP";
case climate::CLIMATE_PRESET_ACTIVITY:
return LOG_STR("ACTIVITY");
return "ACTIVITY";
default:
return LOG_STR("UNKNOWN");
return "UNKNOWN";
}
}

View File

@@ -1,7 +1,6 @@
#pragma once
#include <cstdint>
#include "esphome/core/log.h"
namespace esphome {
namespace climate {
@@ -97,19 +96,19 @@ enum ClimatePreset : uint8_t {
};
/// Convert the given ClimateMode to a human-readable string.
const LogString *climate_mode_to_string(ClimateMode mode);
const char *climate_mode_to_string(ClimateMode mode);
/// Convert the given ClimateAction to a human-readable string.
const LogString *climate_action_to_string(ClimateAction action);
const char *climate_action_to_string(ClimateAction action);
/// Convert the given ClimateFanMode to a human-readable string.
const LogString *climate_fan_mode_to_string(ClimateFanMode mode);
const char *climate_fan_mode_to_string(ClimateFanMode mode);
/// Convert the given ClimateSwingMode to a human-readable string.
const LogString *climate_swing_mode_to_string(ClimateSwingMode mode);
const char *climate_swing_mode_to_string(ClimateSwingMode mode);
/// Convert the given ClimateSwingMode to a human-readable string.
const LogString *climate_preset_to_string(ClimatePreset preset);
const char *climate_preset_to_string(ClimatePreset preset);
} // namespace climate
} // namespace esphome

View File

@@ -1,5 +1,5 @@
#include "climate_traits.h"
#include <cstdio>
#include "esphome/core/log.h"
namespace esphome {
namespace climate {

View File

@@ -72,7 +72,6 @@ class ClimateTraits {
void set_supported_fan_modes(std::set<ClimateFanMode> modes) { supported_fan_modes_ = std::move(modes); }
void add_supported_fan_mode(ClimateFanMode mode) { supported_fan_modes_.insert(mode); }
void add_supported_custom_fan_mode(const std::string &mode) { supported_custom_fan_modes_.insert(mode); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
void set_supports_fan_mode_on(bool supported) { set_fan_mode_support_(CLIMATE_FAN_ON, supported); }
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
@@ -105,7 +104,6 @@ class ClimateTraits {
void set_supported_presets(std::set<ClimatePreset> presets) { supported_presets_ = std::move(presets); }
void add_supported_preset(ClimatePreset preset) { supported_presets_.insert(preset); }
void add_supported_custom_preset(const std::string &preset) { supported_custom_presets_.insert(preset); }
bool supports_preset(ClimatePreset preset) const { return supported_presets_.count(preset); }
bool get_supports_presets() const { return !supported_presets_.empty(); }
const std::set<climate::ClimatePreset> &get_supported_presets() const { return supported_presets_; }

View File

@@ -52,7 +52,7 @@ void ClimateIR::setup() {
this->swing_mode = climate::CLIMATE_SWING_OFF;
}
// Never send nan to HA
if (std::isnan(this->target_temperature))
if (isnan(this->target_temperature))
this->target_temperature = 24;
}

View File

@@ -4,21 +4,18 @@ from esphome import automation
from esphome.automation import maybe_simple_id, Condition
from esphome.components import mqtt
from esphome.const import (
CONF_DISABLED_BY_DEFAULT,
CONF_ID,
CONF_INTERNAL,
CONF_DEVICE_CLASS,
CONF_STATE,
CONF_POSITION,
CONF_POSITION_COMMAND_TOPIC,
CONF_POSITION_STATE_TOPIC,
CONF_TILT,
CONF_TILT_COMMAND_TOPIC,
CONF_TILT_STATE_TOPIC,
CONF_STOP,
CONF_MQTT_ID,
CONF_TRIGGER_ID,
CONF_NAME,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity
IS_PLATFORM_COMPONENT = True
@@ -39,7 +36,7 @@ DEVICE_CLASSES = [
cover_ns = cg.esphome_ns.namespace("cover")
Cover = cover_ns.class_("Cover", cg.EntityBase)
Cover = cover_ns.class_("Cover", cg.Nameable)
COVER_OPEN = cover_ns.COVER_OPEN
COVER_CLOSED = cover_ns.COVER_CLOSED
@@ -62,84 +59,33 @@ validate_cover_operation = cv.enum(COVER_OPERATIONS, upper=True)
OpenAction = cover_ns.class_("OpenAction", automation.Action)
CloseAction = cover_ns.class_("CloseAction", automation.Action)
StopAction = cover_ns.class_("StopAction", automation.Action)
ToggleAction = cover_ns.class_("ToggleAction", automation.Action)
ControlAction = cover_ns.class_("ControlAction", automation.Action)
CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action)
CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition)
CoverIsClosedCondition = cover_ns.class_("CoverIsClosedCondition", Condition)
# Triggers
CoverOpenTrigger = cover_ns.class_("CoverOpenTrigger", automation.Trigger.template())
CoverClosedTrigger = cover_ns.class_(
"CoverClosedTrigger", automation.Trigger.template()
)
CONF_ON_OPEN = "on_open"
CONF_ON_CLOSED = "on_closed"
COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
COVER_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(Cover),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent),
cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_TILT_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_TILT_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_ON_OPEN): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenTrigger),
}
),
cv.Optional(CONF_ON_CLOSED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverClosedTrigger),
}
),
# TODO: MQTT topic options
}
)
async def setup_cover_core_(var, config):
await setup_entity(var, config)
cg.add(var.set_name(config[CONF_NAME]))
cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
if CONF_INTERNAL in config:
cg.add(var.set_internal(config[CONF_INTERNAL]))
if CONF_DEVICE_CLASS in config:
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
for conf in config.get(CONF_ON_OPEN, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_CLOSED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
if CONF_MQTT_ID in config:
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
await mqtt.register_mqtt_component(mqtt_, config)
if CONF_POSITION_STATE_TOPIC in config:
cg.add(
mqtt_.set_custom_position_state_topic(config[CONF_POSITION_STATE_TOPIC])
)
if CONF_POSITION_COMMAND_TOPIC in config:
cg.add(
mqtt_.set_custom_position_command_topic(
config[CONF_POSITION_COMMAND_TOPIC]
)
)
if CONF_TILT_STATE_TOPIC in config:
cg.add(mqtt_.set_custom_tilt_state_topic(config[CONF_TILT_STATE_TOPIC]))
if CONF_TILT_COMMAND_TOPIC in config:
cg.add(mqtt_.set_custom_tilt_command_topic(config[CONF_TILT_COMMAND_TOPIC]))
async def register_cover(var, config):
if not CORE.has_id(config[CONF_ID]):
@@ -173,12 +119,6 @@ async def cover_stop_to_code(config, action_id, template_arg, args):
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action("cover.toggle", ToggleAction, COVER_ACTION_SCHEMA)
def cover_toggle_to_code(config, action_id, template_arg, args):
paren = yield cg.get_variable(config[CONF_ID])
yield cg.new_Pvariable(action_id, template_arg, paren)
COVER_CONTROL_ACTION_SCHEMA = cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(Cover),

View File

@@ -11,7 +11,7 @@ template<typename... Ts> class OpenAction : public Action<Ts...> {
public:
explicit OpenAction(Cover *cover) : cover_(cover) {}
void play(Ts... x) override { this->cover_->make_call().set_command_open().perform(); }
void play(Ts... x) override { this->cover_->open(); }
protected:
Cover *cover_;
@@ -21,7 +21,7 @@ template<typename... Ts> class CloseAction : public Action<Ts...> {
public:
explicit CloseAction(Cover *cover) : cover_(cover) {}
void play(Ts... x) override { this->cover_->make_call().set_command_close().perform(); }
void play(Ts... x) override { this->cover_->close(); }
protected:
Cover *cover_;
@@ -31,17 +31,7 @@ template<typename... Ts> class StopAction : public Action<Ts...> {
public:
explicit StopAction(Cover *cover) : cover_(cover) {}
void play(Ts... x) override { this->cover_->make_call().set_command_stop().perform(); }
protected:
Cover *cover_;
};
template<typename... Ts> class ToggleAction : public Action<Ts...> {
public:
explicit ToggleAction(Cover *cover) : cover_(cover) {}
void play(Ts... x) override { this->cover_->make_call().set_command_toggle().perform(); }
void play(Ts... x) override { this->cover_->stop(); }
protected:
Cover *cover_;
@@ -99,7 +89,6 @@ template<typename... Ts> class CoverIsOpenCondition : public Condition<Ts...> {
protected:
Cover *cover_;
};
template<typename... Ts> class CoverIsClosedCondition : public Condition<Ts...> {
public:
CoverIsClosedCondition(Cover *cover) : cover_(cover) {}
@@ -109,27 +98,5 @@ template<typename... Ts> class CoverIsClosedCondition : public Condition<Ts...>
Cover *cover_;
};
class CoverOpenTrigger : public Trigger<> {
public:
CoverOpenTrigger(Cover *a_cover) {
a_cover->add_on_state_callback([this, a_cover]() {
if (a_cover->is_fully_open()) {
this->trigger();
}
});
}
};
class CoverClosedTrigger : public Trigger<> {
public:
CoverClosedTrigger(Cover *a_cover) {
a_cover->add_on_state_callback([this, a_cover]() {
if (a_cover->is_fully_closed()) {
this->trigger();
}
});
}
};
} // namespace cover
} // namespace esphome

View File

@@ -31,7 +31,7 @@ const char *cover_operation_to_str(CoverOperation op) {
}
}
Cover::Cover(const std::string &name) : EntityBase(name), position{COVER_OPEN} {}
Cover::Cover(const std::string &name) : Nameable(name), position{COVER_OPEN} {}
uint32_t Cover::hash_base() { return 1727367479UL; }
@@ -43,8 +43,6 @@ CoverCall &CoverCall::set_command(const char *command) {
this->set_command_close();
} else if (strcasecmp(command, "STOP") == 0) {
this->set_command_stop();
} else if (strcasecmp(command, "TOGGLE") == 0) {
this->set_command_toggle();
} else {
ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command);
}
@@ -62,10 +60,6 @@ CoverCall &CoverCall::set_command_stop() {
this->stop_ = true;
return *this;
}
CoverCall &CoverCall::set_command_toggle() {
this->toggle_ = true;
return *this;
}
CoverCall &CoverCall::set_position(float position) {
this->position_ = position;
return *this;
@@ -91,14 +85,10 @@ void CoverCall::perform() {
if (this->tilt_.has_value()) {
ESP_LOGD(TAG, " Tilt: %.0f%%", *this->tilt_ * 100.0f);
}
if (this->toggle_.has_value()) {
ESP_LOGD(TAG, " Command: TOGGLE");
}
this->parent_->control(*this);
}
const optional<float> &CoverCall::get_position() const { return this->position_; }
const optional<float> &CoverCall::get_tilt() const { return this->tilt_; }
const optional<bool> &CoverCall::get_toggle() const { return this->toggle_; }
void CoverCall::validate_() {
auto traits = this->parent_->get_traits();
if (this->position_.has_value()) {
@@ -121,12 +111,6 @@ void CoverCall::validate_() {
this->tilt_ = clamp(tilt, 0.0f, 1.0f);
}
}
if (this->toggle_.has_value()) {
if (!traits.get_supports_toggle()) {
ESP_LOGW(TAG, "'%s' - This cover device does not support toggle!", this->parent_->get_name().c_str());
this->toggle_.reset();
}
}
if (this->stop_) {
if (this->position_.has_value()) {
ESP_LOGW(TAG, "Cannot set position when stopping a cover!");
@@ -136,10 +120,6 @@ void CoverCall::validate_() {
ESP_LOGW(TAG, "Cannot set tilt when stopping a cover!");
this->tilt_.reset();
}
if (this->toggle_.has_value()) {
ESP_LOGW(TAG, "Cannot set toggle when stopping a cover!");
this->toggle_.reset();
}
}
}
CoverCall &CoverCall::set_stop(bool stop) {
@@ -200,7 +180,7 @@ void Cover::publish_state(bool save) {
}
}
optional<CoverRestoreState> Cover::restore_state_() {
this->rtc_ = global_preferences->make_preference<CoverRestoreState>(this->get_object_id_hash());
this->rtc_ = global_preferences.make_preference<CoverRestoreState>(this->get_object_id_hash());
CoverRestoreState recovered{};
if (!this->rtc_.load(&recovered))
return {};

View File

@@ -1,7 +1,6 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
#include "cover_traits.h"
@@ -14,7 +13,7 @@ const extern float COVER_CLOSED;
#define LOG_COVER(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \
auto traits_ = (obj)->get_traits(); \
if (traits_.get_is_assumed_state()) { \
ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \
@@ -30,7 +29,7 @@ class CoverCall {
public:
CoverCall(Cover *parent);
/// Set the command as a string, "STOP", "OPEN", "CLOSE", "TOGGLE".
/// Set the command as a string, "STOP", "OPEN", "CLOSE".
CoverCall &set_command(const char *command);
/// Set the command to open the cover.
CoverCall &set_command_open();
@@ -38,8 +37,6 @@ class CoverCall {
CoverCall &set_command_close();
/// Set the command to stop the cover.
CoverCall &set_command_stop();
/// Set the command to toggle the cover.
CoverCall &set_command_toggle();
/// Set the call to a certain target position.
CoverCall &set_position(float position);
/// Set the call to a certain target tilt.
@@ -53,7 +50,6 @@ class CoverCall {
const optional<float> &get_position() const;
bool get_stop() const;
const optional<float> &get_tilt() const;
const optional<bool> &get_toggle() const;
protected:
void validate_();
@@ -62,7 +58,6 @@ class CoverCall {
bool stop_{false};
optional<float> position_{};
optional<float> tilt_{};
optional<bool> toggle_{};
};
/// Struct used to store the restored state of a cover
@@ -108,7 +103,7 @@ const char *cover_operation_to_str(CoverOperation op);
* to control all values of the cover. Also implement get_traits() to return what operations
* the cover supports.
*/
class Cover : public EntityBase {
class Cover : public Nameable {
public:
explicit Cover();
explicit Cover(const std::string &name);
@@ -130,19 +125,16 @@ class Cover : public EntityBase {
*
* This is a legacy method and may be removed later, please use `.make_call()` instead.
*/
ESPDEPRECATED("open() is deprecated, use make_call().set_command_open() instead.", "2021.9")
void open();
/** Close the cover.
*
* This is a legacy method and may be removed later, please use `.make_call()` instead.
*/
ESPDEPRECATED("close() is deprecated, use make_call().set_command_close() instead.", "2021.9")
void close();
/** Stop the cover.
*
* This is a legacy method and may be removed later, please use `.make_call()` instead.
*/
ESPDEPRECATED("stop() is deprecated, use make_call().set_command_stop() instead.", "2021.9")
void stop();
void add_on_state_callback(std::function<void()> &&f);

View File

@@ -13,14 +13,11 @@ class CoverTraits {
void set_supports_position(bool supports_position) { this->supports_position_ = supports_position; }
bool get_supports_tilt() const { return this->supports_tilt_; }
void set_supports_tilt(bool supports_tilt) { this->supports_tilt_ = supports_tilt; }
bool get_supports_toggle() const { return this->supports_toggle_; }
void set_supports_toggle(bool supports_toggle) { this->supports_toggle_ = supports_toggle; }
protected:
bool is_assumed_state_{false};
bool supports_position_{false};
bool supports_tilt_{false};
bool supports_toggle_{false};
};
} // namespace cover

View File

@@ -102,6 +102,8 @@ void CS5460AComponent::hw_init_() {
/* Doesn't reset the register values etc., just restarts the "computation cycle" */
void CS5460AComponent::restart_() {
int cnt;
this->enable();
/* Stop running conversion, wake up if needed */
this->write_byte(CMD_POWER_UP);

View File

@@ -1,244 +0,0 @@
#include "cse7761.h"
#include "esphome/core/log.h"
namespace esphome {
namespace cse7761 {
static const char *const TAG = "cse7761";
/*********************************************************************************************\
* CSE7761 - Energy (Sonoff Dual R3 Pow v1.x)
*
* Based on Tasmota source code
* See https://github.com/arendst/Tasmota/discussions/10793
* https://github.com/arendst/Tasmota/blob/development/tasmota/xnrg_19_cse7761.ino
\*********************************************************************************************/
static const int CSE7761_UREF = 42563; // RmsUc
static const int CSE7761_IREF = 52241; // RmsIAC
static const int CSE7761_PREF = 44513; // PowerPAC
static const uint8_t CSE7761_REG_SYSCON = 0x00; // (2) System Control Register (0x0A04)
static const uint8_t CSE7761_REG_EMUCON = 0x01; // (2) Metering control register (0x0000)
static const uint8_t CSE7761_REG_EMUCON2 = 0x13; // (2) Metering control register 2 (0x0001)
static const uint8_t CSE7761_REG_PULSE1SEL = 0x1D; // (2) Pin function output select register (0x3210)
static const uint8_t CSE7761_REG_RMSIA = 0x24; // (3) The effective value of channel A current (0x000000)
static const uint8_t CSE7761_REG_RMSIB = 0x25; // (3) The effective value of channel B current (0x000000)
static const uint8_t CSE7761_REG_RMSU = 0x26; // (3) Voltage RMS (0x000000)
static const uint8_t CSE7761_REG_POWERPA = 0x2C; // (4) Channel A active power, update rate 27.2Hz (0x00000000)
static const uint8_t CSE7761_REG_POWERPB = 0x2D; // (4) Channel B active power, update rate 27.2Hz (0x00000000)
static const uint8_t CSE7761_REG_SYSSTATUS = 0x43; // (1) System status register
static const uint8_t CSE7761_REG_COEFFCHKSUM = 0x6F; // (2) Coefficient checksum
static const uint8_t CSE7761_REG_RMSIAC = 0x70; // (2) Channel A effective current conversion coefficient
static const uint8_t CSE7761_SPECIAL_COMMAND = 0xEA; // Start special command
static const uint8_t CSE7761_CMD_RESET = 0x96; // Reset command, after receiving the command, the chip resets
static const uint8_t CSE7761_CMD_CLOSE_WRITE = 0xDC; // Close write operation
static const uint8_t CSE7761_CMD_ENABLE_WRITE = 0xE5; // Enable write operation
enum CSE7761 { RMS_IAC, RMS_IBC, RMS_UC, POWER_PAC, POWER_PBC, POWER_SC, ENERGY_AC, ENERGY_BC };
void CSE7761Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up CSE7761...");
this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_RESET);
uint16_t syscon = this->read_(0x00, 2); // Default 0x0A04
if ((0x0A04 == syscon) && this->chip_init_()) {
this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_CLOSE_WRITE);
ESP_LOGD(TAG, "CSE7761 found");
this->data_.ready = true;
} else {
this->mark_failed();
}
}
void CSE7761Component::dump_config() {
ESP_LOGCONFIG(TAG, "CSE7761:");
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with CSE7761 failed!");
}
LOG_UPDATE_INTERVAL(this);
this->check_uart_settings(38400, 1, uart::UART_CONFIG_PARITY_EVEN, 8);
}
float CSE7761Component::get_setup_priority() const { return setup_priority::DATA; }
void CSE7761Component::update() {
if (this->data_.ready) {
this->get_data_();
}
}
void CSE7761Component::write_(uint8_t reg, uint16_t data) {
uint8_t buffer[5];
buffer[0] = 0xA5;
buffer[1] = reg;
uint32_t len = 2;
if (data) {
if (data < 0xFF) {
buffer[2] = data & 0xFF;
len = 3;
} else {
buffer[2] = (data >> 8) & 0xFF;
buffer[3] = data & 0xFF;
len = 4;
}
uint8_t crc = 0;
for (uint32_t i = 0; i < len; i++) {
crc += buffer[i];
}
buffer[len] = ~crc;
len++;
}
this->write_array(buffer, len);
}
bool CSE7761Component::read_once_(uint8_t reg, uint8_t size, uint32_t *value) {
while (this->available()) {
this->read();
}
this->write_(reg, 0);
uint8_t buffer[8] = {0};
uint32_t rcvd = 0;
for (uint32_t i = 0; i <= size; i++) {
int value = this->read();
if (value > -1 && rcvd < sizeof(buffer) - 1) {
buffer[rcvd++] = value;
}
}
if (!rcvd) {
ESP_LOGD(TAG, "Received 0 bytes for register %hhu", reg);
return false;
}
rcvd--;
uint32_t result = 0;
// CRC check
uint8_t crc = 0xA5 + reg;
for (uint32_t i = 0; i < rcvd; i++) {
result = (result << 8) | buffer[i];
crc += buffer[i];
}
crc = ~crc;
if (crc != buffer[rcvd]) {
return false;
}
*value = result;
return true;
}
uint32_t CSE7761Component::read_(uint8_t reg, uint8_t size) {
bool result = false; // Start loop
uint8_t retry = 3; // Retry up to three times
uint32_t value = 0; // Default no value
while (!result && retry > 0) {
retry--;
if (this->read_once_(reg, size, &value))
return value;
}
ESP_LOGE(TAG, "Reading register %hhu failed!", reg);
return value;
}
uint32_t CSE7761Component::coefficient_by_unit_(uint32_t unit) {
switch (unit) {
case RMS_UC:
return 0x400000 * 100 / this->data_.coefficient[RMS_UC];
case RMS_IAC:
return (0x800000 * 100 / this->data_.coefficient[RMS_IAC]) * 10; // Stay within 32 bits
case POWER_PAC:
return 0x80000000 / this->data_.coefficient[POWER_PAC];
}
return 0;
}
bool CSE7761Component::chip_init_() {
uint16_t calc_chksum = 0xFFFF;
for (uint32_t i = 0; i < 8; i++) {
this->data_.coefficient[i] = this->read_(CSE7761_REG_RMSIAC + i, 2);
calc_chksum += this->data_.coefficient[i];
}
calc_chksum = ~calc_chksum;
uint16_t coeff_chksum = this->read_(CSE7761_REG_COEFFCHKSUM, 2);
if ((calc_chksum != coeff_chksum) || (!calc_chksum)) {
ESP_LOGD(TAG, "Default calibration");
this->data_.coefficient[RMS_IAC] = CSE7761_IREF;
this->data_.coefficient[RMS_UC] = CSE7761_UREF;
this->data_.coefficient[POWER_PAC] = CSE7761_PREF;
}
this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_ENABLE_WRITE);
uint8_t sys_status = this->read_(CSE7761_REG_SYSSTATUS, 1);
if (sys_status & 0x10) { // Write enable to protected registers (WREN)
this->write_(CSE7761_REG_SYSCON | 0x80, 0xFF04);
this->write_(CSE7761_REG_EMUCON | 0x80, 0x1183);
this->write_(CSE7761_REG_EMUCON2 | 0x80, 0x0FC1);
this->write_(CSE7761_REG_PULSE1SEL | 0x80, 0x3290);
} else {
ESP_LOGD(TAG, "Write failed at chip_init");
return false;
}
return true;
}
void CSE7761Component::get_data_() {
// The effective value of current and voltage Rms is a 24-bit signed number,
// the highest bit is 0 for valid data,
// and when the highest bit is 1, the reading will be processed as zero
// The active power parameter PowerA/B is in twos complement format, 32-bit
// data, the highest bit is Sign bit.
uint32_t value = this->read_(CSE7761_REG_RMSU, 3);
this->data_.voltage_rms = (value >= 0x800000) ? 0 : value;
value = this->read_(CSE7761_REG_RMSIA, 3);
this->data_.current_rms[0] = ((value >= 0x800000) || (value < 1600)) ? 0 : value; // No load threshold of 10mA
value = this->read_(CSE7761_REG_POWERPA, 4);
this->data_.active_power[0] = (0 == this->data_.current_rms[0]) ? 0 : ((uint32_t) abs((int) value));
value = this->read_(CSE7761_REG_RMSIB, 3);
this->data_.current_rms[1] = ((value >= 0x800000) || (value < 1600)) ? 0 : value; // No load threshold of 10mA
value = this->read_(CSE7761_REG_POWERPB, 4);
this->data_.active_power[1] = (0 == this->data_.current_rms[1]) ? 0 : ((uint32_t) abs((int) value));
// convert values and publish to sensors
float voltage = (float) this->data_.voltage_rms / this->coefficient_by_unit_(RMS_UC);
if (this->voltage_sensor_ != nullptr) {
this->voltage_sensor_->publish_state(voltage);
}
for (uint32_t channel = 0; channel < 2; channel++) {
// Active power = PowerPA * PowerPAC * 1000 / 0x80000000
float active_power = (float) this->data_.active_power[channel] / this->coefficient_by_unit_(POWER_PAC); // W
float amps = (float) this->data_.current_rms[channel] / this->coefficient_by_unit_(RMS_IAC); // A
ESP_LOGD(TAG, "Channel %d power %f W, current %f A", channel + 1, active_power, amps);
if (channel == 0) {
if (this->power_sensor_1_ != nullptr) {
this->power_sensor_1_->publish_state(active_power);
}
if (this->current_sensor_1_ != nullptr) {
this->current_sensor_1_->publish_state(amps);
}
} else if (channel == 1) {
if (this->power_sensor_2_ != nullptr) {
this->power_sensor_2_->publish_state(active_power);
}
if (this->current_sensor_2_ != nullptr) {
this->current_sensor_2_->publish_state(amps);
}
}
}
}
} // namespace cse7761
} // namespace esphome

View File

@@ -1,52 +0,0 @@
#pragma once
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h"
#include "esphome/core/component.h"
namespace esphome {
namespace cse7761 {
struct CSE7761DataStruct {
uint32_t frequency = 0;
uint32_t voltage_rms = 0;
uint32_t current_rms[2] = {0};
uint32_t energy[2] = {0};
uint32_t active_power[2] = {0};
uint16_t coefficient[8] = {0};
uint8_t energy_update = 0;
bool ready = false;
};
/// This class implements support for the CSE7761 UART power sensor.
class CSE7761Component : public PollingComponent, public uart::UARTDevice {
public:
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
void set_active_power_1_sensor(sensor::Sensor *power_sensor_1) { power_sensor_1_ = power_sensor_1; }
void set_current_1_sensor(sensor::Sensor *current_sensor_1) { current_sensor_1_ = current_sensor_1; }
void set_active_power_2_sensor(sensor::Sensor *power_sensor_2) { power_sensor_2_ = power_sensor_2; }
void set_current_2_sensor(sensor::Sensor *current_sensor_2) { current_sensor_2_ = current_sensor_2; }
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
protected:
// Sensors
sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *power_sensor_1_{nullptr};
sensor::Sensor *current_sensor_1_{nullptr};
sensor::Sensor *power_sensor_2_{nullptr};
sensor::Sensor *current_sensor_2_{nullptr};
CSE7761DataStruct data_;
void write_(uint8_t reg, uint16_t data);
bool read_once_(uint8_t reg, uint8_t size, uint32_t *value);
uint32_t read_(uint8_t reg, uint8_t size);
uint32_t coefficient_by_unit_(uint32_t unit);
bool chip_init_();
void get_data_();
};
} // namespace cse7761
} // namespace esphome

View File

@@ -1,90 +0,0 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, uart
from esphome.const import (
CONF_ID,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_POWER,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
)
CODEOWNERS = ["@berfenger"]
DEPENDENCIES = ["uart"]
cse7761_ns = cg.esphome_ns.namespace("cse7761")
CSE7761Component = cse7761_ns.class_(
"CSE7761Component", cg.PollingComponent, uart.UARTDevice
)
CONF_CURRENT_1 = "current_1"
CONF_CURRENT_2 = "current_2"
CONF_ACTIVE_POWER_1 = "active_power_1"
CONF_ACTIVE_POWER_2 = "active_power_2"
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(CSE7761Component),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT_1): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT_2): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ACTIVE_POWER_1): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ACTIVE_POWER_2): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(uart.UART_DEVICE_SCHEMA)
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"cse7761", baud_rate=38400, require_rx=True, require_tx=True
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
for key in [
CONF_VOLTAGE,
CONF_CURRENT_1,
CONF_CURRENT_2,
CONF_ACTIVE_POWER_1,
CONF_ACTIVE_POWER_2,
]:
if key not in config:
continue
conf = config[key]
sens = await sensor.new_sensor(conf)
cg.add(getattr(var, f"set_{key}_sensor")(sens))

View File

@@ -90,7 +90,6 @@ void CSE7766Component::parse_data_() {
uint32_t power_cycle = this->get_24_bit_uint_(17);
uint8_t adj = this->raw_data_[20];
uint32_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22];
bool power_ok = true;
bool voltage_ok = true;
@@ -128,18 +127,6 @@ void CSE7766Component::parse_data_() {
power = power_calib / float(power_cycle);
this->power_acc_ += power;
this->power_counts_ += 1;
uint32_t difference;
if (this->cf_pulses_last_ == 0)
this->cf_pulses_last_ = cf_pulses;
if (cf_pulses < this->cf_pulses_last_) {
difference = cf_pulses + (0x10000 - this->cf_pulses_last_);
} else {
difference = cf_pulses - this->cf_pulses_last_;
}
this->cf_pulses_last_ = cf_pulses;
this->energy_total_ += difference * float(power_calib) / 1000000.0 / 3600.0;
}
if ((adj & 0x20) == 0x20 && current_ok && voltage_ok && power != 0.0) {
@@ -149,9 +136,9 @@ void CSE7766Component::parse_data_() {
}
}
void CSE7766Component::update() {
float voltage = this->voltage_counts_ > 0 ? this->voltage_acc_ / this->voltage_counts_ : 0.0f;
float current = this->current_counts_ > 0 ? this->current_acc_ / this->current_counts_ : 0.0f;
float power = this->power_counts_ > 0 ? this->power_acc_ / this->power_counts_ : 0.0f;
float voltage = this->voltage_counts_ > 0 ? this->voltage_acc_ / this->voltage_counts_ : 0.0;
float current = this->current_counts_ > 0 ? this->current_acc_ / this->current_counts_ : 0.0;
float power = this->power_counts_ > 0 ? this->power_acc_ / this->power_counts_ : 0.0;
ESP_LOGV(TAG, "Got voltage_acc=%.2f current_acc=%.2f power_acc=%.2f", this->voltage_acc_, this->current_acc_,
this->power_acc_);
@@ -165,8 +152,6 @@ void CSE7766Component::update() {
this->current_sensor_->publish_state(current);
if (this->power_sensor_ != nullptr)
this->power_sensor_->publish_state(power);
if (this->energy_sensor_ != nullptr)
this->energy_sensor_->publish_state(this->energy_total_);
this->voltage_acc_ = 0.0f;
this->current_acc_ = 0.0f;
@@ -187,7 +172,6 @@ void CSE7766Component::dump_config() {
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
LOG_SENSOR(" ", "Current", this->current_sensor_);
LOG_SENSOR(" ", "Power", this->power_sensor_);
LOG_SENSOR(" ", "Energy", this->energy_sensor_);
this->check_uart_settings(4800);
}

View File

@@ -12,7 +12,6 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice {
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
void loop() override;
float get_setup_priority() const override;
@@ -30,12 +29,9 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice {
sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *power_sensor_{nullptr};
sensor::Sensor *energy_sensor_{nullptr};
float voltage_acc_{0.0f};
float current_acc_{0.0f};
float power_acc_{0.0f};
float energy_total_{0.0f};
uint32_t cf_pulses_last_{0};
uint32_t voltage_counts_{0};
uint32_t current_counts_{0};
uint32_t power_counts_{0};

View File

@@ -3,20 +3,16 @@ import esphome.config_validation as cv
from esphome.components import sensor, uart
from esphome.const import (
CONF_CURRENT,
CONF_ENERGY,
CONF_ID,
CONF_POWER,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
UNIT_WATT_HOURS,
)
DEPENDENCIES = ["uart"]
@@ -48,12 +44,6 @@ CONFIG_SCHEMA = (
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ENERGY): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
}
)
.extend(cv.polling_component_schema("60s"))
@@ -81,7 +71,3 @@ async def to_code(config):
conf = config[CONF_POWER]
sens = await sensor.new_sensor(conf)
cg.add(var.set_power_sensor(sens))
if CONF_ENERGY in config:
conf = config[CONF_ENERGY]
sens = await sensor.new_sensor(conf)
cg.add(var.set_energy_sensor(sens))

View File

@@ -8,6 +8,18 @@ namespace ct_clamp {
static const char *const TAG = "ct_clamp";
void CTClampSensor::setup() {
this->is_calibrating_offset_ = true;
this->high_freq_.start();
this->set_timeout("calibrate_offset", this->sample_duration_, [this]() {
this->high_freq_.stop();
this->is_calibrating_offset_ = false;
if (this->num_samples_ != 0) {
this->offset_ = this->sample_sum_ / this->num_samples_;
}
});
}
void CTClampSensor::dump_config() {
LOG_SENSOR("", "CT Clamp Sensor", this);
ESP_LOGCONFIG(TAG, " Sample Duration: %.2fs", this->sample_duration_ / 1e3f);
@@ -15,6 +27,9 @@ void CTClampSensor::dump_config() {
}
void CTClampSensor::update() {
if (this->is_calibrating_offset_)
return;
// Update only starts the sampling phase, in loop() the actual sampling is happening.
// Request a high loop() execution interval during sampling phase.
@@ -31,39 +46,44 @@ void CTClampSensor::update() {
return;
}
const float rms_ac_dc_squared = this->sample_squared_sum_ / this->num_samples_;
const float rms_dc = this->sample_sum_ / this->num_samples_;
const float rms_ac = std::sqrt(rms_ac_dc_squared - rms_dc * rms_dc);
ESP_LOGD(TAG, "'%s' - Raw AC Value: %.3fA after %d different samples (%d SPS)", this->name_.c_str(), rms_ac,
this->num_samples_, 1000 * this->num_samples_ / this->sample_duration_);
this->publish_state(rms_ac);
float raw = this->sample_sum_ / this->num_samples_;
float irms = std::sqrt(raw);
ESP_LOGD(TAG, "'%s' - Raw Value: %.2fA", this->name_.c_str(), irms);
this->publish_state(irms);
});
// Set sampling values
this->last_value_ = 0.0;
this->is_sampling_ = true;
this->num_samples_ = 0;
this->sample_sum_ = 0.0f;
this->sample_squared_sum_ = 0.0f;
this->is_sampling_ = true;
}
void CTClampSensor::loop() {
if (!this->is_sampling_)
if (!this->is_sampling_ && !this->is_calibrating_offset_)
return;
// Perform a single sample
float value = this->source_->sample();
if (std::isnan(value))
if (isnan(value))
return;
// Assuming a sine wave, avoid requesting values faster than the ADC can provide them
if (this->last_value_ == value)
if (this->is_calibrating_offset_) {
this->sample_sum_ += value;
this->num_samples_++;
return;
this->last_value_ = value;
}
// Adjust DC offset via low pass filter (exponential moving average)
const float alpha = 0.001f;
this->offset_ = this->offset_ * (1 - alpha) + value * alpha;
// Filtered value centered around the mid-point (0V)
float filtered = value - this->offset_;
// IRMS is sqrt(∑v_i²)
float sq = filtered * filtered;
this->sample_sum_ += sq;
this->num_samples_++;
this->sample_sum_ += value;
this->sample_squared_sum_ += value * value;
}
} // namespace ct_clamp

View File

@@ -1,7 +1,7 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/esphal.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/voltage_sampler/voltage_sampler.h"
@@ -10,6 +10,7 @@ namespace ct_clamp {
class CTClampSensor : public sensor::Sensor, public PollingComponent {
public:
void setup() override;
void update() override;
void loop() override;
void dump_config() override;
@@ -34,20 +35,17 @@ class CTClampSensor : public sensor::Sensor, public PollingComponent {
*
* Diagram: https://learn.openenergymonitor.org/electricity-monitoring/ct-sensors/interface-with-arduino
*
* The current clamp only measures AC, so any DC component is an unwanted artifact from the
* sampling circuit. The AC component is essentially the same as the calculating the Standard-Deviation,
* which can be done by cumulating 3 values per sample:
* 1) Number of samples
* 2) Sum of samples
* 3) Sum of sample squared
* https://en.wikipedia.org/wiki/Root_mean_square
* This is automatically calculated with an exponential moving average/digital low pass filter.
*
* 0.5 is a good initial approximation to start with for most ESP8266 setups.
*/
float offset_ = 0.5f;
float last_value_ = 0.0f;
float sample_sum_ = 0.0f;
float sample_squared_sum_ = 0.0f;
uint32_t num_samples_ = 0;
bool is_sampling_ = false;
/// Calibrate offset value once at boot
bool is_calibrating_offset_ = false;
};
} // namespace ct_clamp

View File

@@ -1 +0,0 @@
CODEOWNERS = ["@djwmarcx"]

View File

@@ -1,124 +0,0 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import sensor, cover
from esphome.const import (
CONF_CLOSE_ACTION,
CONF_CLOSE_DURATION,
CONF_ID,
CONF_OPEN_ACTION,
CONF_OPEN_DURATION,
CONF_STOP_ACTION,
CONF_MAX_DURATION,
)
CONF_OPEN_SENSOR = "open_sensor"
CONF_OPEN_MOVING_CURRENT_THRESHOLD = "open_moving_current_threshold"
CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD = "open_obstacle_current_threshold"
CONF_CLOSE_SENSOR = "close_sensor"
CONF_CLOSE_MOVING_CURRENT_THRESHOLD = "close_moving_current_threshold"
CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD = "close_obstacle_current_threshold"
CONF_OBSTACLE_ROLLBACK = "obstacle_rollback"
CONF_MALFUNCTION_DETECTION = "malfunction_detection"
CONF_MALFUNCTION_ACTION = "malfunction_action"
CONF_START_SENSING_DELAY = "start_sensing_delay"
current_based_ns = cg.esphome_ns.namespace("current_based")
CurrentBasedCover = current_based_ns.class_(
"CurrentBasedCover", cover.Cover, cg.Component
)
CONFIG_SCHEMA = cover.COVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(CurrentBasedCover),
cv.Required(CONF_STOP_ACTION): automation.validate_automation(single=True),
cv.Required(CONF_OPEN_SENSOR): cv.use_id(sensor.Sensor),
cv.Required(CONF_OPEN_MOVING_CURRENT_THRESHOLD): cv.float_range(
min=0, min_included=False
),
cv.Optional(CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD): cv.float_range(
min=0, min_included=False
),
cv.Required(CONF_OPEN_ACTION): automation.validate_automation(single=True),
cv.Required(CONF_OPEN_DURATION): cv.positive_time_period_milliseconds,
cv.Required(CONF_CLOSE_SENSOR): cv.use_id(sensor.Sensor),
cv.Required(CONF_CLOSE_MOVING_CURRENT_THRESHOLD): cv.float_range(
min=0, min_included=False
),
cv.Optional(CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD): cv.float_range(
min=0, min_included=False
),
cv.Required(CONF_CLOSE_ACTION): automation.validate_automation(single=True),
cv.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds,
cv.Optional(CONF_OBSTACLE_ROLLBACK, default="10%"): cv.percentage,
cv.Optional(CONF_MAX_DURATION): cv.positive_time_period_milliseconds,
cv.Optional(CONF_MALFUNCTION_DETECTION, default=True): cv.boolean,
cv.Optional(CONF_MALFUNCTION_ACTION): automation.validate_automation(
single=True
),
cv.Optional(
CONF_START_SENSING_DELAY, default="500ms"
): cv.positive_time_period_milliseconds,
}
).extend(cv.COMPONENT_SCHEMA)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield cover.register_cover(var, config)
yield automation.build_automation(
var.get_stop_trigger(), [], config[CONF_STOP_ACTION]
)
# OPEN
bin = yield cg.get_variable(config[CONF_OPEN_SENSOR])
cg.add(var.set_open_sensor(bin))
cg.add(
var.set_open_moving_current_threshold(
config[CONF_OPEN_MOVING_CURRENT_THRESHOLD]
)
)
if CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD in config:
cg.add(
var.set_open_obstacle_current_threshold(
config[CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD]
)
)
cg.add(var.set_open_duration(config[CONF_OPEN_DURATION]))
yield automation.build_automation(
var.get_open_trigger(), [], config[CONF_OPEN_ACTION]
)
# CLOSE
bin = yield cg.get_variable(config[CONF_CLOSE_SENSOR])
cg.add(var.set_close_sensor(bin))
cg.add(
var.set_close_moving_current_threshold(
config[CONF_CLOSE_MOVING_CURRENT_THRESHOLD]
)
)
if CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD in config:
cg.add(
var.set_close_obstacle_current_threshold(
config[CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD]
)
)
cg.add(var.set_close_duration(config[CONF_CLOSE_DURATION]))
yield automation.build_automation(
var.get_close_trigger(), [], config[CONF_CLOSE_ACTION]
)
cg.add(var.set_obstacle_rollback(config[CONF_OBSTACLE_ROLLBACK]))
if CONF_MAX_DURATION in config:
cg.add(var.set_max_duration(config[CONF_MAX_DURATION]))
cg.add(var.set_malfunction_detection(config[CONF_MALFUNCTION_DETECTION]))
if CONF_MALFUNCTION_ACTION in config:
yield automation.build_automation(
var.get_malfunction_trigger(), [], config[CONF_MALFUNCTION_ACTION]
)
cg.add(var.set_start_sensing_delay(config[CONF_START_SENSING_DELAY]))

View File

@@ -1,251 +0,0 @@
#include "current_based_cover.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include <cfloat>
namespace esphome {
namespace current_based {
static const char *const TAG = "current_based.cover";
using namespace esphome::cover;
CoverTraits CurrentBasedCover::get_traits() {
auto traits = CoverTraits();
traits.set_supports_position(true);
traits.set_is_assumed_state(false);
return traits;
}
void CurrentBasedCover::control(const CoverCall &call) {
if (call.get_stop()) {
this->direction_idle_();
}
if (call.get_position().has_value()) {
auto pos = *call.get_position();
if (pos == this->position) {
// already at target
} else {
auto op = pos < this->position ? COVER_OPERATION_CLOSING : COVER_OPERATION_OPENING;
this->target_position_ = pos;
this->start_direction_(op);
}
}
}
void CurrentBasedCover::setup() {
auto restore = this->restore_state_();
if (restore.has_value()) {
restore->apply(this);
} else {
this->position = 0.5f;
}
}
void CurrentBasedCover::loop() {
if (this->current_operation == COVER_OPERATION_IDLE)
return;
const uint32_t now = millis();
if (this->current_operation == COVER_OPERATION_OPENING) {
if (this->malfunction_detection_ && this->is_closing_()) { // Malfunction
this->direction_idle_();
this->malfunction_trigger_->trigger();
ESP_LOGI(TAG, "'%s' - Malfunction detected during opening. Current flow detected in close circuit",
this->name_.c_str());
} else if (this->is_opening_blocked_()) { // Blocked
ESP_LOGD(TAG, "'%s' - Obstacle detected during opening.", this->name_.c_str());
this->direction_idle_();
if (this->obstacle_rollback_ != 0) {
this->set_timeout("rollback", 300, [this]() {
ESP_LOGD(TAG, "'%s' - Rollback.", this->name_.c_str());
this->target_position_ = clamp(this->position - this->obstacle_rollback_, 0.0F, 1.0F);
this->start_direction_(COVER_OPERATION_CLOSING);
});
}
} else if (this->is_initial_delay_finished_() && !this->is_opening_()) { // End reached
auto dur = (now - this->start_dir_time_) / 1e3f;
ESP_LOGD(TAG, "'%s' - Open position reached. Took %.1fs.", this->name_.c_str(), dur);
this->direction_idle_(COVER_OPEN);
}
} else if (this->current_operation == COVER_OPERATION_CLOSING) {
if (this->malfunction_detection_ && this->is_opening_()) { // Malfunction
this->direction_idle_();
this->malfunction_trigger_->trigger();
ESP_LOGI(TAG, "'%s' - Malfunction detected during closing. Current flow detected in open circuit",
this->name_.c_str());
} else if (this->is_closing_blocked_()) { // Blocked
ESP_LOGD(TAG, "'%s' - Obstacle detected during closing.", this->name_.c_str());
this->direction_idle_();
if (this->obstacle_rollback_ != 0) {
this->set_timeout("rollback", 300, [this]() {
ESP_LOGD(TAG, "'%s' - Rollback.", this->name_.c_str());
this->target_position_ = clamp(this->position + this->obstacle_rollback_, 0.0F, 1.0F);
this->start_direction_(COVER_OPERATION_OPENING);
});
}
} else if (this->is_initial_delay_finished_() && !this->is_closing_()) { // End reached
auto dur = (now - this->start_dir_time_) / 1e3f;
ESP_LOGD(TAG, "'%s' - Close position reached. Took %.1fs.", this->name_.c_str(), dur);
this->direction_idle_(COVER_CLOSED);
}
} else if (now - this->start_dir_time_ > this->max_duration_) {
ESP_LOGD(TAG, "'%s' - Max duration reached. Stopping cover.", this->name_.c_str());
this->direction_idle_();
}
// Recompute position every loop cycle
this->recompute_position_();
if (this->current_operation != COVER_OPERATION_IDLE && this->is_at_target_()) {
this->direction_idle_();
}
// Send current position every second
if (this->current_operation != COVER_OPERATION_IDLE && now - this->last_publish_time_ > 1000) {
this->publish_state(false);
this->last_publish_time_ = now;
}
}
void CurrentBasedCover::direction_idle_(float new_position) {
this->start_direction_(COVER_OPERATION_IDLE);
if (new_position != FLT_MAX) {
this->position = new_position;
}
this->publish_state();
}
void CurrentBasedCover::dump_config() {
LOG_COVER("", "Endstop Cover", this);
LOG_SENSOR(" ", "Open Sensor", this->open_sensor_);
ESP_LOGCONFIG(TAG, " Open moving current threshold: %.11fA", this->open_moving_current_threshold_);
if (this->open_obstacle_current_threshold_ != FLT_MAX) {
ESP_LOGCONFIG(TAG, " Open obstacle current threshold: %.11fA", this->open_obstacle_current_threshold_);
}
ESP_LOGCONFIG(TAG, " Open Duration: %.1fs", this->open_duration_ / 1e3f);
LOG_SENSOR(" ", "Close Sensor", this->close_sensor_);
ESP_LOGCONFIG(TAG, " Close moving current threshold: %.11fA", this->close_moving_current_threshold_);
if (this->close_obstacle_current_threshold_ != FLT_MAX) {
ESP_LOGCONFIG(TAG, " Close obstacle current threshold: %.11fA", this->close_obstacle_current_threshold_);
}
ESP_LOGCONFIG(TAG, " Close Duration: %.1fs", this->close_duration_ / 1e3f);
ESP_LOGCONFIG(TAG, "Obstacle Rollback: %.1f%%", this->obstacle_rollback_ * 100);
if (this->max_duration_ != UINT32_MAX) {
ESP_LOGCONFIG(TAG, "Maximun duration: %.1fs", this->max_duration_ / 1e3f);
}
ESP_LOGCONFIG(TAG, "Start sensing delay: %.1fs", this->start_sensing_delay_ / 1e3f);
ESP_LOGCONFIG(TAG, "Malfunction detection: %s", YESNO(this->malfunction_detection_));
}
float CurrentBasedCover::get_setup_priority() const { return setup_priority::DATA; }
void CurrentBasedCover::stop_prev_trigger_() {
if (this->prev_command_trigger_ != nullptr) {
this->prev_command_trigger_->stop_action();
this->prev_command_trigger_ = nullptr;
}
}
bool CurrentBasedCover::is_opening_() const {
return this->open_sensor_->get_state() > this->open_moving_current_threshold_;
}
bool CurrentBasedCover::is_opening_blocked_() const {
if (this->open_obstacle_current_threshold_ == FLT_MAX) {
return false;
}
return this->open_sensor_->get_state() > this->open_obstacle_current_threshold_;
}
bool CurrentBasedCover::is_closing_() const {
return this->close_sensor_->get_state() > this->close_moving_current_threshold_;
}
bool CurrentBasedCover::is_closing_blocked_() const {
if (this->close_obstacle_current_threshold_ == FLT_MAX) {
return false;
}
return this->open_sensor_->get_state() > this->open_obstacle_current_threshold_;
}
bool CurrentBasedCover::is_initial_delay_finished_() const {
return millis() - this->start_dir_time_ > this->start_sensing_delay_;
}
bool CurrentBasedCover::is_at_target_() const {
switch (this->current_operation) {
case COVER_OPERATION_OPENING:
if (this->target_position_ == COVER_OPEN) {
if (!this->is_initial_delay_finished_()) // During initial delay, state is assumed
return false;
return !this->is_opening_();
}
return this->position >= this->target_position_;
case COVER_OPERATION_CLOSING:
if (this->target_position_ == COVER_CLOSED) {
if (!this->is_initial_delay_finished_()) // During initial delay, state is assumed
return false;
return !this->is_closing_();
}
return this->position <= this->target_position_;
case COVER_OPERATION_IDLE:
default:
return true;
}
}
void CurrentBasedCover::start_direction_(CoverOperation dir) {
if (dir == this->current_operation)
return;
this->recompute_position_();
Trigger<> *trig;
switch (dir) {
case COVER_OPERATION_IDLE:
trig = this->stop_trigger_;
break;
case COVER_OPERATION_OPENING:
trig = this->open_trigger_;
break;
case COVER_OPERATION_CLOSING:
trig = this->close_trigger_;
break;
default:
return;
}
this->current_operation = dir;
this->stop_prev_trigger_();
trig->trigger();
this->prev_command_trigger_ = trig;
const auto now = millis();
this->start_dir_time_ = now;
this->last_recompute_time_ = now;
}
void CurrentBasedCover::recompute_position_() {
if (this->current_operation == COVER_OPERATION_IDLE)
return;
float dir;
float action_dur;
switch (this->current_operation) {
case COVER_OPERATION_OPENING:
dir = 1.0F;
action_dur = this->open_duration_;
break;
case COVER_OPERATION_CLOSING:
dir = -1.0F;
action_dur = this->close_duration_;
break;
default:
return;
}
const auto now = millis();
this->position += dir * (now - this->last_recompute_time_) / action_dur;
this->position = clamp(this->position, 0.0F, 1.0F);
this->last_recompute_time_ = now;
}
} // namespace current_based
} // namespace esphome

View File

@@ -1,95 +0,0 @@
#pragma once
#include "esphome/components/cover/cover.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include <cfloat>
namespace esphome {
namespace current_based {
class CurrentBasedCover : public cover::Cover, public Component {
public:
void setup() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override;
Trigger<> *get_stop_trigger() const { return this->stop_trigger_; }
Trigger<> *get_open_trigger() const { return this->open_trigger_; }
void set_open_sensor(sensor::Sensor *open_sensor) { this->open_sensor_ = open_sensor; }
void set_open_moving_current_threshold(float open_moving_current_threshold) {
this->open_moving_current_threshold_ = open_moving_current_threshold;
}
void set_open_obstacle_current_threshold(float open_obstacle_current_threshold) {
this->open_obstacle_current_threshold_ = open_obstacle_current_threshold;
}
void set_open_duration(uint32_t open_duration) { this->open_duration_ = open_duration; }
Trigger<> *get_close_trigger() const { return this->close_trigger_; }
void set_close_sensor(sensor::Sensor *close_sensor) { this->close_sensor_ = close_sensor; }
void set_close_moving_current_threshold(float close_moving_current_threshold) {
this->close_moving_current_threshold_ = close_moving_current_threshold;
}
void set_close_obstacle_current_threshold(float close_obstacle_current_threshold) {
this->close_obstacle_current_threshold_ = close_obstacle_current_threshold;
}
void set_close_duration(uint32_t close_duration) { this->close_duration_ = close_duration; }
void set_max_duration(uint32_t max_duration) { this->max_duration_ = max_duration; }
void set_obstacle_rollback(float obstacle_rollback) { this->obstacle_rollback_ = obstacle_rollback; }
void set_malfunction_detection(bool malfunction_detection) { this->malfunction_detection_ = malfunction_detection; }
void set_start_sensing_delay(uint32_t start_sensing_delay) { this->start_sensing_delay_ = start_sensing_delay; }
Trigger<> *get_malfunction_trigger() const { return this->malfunction_trigger_; }
cover::CoverTraits get_traits() override;
protected:
void control(const cover::CoverCall &call) override;
void stop_prev_trigger_();
bool is_at_target_() const;
bool is_opening_() const;
bool is_opening_blocked_() const;
bool is_closing_() const;
bool is_closing_blocked_() const;
bool is_initial_delay_finished_() const;
void direction_idle_(float new_position = FLT_MAX);
void start_direction_(cover::CoverOperation dir);
void recompute_position_();
Trigger<> *stop_trigger_{new Trigger<>()};
sensor::Sensor *open_sensor_{nullptr};
Trigger<> *open_trigger_{new Trigger<>()};
float open_moving_current_threshold_;
float open_obstacle_current_threshold_{FLT_MAX};
uint32_t open_duration_;
sensor::Sensor *close_sensor_{nullptr};
Trigger<> *close_trigger_{new Trigger<>()};
float close_moving_current_threshold_;
float close_obstacle_current_threshold_{FLT_MAX};
uint32_t close_duration_;
uint32_t max_duration_{UINT32_MAX};
bool malfunction_detection_{true};
Trigger<> *malfunction_trigger_{new Trigger<>()};
uint32_t start_sensing_delay_;
float obstacle_rollback_;
Trigger<> *prev_command_trigger_{nullptr};
uint32_t last_recompute_time_{0};
uint32_t start_dir_time_{0};
uint32_t last_publish_time_{0};
float target_position_{0};
};
} // namespace current_based
} // namespace esphome

View File

@@ -231,7 +231,7 @@ bool DaikinClimate::on_receive(remote_base::RemoteReceiveData data) {
// frame header
if (byte != 0x27)
return false;
} else if (pos == 3) { // NOLINT(bugprone-branch-clone)
} else if (pos == 3) {
// frame header
if (byte != 0x00)
return false;

View File

@@ -6,20 +6,22 @@ from esphome.const import CONF_ID, CONF_PIN
MULTI_CONF = True
AUTO_LOAD = ["sensor"]
CONF_ONE_WIRE_ID = "one_wire_id"
dallas_ns = cg.esphome_ns.namespace("dallas")
DallasComponent = dallas_ns.class_("DallasComponent", cg.PollingComponent)
ESPOneWire = dallas_ns.class_("ESPOneWire")
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(DallasComponent),
cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire),
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_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)
pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(pin))
one_wire = cg.new_Pvariable(config[CONF_ONE_WIRE_ID], pin)
var = cg.new_Pvariable(config[CONF_ID], one_wire)
await cg.register_component(var, config)

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