Merge branch 'dev' into bump-2021.10.0b1

This commit is contained in:
Jesse Hills 2021-10-13 16:40:46 +13:00
commit 112215848d
No known key found for this signature in database
GPG key ID: BEAAE804EFD8E83A
861 changed files with 21802 additions and 8720 deletions

View file

@ -2,9 +2,11 @@
Checks: >- Checks: >-
*, *,
-abseil-*, -abseil-*,
-altera-*,
-android-*, -android-*,
-boost-*, -boost-*,
-bugprone-branch-clone, -bugprone-branch-clone,
-bugprone-easily-swappable-parameters,
-bugprone-narrowing-conversions, -bugprone-narrowing-conversions,
-bugprone-signed-char-misuse, -bugprone-signed-char-misuse,
-bugprone-too-small-loop-variable, -bugprone-too-small-loop-variable,
@ -20,6 +22,7 @@ Checks: >-
-clang-diagnostic-sign-compare, -clang-diagnostic-sign-compare,
-clang-diagnostic-unused-variable, -clang-diagnostic-unused-variable,
-clang-diagnostic-unused-const-variable, -clang-diagnostic-unused-const-variable,
-concurrency-*,
-cppcoreguidelines-avoid-c-arrays, -cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-avoid-goto, -cppcoreguidelines-avoid-goto,
-cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-avoid-magic-numbers,
@ -27,7 +30,6 @@ Checks: >-
-cppcoreguidelines-macro-usage, -cppcoreguidelines-macro-usage,
-cppcoreguidelines-narrowing-conversions, -cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-non-private-member-variables-in-classes, -cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-constant-array-index, -cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-pro-bounds-pointer-arithmetic, -cppcoreguidelines-pro-bounds-pointer-arithmetic,
@ -61,17 +63,21 @@ Checks: >-
-misc-no-recursion, -misc-no-recursion,
-misc-unused-parameters, -misc-unused-parameters,
-modernize-avoid-c-arrays, -modernize-avoid-c-arrays,
-modernize-avoid-bind,
-modernize-concat-nested-namespaces,
-modernize-return-braced-init-list, -modernize-return-braced-init-list,
-modernize-use-auto, -modernize-use-auto,
-modernize-use-default-member-init, -modernize-use-default-member-init,
-modernize-use-equals-default, -modernize-use-equals-default,
-modernize-use-trailing-return-type, -modernize-use-trailing-return-type,
-modernize-use-nodiscard,
-mpi-*, -mpi-*,
-objc-*, -objc-*,
-readability-braces-around-statements, -readability-braces-around-statements,
-readability-const-return-type, -readability-const-return-type,
-readability-convert-member-functions-to-static, -readability-convert-member-functions-to-static,
-readability-else-after-return, -readability-else-after-return,
-readability-function-cognitive-complexity,
-readability-implicit-bool-conversion, -readability-implicit-bool-conversion,
-readability-isolate-declaration, -readability-isolate-declaration,
-readability-magic-numbers, -readability-magic-numbers,
@ -83,7 +89,6 @@ Checks: >-
-readability-redundant-string-init, -readability-redundant-string-init,
-readability-uppercase-literal-suffix, -readability-uppercase-literal-suffix,
-readability-use-anyofallof, -readability-use-anyofallof,
-warnings-as-errors
WarningsAsErrors: '*' WarningsAsErrors: '*'
AnalyzeTemporaryDtors: false AnalyzeTemporaryDtors: false
FormatStyle: google FormatStyle: google
@ -108,6 +113,10 @@ CheckOptions:
value: llvm value: llvm
- key: modernize-use-nullptr.NullMacros - key: modernize-use-nullptr.NullMacros
value: 'NULL' 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 - key: readability-identifier-naming.LocalVariableCase
value: 'lower_case' value: 'lower_case'
- key: readability-identifier-naming.ClassCase - key: readability-identifier-naming.ClassCase
@ -121,15 +130,19 @@ CheckOptions:
- key: readability-identifier-naming.StaticConstantCase - key: readability-identifier-naming.StaticConstantCase
value: 'UPPER_CASE' value: 'UPPER_CASE'
- key: readability-identifier-naming.StaticVariableCase - key: readability-identifier-naming.StaticVariableCase
value: 'UPPER_CASE' value: 'lower_case'
- key: readability-identifier-naming.GlobalConstantCase - key: readability-identifier-naming.GlobalConstantCase
value: 'UPPER_CASE' value: 'UPPER_CASE'
- key: readability-identifier-naming.ParameterCase - key: readability-identifier-naming.ParameterCase
value: 'lower_case' value: 'lower_case'
- key: readability-identifier-naming.PrivateMemberPrefix - key: readability-identifier-naming.PrivateMemberCase
value: 'NO_PRIVATE_MEMBERS_ALWAYS_USE_PROTECTED' value: 'lower_case'
- key: readability-identifier-naming.PrivateMethodPrefix - key: readability-identifier-naming.PrivateMemberSuffix
value: 'NO_PRIVATE_METHODS_ALWAYS_USE_PROTECTED' value: '_'
- key: readability-identifier-naming.PrivateMethodCase
value: 'lower_case'
- key: readability-identifier-naming.PrivateMethodSuffix
value: '_'
- key: readability-identifier-naming.ClassMemberCase - key: readability-identifier-naming.ClassMemberCase
value: 'lower_case' value: 'lower_case'
- key: readability-identifier-naming.ClassMemberCase - key: readability-identifier-naming.ClassMemberCase

View file

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

2
.gitattributes vendored Normal file
View file

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

59
.github/stale.yml vendored
View file

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

View file

@ -7,11 +7,15 @@ on:
paths: paths:
- 'docker/**' - 'docker/**'
- '.github/workflows/**' - '.github/workflows/**'
- 'requirements*.txt'
- 'platformio.ini'
pull_request: pull_request:
paths: paths:
- 'docker/**' - 'docker/**'
- '.github/workflows/**' - '.github/workflows/**'
- 'requirements*.txt'
- 'platformio.ini'
jobs: jobs:
check-docker: check-docker:
@ -27,6 +31,11 @@ jobs:
uses: actions/setup-python@v2 uses: actions/setup-python@v2
with: with:
python-version: '3.9' 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 - name: Set TAG
run: | run: |
echo "TAG=check" >> $GITHUB_ENV echo "TAG=check" >> $GITHUB_ENV

View file

@ -9,58 +9,7 @@ on:
pull_request: pull_request:
jobs: 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: 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 }} name: ${{ matrix.name }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
@ -74,48 +23,87 @@ jobs:
- id: test - id: test
file: tests/test1.yaml file: tests/test1.yaml
name: Test tests/test1.yaml name: Test tests/test1.yaml
pio_cache_key: test1
- id: test - id: test
file: tests/test2.yaml file: tests/test2.yaml
name: Test tests/test2.yaml name: Test tests/test2.yaml
pio_cache_key: test2
- id: test - id: test
file: tests/test3.yaml file: tests/test3.yaml
name: Test tests/test3.yaml name: Test tests/test3.yaml
pio_cache_key: test1
- id: test - id: test
file: tests/test4.yaml file: tests/test4.yaml
name: Test tests/test4.yaml name: Test tests/test4.yaml
pio_cache_key: test4
- id: test - id: test
file: tests/test5.yaml file: tests/test5.yaml
name: Test tests/test5.yaml name: Test tests/test5.yaml
pio_cache_key: test5
- id: pytest - id: pytest
name: Run 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: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v2 uses: actions/setup-python@v2
id: python
with: with:
python-version: '3.7' python-version: '3.7'
- name: Cache pip modules - name: Cache pip modules
uses: actions/cache@v1 uses: actions/cache@v2
with: with:
path: ~/.cache/pip path: ~/.cache/pip
key: esphome-pip-3.7-${{ hashFiles('setup.py') }} key: pip-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }}
restore-keys: | restore-keys: |
esphome-pip-3.7- pip-${{ steps.python.outputs.python-version }}-
# Use per test platformio cache because tests have different platform versions
- name: Cache ~/.platformio
uses: actions/cache@v1
with:
path: ~/.platformio
key: test-home-platformio-${{ matrix.file }}-${{ hashFiles('esphome/core/config.py') }}
restore-keys: |
test-home-platformio-${{ matrix.file }}-
if: ${{ matrix.id == 'test' }}
- name: Set up python environment - name: Set up python environment
run: script/setup run: |
pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
pip3 install -e .
# Use per check platformio cache because checks use different parts
- name: Cache platformio
uses: actions/cache@v2
with:
path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
if: matrix.id == 'test' || matrix.id == 'clang-tidy'
- name: Install clang tools
run: |
sudo apt-get install \
clang-format-11 \
clang-tidy-11
if: matrix.id == 'clang-tidy' || matrix.id == 'clang-format'
- name: Register problem matchers - name: Register problem matchers
run: | run: |
@ -124,20 +112,45 @@ jobs:
echo "::add-matcher::.github/workflows/matchers/python.json" echo "::add-matcher::.github/workflows/matchers/python.json"
echo "::add-matcher::.github/workflows/matchers/pytest.json" echo "::add-matcher::.github/workflows/matchers/pytest.json"
echo "::add-matcher::.github/workflows/matchers/gcc.json" echo "::add-matcher::.github/workflows/matchers/gcc.json"
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
- name: Lint Custom - name: Lint Custom
run: | run: |
script/ci-custom.py script/ci-custom.py
script/build_codeowners.py --check script/build_codeowners.py --check
if: ${{ matrix.id == 'ci-custom' }} if: matrix.id == 'ci-custom'
- name: Lint Python - name: Lint Python
run: script/lint-python run: script/lint-python
if: ${{ matrix.id == 'lint-python' }} if: matrix.id == 'lint-python'
- run: esphome compile ${{ matrix.file }} - run: esphome compile ${{ matrix.file }}
if: ${{ matrix.id == 'test' }} if: matrix.id == 'test'
env:
# Also cache libdeps, store them in a ~/.platformio subfolder
PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
- name: Run pytest - name: Run pytest
run: | run: |
pytest -vv --tb=native tests pytest -vv --tb=native tests
if: ${{ matrix.id == 'pytest' }} 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')

View file

@ -1,100 +0,0 @@
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

21
.github/workflows/lock.yml vendored Normal file
View file

@ -0,0 +1,21 @@
name: Lock
on:
schedule:
- cron: '30 0 * * *'
workflow_dispatch:
permissions:
issues: write
pull-requests: write
jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v2
with:
github-token: ${{ github.token }}
pr-lock-inactive-days: "1"
pr-lock-reason: ""
process-only: prs

View file

@ -57,7 +57,7 @@ jobs:
strategy: strategy:
matrix: matrix:
arch: [amd64, armv7, aarch64] arch: [amd64, armv7, aarch64]
build_type: ["ha-addon", "docker"] build_type: ["ha-addon", "docker", "lint"]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python - name: Set up Python
@ -65,13 +65,10 @@ jobs:
with: with:
python-version: '3.9' python-version: '3.9'
- name: Run build - name: Set up Docker Buildx
run: | uses: docker/setup-buildx-action@v1
docker/build.py \ - name: Set up QEMU
--tag "${{ needs.init.outputs.tag }}" \ uses: docker/setup-qemu-action@v1
--arch "${{ matrix.arch }}" \
--build-type "${{ matrix.build_type }}" \
build
- name: Log in to docker hub - name: Log in to docker hub
uses: docker/login-action@v1 uses: docker/login-action@v1
@ -85,13 +82,14 @@ jobs:
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Run push - name: Build and push
run: | run: |
docker/build.py \ docker/build.py \
--tag "${{ needs.init.outputs.tag }}" \ --tag "${{ needs.init.outputs.tag }}" \
--arch "${{ matrix.arch }}" \ --arch "${{ matrix.arch }}" \
--build-type "${{ matrix.build_type }}" \ --build-type "${{ matrix.build_type }}" \
push build \
--push
deploy-docker-manifest: deploy-docker-manifest:
if: github.repository == 'esphome/esphome' if: github.repository == 'esphome/esphome'
@ -99,7 +97,7 @@ jobs:
needs: [init, deploy-docker] needs: [init, deploy-docker]
strategy: strategy:
matrix: matrix:
build_type: ["ha-addon", "docker"] build_type: ["ha-addon", "docker", "lint"]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python - name: Set up Python

30
.github/workflows/stale.yml vendored Normal file
View file

@ -0,0 +1,30 @@
name: Stale
on:
schedule:
- cron: '30 0 * * *'
workflow_dispatch:
permissions:
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v4
with:
repo-token: ${{ github.token }}
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.

8
.gitignore vendored
View file

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

View file

@ -15,6 +15,7 @@ esphome/components/ac_dimmer/* @glmnet
esphome/components/adc/* @esphome/core esphome/components/adc/* @esphome/core
esphome/components/addressable_light/* @justfalter esphome/components/addressable_light/* @justfalter
esphome/components/airthings_ble/* @jeromelaban esphome/components/airthings_ble/* @jeromelaban
esphome/components/airthings_wave_mini/* @ncareau
esphome/components/airthings_wave_plus/* @jeromelaban esphome/components/airthings_wave_plus/* @jeromelaban
esphome/components/am43/* @buxtronix esphome/components/am43/* @buxtronix
esphome/components/am43/cover/* @buxtronix esphome/components/am43/cover/* @buxtronix
@ -39,14 +40,19 @@ esphome/components/coolix/* @glmnet
esphome/components/cover/* @esphome/core esphome/components/cover/* @esphome/core
esphome/components/cs5460a/* @balrog-kun esphome/components/cs5460a/* @balrog-kun
esphome/components/ct_clamp/* @jesserockz 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/debug/* @OttoWinter
esphome/components/dfplayer/* @glmnet esphome/components/dfplayer/* @glmnet
esphome/components/dht/* @OttoWinter esphome/components/dht/* @OttoWinter
esphome/components/ds1307/* @badbadc0ffee esphome/components/ds1307/* @badbadc0ffee
esphome/components/dsmr/* @glmnet @zuidwijk esphome/components/dsmr/* @glmnet @zuidwijk
esphome/components/esp32/* @esphome/core
esphome/components/esp32_ble/* @jesserockz esphome/components/esp32_ble/* @jesserockz
esphome/components/esp32_ble_server/* @jesserockz esphome/components/esp32_ble_server/* @jesserockz
esphome/components/esp32_improv/* @jesserockz esphome/components/esp32_improv/* @jesserockz
esphome/components/esp8266/* @esphome/core
esphome/components/exposure_notifications/* @OttoWinter esphome/components/exposure_notifications/* @OttoWinter
esphome/components/ezo/* @ssieb esphome/components/ezo/* @ssieb
esphome/components/fastled_base/* @OttoWinter esphome/components/fastled_base/* @OttoWinter
@ -54,9 +60,11 @@ esphome/components/fingerprint_grow/* @OnFreund @loongyh
esphome/components/globals/* @esphome/core esphome/components/globals/* @esphome/core
esphome/components/gpio/* @esphome/core esphome/components/gpio/* @esphome/core
esphome/components/gps/* @coogle esphome/components/gps/* @coogle
esphome/components/graph/* @synco
esphome/components/havells_solar/* @sourabhjaiswal esphome/components/havells_solar/* @sourabhjaiswal
esphome/components/hbridge/fan/* @WeekendWarrior esphome/components/hbridge/fan/* @WeekendWarrior
esphome/components/hbridge/light/* @DotNetDann esphome/components/hbridge/light/* @DotNetDann
esphome/components/heatpumpir/* @rob-deutsch
esphome/components/hitachi_ac424/* @sourabhjaiswal esphome/components/hitachi_ac424/* @sourabhjaiswal
esphome/components/homeassistant/* @OttoWinter esphome/components/homeassistant/* @OttoWinter
esphome/components/hrxl_maxsonar_wr/* @netmikey esphome/components/hrxl_maxsonar_wr/* @netmikey
@ -70,6 +78,7 @@ esphome/components/json/* @OttoWinter
esphome/components/ledc/* @OttoWinter esphome/components/ledc/* @OttoWinter
esphome/components/light/* @esphome/core esphome/components/light/* @esphome/core
esphome/components/logger/* @esphome/core esphome/components/logger/* @esphome/core
esphome/components/ltr390/* @sjtrny
esphome/components/max7219digit/* @rspaargaren esphome/components/max7219digit/* @rspaargaren
esphome/components/mcp23008/* @jesserockz esphome/components/mcp23008/* @jesserockz
esphome/components/mcp23017/* @jesserockz esphome/components/mcp23017/* @jesserockz
@ -80,8 +89,16 @@ esphome/components/mcp23x17_base/* @jesserockz
esphome/components/mcp23xxx_base/* @jesserockz esphome/components/mcp23xxx_base/* @jesserockz
esphome/components/mcp2515/* @danielschramm @mvturnho esphome/components/mcp2515/* @danielschramm @mvturnho
esphome/components/mcp9808/* @k7hpn esphome/components/mcp9808/* @k7hpn
esphome/components/mdns/* @esphome/core
esphome/components/midea/* @dudanov esphome/components/midea/* @dudanov
esphome/components/mitsubishi/* @RubyBailey 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/network/* @esphome/core
esphome/components/nextion/* @senexcrenshaw esphome/components/nextion/* @senexcrenshaw
esphome/components/nextion/binary_sensor/* @senexcrenshaw esphome/components/nextion/binary_sensor/* @senexcrenshaw
@ -100,6 +117,7 @@ esphome/components/pn532/* @OttoWinter @jesserockz
esphome/components/pn532_i2c/* @OttoWinter @jesserockz esphome/components/pn532_i2c/* @OttoWinter @jesserockz
esphome/components/pn532_spi/* @OttoWinter @jesserockz esphome/components/pn532_spi/* @OttoWinter @jesserockz
esphome/components/power_supply/* @esphome/core esphome/components/power_supply/* @esphome/core
esphome/components/preferences/* @esphome/core
esphome/components/pulse_meter/* @stevebaxter esphome/components/pulse_meter/* @stevebaxter
esphome/components/pvvx_mithermometer/* @pasiz esphome/components/pvvx_mithermometer/* @pasiz
esphome/components/rc522/* @glmnet esphome/components/rc522/* @glmnet
@ -109,6 +127,8 @@ esphome/components/restart/* @esphome/core
esphome/components/rf_bridge/* @jesserockz esphome/components/rf_bridge/* @jesserockz
esphome/components/rgbct/* @jesserockz esphome/components/rgbct/* @jesserockz
esphome/components/rtttl/* @glmnet esphome/components/rtttl/* @glmnet
esphome/components/safe_mode/* @paulmonigatti
esphome/components/scd4x/* @sjtrny
esphome/components/script/* @esphome/core esphome/components/script/* @esphome/core
esphome/components/sdm_meter/* @jesserockz @polyfaces esphome/components/sdm_meter/* @jesserockz @polyfaces
esphome/components/sdp3x/* @Azimath esphome/components/sdp3x/* @Azimath

View file

@ -1,10 +1,6 @@
# Contributing to ESPHome # Contributing to ESPHome
This python project is responsible for reading in YAML configuration files, For a detailed guide, please see https://esphome.io/guides/contributing.html#contributing-to-esphome
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: Things to note when contributing:

View file

@ -1,5 +1,60 @@
ARG BUILD_FROM=esphome/esphome-base:latest # Build these with the build.py script
FROM ${BUILD_FROM} # 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.0 AS base-hassio-amd64
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.0 AS base-hassio-arm64
FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.0 AS base-hassio-armv7
FROM debian:bullseye-20210902-slim AS base-docker-amd64
FROM debian:bullseye-20210902-slim AS base-docker-arm64
FROM debian:bullseye-20210902-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.0 \
# Change some platformio settings
&& platformio settings set enable_telemetry No \
&& platformio settings set check_libraries_interval 1000000 \
&& platformio settings set check_platformio_interval 1000000 \
&& platformio settings set check_platforms_interval 1000000 \
&& mkdir -p /piolibs
# ======================= docker-type image =======================
FROM base AS docker
# First install requirements to leverage caching when requirements don't change # First install requirements to leverage caching when requirements don't change
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
@ -7,9 +62,9 @@ RUN \
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
&& /platformio_install_deps.py /platformio.ini && /platformio_install_deps.py /platformio.ini
# Then copy esphome and install # Copy esphome and install
COPY . . COPY . /esphome
RUN pip3 install --no-cache-dir -e . RUN pip3 install --no-cache-dir -e /esphome
# Settings for dashboard # Settings for dashboard
ENV USERNAME="" PASSWORD="" ENV USERNAME="" PASSWORD=""
@ -17,14 +72,85 @@ ENV USERNAME="" PASSWORD=""
# Expose the dashboard to Docker # Expose the dashboard to Docker
EXPOSE 6052 EXPOSE 6052
# Run healthcheck (heartbeat) COPY docker/docker_entrypoint.sh /entrypoint.sh
HEALTHCHECK --interval=30s --timeout=30s \
CMD curl --fail http://localhost:6052 || exit 1
# The directory the user should mount their configuration files to # The directory the user should mount their configuration files to
VOLUME /config
WORKDIR /config WORKDIR /config
# Set entrypoint to esphome so that the user doesn't have to type 'esphome' # Set entrypoint to esphome (via a script) so that the user doesn't have to type 'esphome'
# in every docker command twice # in every docker command twice
ENTRYPOINT ["esphome"] ENTRYPOINT ["/entrypoint.sh"]
# When no arguments given, start the dashboard in the workdir # When no arguments given, start the dashboard in the workdir
CMD ["dashboard", "/config"] 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 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
VOLUME ["/esphome"]
WORKDIR /esphome

View file

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

View file

@ -1,25 +0,0 @@
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}

View file

@ -1,10 +0,0 @@
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 from dataclasses import dataclass
import subprocess import subprocess
import argparse import argparse
import platform from platform import machine
import shlex import shlex
import re import re
import sys import sys
@ -24,9 +24,6 @@ TYPE_LINT = 'lint'
TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT] TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT]
BASE_VERSION = "4.2.0"
parser = argparse.ArgumentParser() 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("--tag", type=str, required=True, help="The main docker tag to push to. If a version number also adds latest and/or beta tag")
parser.add_argument("--arch", choices=ARCHS, required=False, help="The architecture to build for") parser.add_argument("--arch", choices=ARCHS, required=False, help="The architecture to build for")
@ -34,27 +31,17 @@ 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") 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) subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True)
build_parser = subparsers.add_parser("build", help="Build the image") build_parser = subparsers.add_parser("build", help="Build the image")
push_parser = subparsers.add_parser("push", help="Tag the already built image and push it to docker hub") build_parser.add_argument("--push", help="Also push the images", action="store_true")
manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images") 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) @dataclass(frozen=True)
class DockerParams: class DockerParams:
build_from: str
build_to: str build_to: str
manifest_to: str manifest_to: str
dockerfile: str baseimgtype: str
platform: str
target: str
@classmethod @classmethod
def for_type_arch(cls, build_type, arch): def for_type_arch(cls, build_type, arch):
@ -63,18 +50,28 @@ class DockerParams:
TYPE_HA_ADDON: "esphome/esphome-hassio", TYPE_HA_ADDON: "esphome/esphome-hassio",
TYPE_LINT: "esphome/esphome-lint" TYPE_LINT: "esphome/esphome-lint"
}[build_type] }[build_type]
build_from = f"ghcr.io/{prefix}-base-{arch}:{BASE_VERSION}"
build_to = f"{prefix}-{arch}" build_to = f"{prefix}-{arch}"
dockerfile = { baseimgtype = {
TYPE_DOCKER: "docker/Dockerfile", TYPE_DOCKER: "docker",
TYPE_HA_ADDON: "docker/Dockerfile.hassio", TYPE_HA_ADDON: "hassio",
TYPE_LINT: "docker/Dockerfile.lint", TYPE_LINT: "docker",
}[build_type]
platform = {
ARCH_AMD64: "linux/amd64",
ARCH_ARMV7: "linux/arm/v7",
ARCH_AARCH64: "linux/arm64",
}[arch]
target = {
TYPE_DOCKER: "docker",
TYPE_HA_ADDON: "hassio",
TYPE_LINT: "lint",
}[build_type] }[build_type]
return cls( return cls(
build_from=build_from,
build_to=build_to, build_to=build_to,
manifest_to=prefix, manifest_to=prefix,
dockerfile=dockerfile baseimgtype=baseimgtype,
platform=platform,
target=target,
) )
@ -112,46 +109,31 @@ def main():
# 1. pull cache image # 1. pull cache image
params = DockerParams.for_type_arch(args.build_type, args.arch) params = DockerParams.for_type_arch(args.build_type, args.arch)
cache_tag = { cache_tag = {
CHANNEL_DEV: "dev", CHANNEL_DEV: "cache-dev",
CHANNEL_BETA: "beta", CHANNEL_BETA: "cache-beta",
CHANNEL_RELEASE: "latest", CHANNEL_RELEASE: "cache-latest",
}[channel] }[channel]
cache_img = f"ghcr.io/{params.build_to}:{cache_tag}" cache_img = f"ghcr.io/{params.build_to}:{cache_tag}"
run_command("docker", "pull", cache_img, ignore_error=True)
# 2. register QEMU binfmt (if not host arch)
is_native = UNAME_TO_ARCH.get(platform.machine()) == args.arch
if not is_native:
run_command(
"docker", "run", "--rm", "--privileged", "multiarch/qemu-user-static:5.2.0-2",
"--reset", "-p", "yes"
)
# 3. build
run_command(
"docker", "build",
"--build-arg", f"BUILD_FROM={params.build_from}",
"--build-arg", f"BUILD_VERSION={args.tag}",
"--tag", f"{params.build_to}:{args.tag}",
"--cache-from", cache_img,
"--file", params.dockerfile,
"."
)
elif args.command == "push":
params = DockerParams.for_type_arch(args.build_type, args.arch)
imgs = [f"{params.build_to}:{tag}" for tag in tags_to_push] imgs = [f"{params.build_to}:{tag}" for tag in tags_to_push]
imgs += [f"ghcr.io/{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 # 3. build
for img in imgs[1:]: cmd = [
run_command( "docker", "buildx", "build",
"docker", "tag", src, img "--build-arg", f"BASEIMGTYPE={params.baseimgtype}",
) "--build-arg", f"BUILD_VERSION={args.tag}",
# 2. push images "--cache-from", f"type=registry,ref={cache_img}",
"--file", "docker/Dockerfile",
"--platform", params.platform,
"--target", params.target,
]
for img in imgs: for img in imgs:
run_command( cmd += ["--tag", img]
"docker", "push", img if args.push:
) cmd += ["--push", "--cache-to", f"type=registry,ref={cache_img},mode=max"]
run_command(*cmd, ".")
elif args.command == "manifest": elif args.command == "manifest":
manifest = DockerParams.for_type_arch(args.build_type, ARCH_AMD64).manifest_to manifest = DockerParams.for_type_arch(args.build_type, ARCH_AMD64).manifest_to

24
docker/docker_entrypoint.sh Executable file
View file

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

View file

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

View file

@ -22,5 +22,14 @@ if bashio::config.has_value 'relative_url'; then
export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url') export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url')
fi 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..." bashio::log.info "Starting ESPHome dashboard..."
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --hassio exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --hassio

View file

@ -3,18 +3,11 @@
# all platformio libraries in the global storage # all platformio libraries in the global storage
import configparser import configparser
import re
import subprocess import subprocess
import sys import sys
config = configparser.ConfigParser() config = configparser.ConfigParser(inline_comment_prefixes=(';', ))
config.read(sys.argv[1]) config.read(sys.argv[1])
libs = [] libs = [x for x in config['common']['lib_deps'].splitlines() if len(x) != 0]
for line in config['common']['lib_deps'].splitlines():
# Format: '1655@1.0.2 ; TinyGPSPlus (has name conflict)' (includes comment)
m = re.search(r'([a-zA-Z0-9-_/]+@[0-9\.]+)', line)
if m is None:
continue
libs.append(m.group(1))
subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs]) subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs])

View file

@ -72,7 +72,7 @@ def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api
if default == "OTA": if default == "OTA":
return CORE.address return CORE.address
if show_mqtt and "mqtt" in CORE.config: if show_mqtt and "mqtt" in CORE.config:
options.append(("MQTT ({})".format(CORE.config["mqtt"][CONF_BROKER]), "MQTT")) options.append((f"MQTT ({CORE.config['mqtt'][CONF_BROKER]})", "MQTT"))
if default == "OTA": if default == "OTA":
return "MQTT" return "MQTT"
if default is not None: if default is not None:
@ -184,12 +184,30 @@ def compile_program(args, config):
def upload_using_esptool(config, port): def upload_using_esptool(config, port):
path = CORE.firmware_bin from esphome import platformio_api
first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get( first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
"upload_speed", 460800 "upload_speed", 460800
) )
def run_esptool(baud_rate): 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 = [ cmd = [
"esptool.py", "esptool.py",
"--before", "--before",
@ -198,14 +216,15 @@ def upload_using_esptool(config, port):
"hard_reset", "hard_reset",
"--baud", "--baud",
str(baud_rate), str(baud_rate),
"--chip",
"esp8266",
"--port", "--port",
port, port,
"--chip",
mcu,
"write_flash", "write_flash",
"0x0", "-z",
path,
] ]
for img in flash_images:
cmd += [img.offset, img.path]
if os.environ.get("ESPHOME_USE_SUBPROCESS") is None: if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
import esptool import esptool
@ -229,11 +248,7 @@ def upload_using_esptool(config, port):
def upload_program(config, args, host): def upload_program(config, args, host):
# if upload is to a serial port use platformio, otherwise assume ota # if upload is to a serial port use platformio, otherwise assume ota
if get_port_type(host) == "SERIAL": if get_port_type(host) == "SERIAL":
from esphome import platformio_api
if CORE.is_esp8266:
return upload_using_esptool(config, host) return upload_using_esptool(config, host)
return platformio_api.run_upload(config, CORE.verbose, host)
from esphome import espota2 from esphome import espota2
@ -245,7 +260,7 @@ def upload_program(config, args, host):
ota_conf = config[CONF_OTA] ota_conf = config[CONF_OTA]
remote_port = ota_conf[CONF_PORT] remote_port = ota_conf[CONF_PORT]
password = ota_conf[CONF_PASSWORD] password = ota_conf.get(CONF_PASSWORD, "")
return espota2.run_ota(host, remote_port, password, CORE.firmware_bin) return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
@ -415,30 +430,30 @@ def command_update_all(args):
click.echo(f"{half_line}{middle_text}{half_line}") click.echo(f"{half_line}{middle_text}{half_line}")
for f in files: for f in files:
print("Updating {}".format(color(Fore.CYAN, f))) print(f"Updating {color(Fore.CYAN, f)}")
print("-" * twidth) print("-" * twidth)
print() print()
rc = run_external_process( rc = run_external_process(
"esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA" "esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
) )
if rc == 0: if rc == 0:
print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f)) print_bar(f"[{color(Fore.BOLD_GREEN, 'SUCCESS')}] {f}")
success[f] = True success[f] = True
else: else:
print_bar("[{}] {}".format(color(Fore.BOLD_RED, "ERROR"), f)) print_bar(f"[{color(Fore.BOLD_RED, 'ERROR')}] {f}")
success[f] = False success[f] = False
print() print()
print() print()
print() print()
print_bar("[{}]".format(color(Fore.BOLD_WHITE, "SUMMARY"))) print_bar(f"[{color(Fore.BOLD_WHITE, 'SUMMARY')}]")
failed = 0 failed = 0
for f in files: for f in files:
if success[f]: if success[f]:
print(" - {}: {}".format(f, color(Fore.GREEN, "SUCCESS"))) print(f" - {f}: {color(Fore.GREEN, 'SUCCESS')}")
else: else:
print(" - {}: {}".format(f, color(Fore.BOLD_RED, "FAILED"))) print(f" - {f}: {color(Fore.BOLD_RED, 'FAILED')}")
failed += 1 failed += 1
return failed return failed

View file

@ -6,6 +6,7 @@ from esphome.const import (
CONF_ELSE, CONF_ELSE,
CONF_ID, CONF_ID,
CONF_THEN, CONF_THEN,
CONF_TIMEOUT,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_TYPE_ID, CONF_TYPE_ID,
CONF_TIME, CONF_TIME,
@ -244,6 +245,9 @@ def validate_wait_until(value):
schema = cv.Schema( schema = cv.Schema(
{ {
cv.Required(CONF_CONDITION): validate_potentially_and_condition, 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: if isinstance(value, dict) and CONF_CONDITION in value:
@ -255,6 +259,9 @@ def validate_wait_until(value):
async def wait_until_action_to_code(config, action_id, template_arg, args): async def wait_until_action_to_code(config, action_id, template_arg, args):
conditions = await build_condition(config[CONF_CONDITION], template_arg, args) conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
var = cg.new_Pvariable(action_id, template_arg, conditions) 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, {}) await cg.register_component(var, {})
return var return var

View file

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

View file

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

View file

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

View file

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

View file

@ -19,7 +19,8 @@ DIM_METHODS = {
CONF_GATE_PIN = "gate_pin" CONF_GATE_PIN = "gate_pin"
CONF_ZERO_CROSS_PIN = "zero_cross_pin" CONF_ZERO_CROSS_PIN = "zero_cross_pin"
CONF_INIT_WITH_HALF_CYCLE = "init_with_half_cycle" CONF_INIT_WITH_HALF_CYCLE = "init_with_half_cycle"
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( CONFIG_SCHEMA = cv.All(
output.FLOAT_OUTPUT_SCHEMA.extend(
{ {
cv.Required(CONF_ID): cv.declare_id(AcDimmer), cv.Required(CONF_ID): cv.declare_id(AcDimmer),
cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema, cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema,
@ -29,7 +30,9 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
DIM_METHODS, upper=True, space="_" DIM_METHODS, upper=True, space="_"
), ),
} }
).extend(cv.COMPONENT_SCHEMA) ).extend(cv.COMPONENT_SCHEMA),
cv.only_with_arduino,
)
async def to_code(config): async def to_code(config):

View file

@ -1,8 +1,13 @@
#include "adc_sensor.h" #include "adc_sensor.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#ifdef USE_ESP8266
#ifdef USE_ADC_SENSOR_VCC #ifdef USE_ADC_SENSOR_VCC
#include <Esp.h>
ADC_MODE(ADC_VCC) ADC_MODE(ADC_VCC)
#else
#include <Arduino.h>
#endif
#endif #endif
namespace esphome { namespace esphome {
@ -10,7 +15,7 @@ namespace adc {
static const char *const TAG = "adc"; static const char *const TAG = "adc";
#ifdef ARDUINO_ARCH_ESP32 #ifdef USE_ESP32
void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; } void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; }
inline adc1_channel_t gpio_to_adc1(uint8_t pin) { inline adc1_channel_t gpio_to_adc1(uint8_t pin) {
@ -57,28 +62,28 @@ inline adc1_channel_t gpio_to_adc1(uint8_t pin) {
void ADCSensor::setup() { void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
#ifndef USE_ADC_SENSOR_VCC #ifndef USE_ADC_SENSOR_VCC
GPIOPin(this->pin_, INPUT).setup(); pin_->setup();
#endif #endif
#ifdef ARDUINO_ARCH_ESP32 #ifdef USE_ESP32
adc1_config_channel_atten(gpio_to_adc1(pin_), attenuation_); adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), attenuation_);
adc1_config_width(ADC_WIDTH_BIT_12); adc1_config_width(ADC_WIDTH_BIT_12);
#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2 #if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2
adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_)); adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_->get_pin()));
#endif #endif
#endif #endif
} }
void ADCSensor::dump_config() { void ADCSensor::dump_config() {
LOG_SENSOR("", "ADC Sensor", this); LOG_SENSOR("", "ADC Sensor", this);
#ifdef ARDUINO_ARCH_ESP8266 #ifdef USE_ESP8266
#ifdef USE_ADC_SENSOR_VCC #ifdef USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Pin: VCC"); ESP_LOGCONFIG(TAG, " Pin: VCC");
#else #else
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_); LOG_PIN(" Pin: ", pin_);
#endif #endif
#endif #endif
#ifdef ARDUINO_ARCH_ESP32 #ifdef USE_ESP32
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_); LOG_PIN(" Pin: ", pin_);
switch (this->attenuation_) { switch (this->attenuation_) {
case ADC_ATTEN_DB_0: case ADC_ATTEN_DB_0:
ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
@ -105,8 +110,8 @@ void ADCSensor::update() {
this->publish_state(value_v); this->publish_state(value_v);
} }
float ADCSensor::sample() { float ADCSensor::sample() {
#ifdef ARDUINO_ARCH_ESP32 #ifdef USE_ESP32
int raw = adc1_get_raw(gpio_to_adc1(pin_)); int raw = adc1_get_raw(gpio_to_adc1(pin_->get_pin()));
float value_v = raw / 4095.0f; float value_v = raw / 4095.0f;
#if CONFIG_IDF_TARGET_ESP32 #if CONFIG_IDF_TARGET_ESP32
switch (this->attenuation_) { switch (this->attenuation_) {
@ -146,15 +151,15 @@ float ADCSensor::sample() {
return value_v; return value_v;
#endif #endif
#ifdef ARDUINO_ARCH_ESP8266 #ifdef USE_ESP8266
#ifdef USE_ADC_SENSOR_VCC #ifdef USE_ADC_SENSOR_VCC
return ESP.getVcc() / 1024.0f; return ESP.getVcc() / 1024.0f; // NOLINT(readability-static-accessed-through-instance)
#else #else
return analogRead(this->pin_) / 1024.0f; // NOLINT return analogRead(this->pin_->get_pin()) / 1024.0f; // NOLINT
#endif #endif
#endif #endif
} }
#ifdef ARDUINO_ARCH_ESP8266 #ifdef USE_ESP8266
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; } std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
#endif #endif

View file

@ -1,12 +1,12 @@
#pragma once #pragma once
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/esphal.h" #include "esphome/core/hal.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
#include "esphome/components/voltage_sampler/voltage_sampler.h" #include "esphome/components/voltage_sampler/voltage_sampler.h"
#ifdef ARDUINO_ARCH_ESP32 #ifdef USE_ESP32
#include "driver/adc.h" #include "driver/adc.h"
#endif #endif
@ -15,7 +15,7 @@ namespace adc {
class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler { class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
public: public:
#ifdef ARDUINO_ARCH_ESP32 #ifdef USE_ESP32
/// Set the attenuation for this pin. Only available on the ESP32. /// Set the attenuation for this pin. Only available on the ESP32.
void set_attenuation(adc_atten_t attenuation); void set_attenuation(adc_atten_t attenuation);
#endif #endif
@ -27,17 +27,17 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
void dump_config() override; void dump_config() override;
/// `HARDWARE_LATE` setup priority. /// `HARDWARE_LATE` setup priority.
float get_setup_priority() const override; float get_setup_priority() const override;
void set_pin(uint8_t pin) { this->pin_ = pin; } void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
float sample() override; float sample() override;
#ifdef ARDUINO_ARCH_ESP8266 #ifdef USE_ESP8266
std::string unique_id() override; std::string unique_id() override;
#endif #endif
protected: protected:
uint8_t pin_; InternalGPIOPin *pin_;
#ifdef ARDUINO_ARCH_ESP32 #ifdef USE_ESP32
adc_atten_t attenuation_{ADC_ATTEN_DB_0}; adc_atten_t attenuation_{ADC_ATTEN_DB_0};
#endif #endif
}; };

View file

@ -5,11 +5,13 @@ from esphome.components import sensor, voltage_sampler
from esphome.const import ( from esphome.const import (
CONF_ATTENUATION, CONF_ATTENUATION,
CONF_ID, CONF_ID,
CONF_INPUT,
CONF_PIN, CONF_PIN,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_VOLT, UNIT_VOLT,
) )
from esphome.core import CORE
AUTO_LOAD = ["voltage_sampler"] AUTO_LOAD = ["voltage_sampler"]
@ -23,10 +25,34 @@ ATTENUATION_MODES = {
def validate_adc_pin(value): def validate_adc_pin(value):
vcc = str(value).upper() if str(value).upper() == "VCC":
if vcc == "VCC": return cv.only_on_esp8266("VCC")
return cv.only_on_esp8266(vcc)
return pins.analog_pin(value) if CORE.is_esp32:
from esphome.components.esp32 import is_esp32c3
value = pins.internal_gpio_input_pin_number(value)
if is_esp32c3():
if not (0 <= value <= 4): # ADC1
raise cv.Invalid("ESP32-C3: Only pins 0 though 4 support ADC.")
if not (32 <= value <= 39): # ADC1
raise cv.Invalid("ESP32: Only pins 32 though 39 support ADC.")
elif 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)
else:
raise NotImplementedError
return pins.internal_gpio_input_pin_schema(value)
adc_ns = cg.esphome_ns.namespace("adc") adc_ns = cg.esphome_ns.namespace("adc")
@ -62,7 +88,8 @@ async def to_code(config):
if config[CONF_PIN] == "VCC": if config[CONF_PIN] == "VCC":
cg.add_define("USE_ADC_SENSOR_VCC") cg.add_define("USE_ADC_SENSOR_VCC")
else: else:
cg.add(var.set_pin(config[CONF_PIN])) pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(pin))
if CONF_ATTENUATION in config: if CONF_ATTENUATION in config:
cg.add(var.set_attenuation(config[CONF_ATTENUATION])) cg.add(var.set_attenuation(config[CONF_ATTENUATION]))

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,12 +1,12 @@
#include "airthings_listener.h" #include "airthings_listener.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#ifdef ARDUINO_ARCH_ESP32 #ifdef USE_ESP32
namespace esphome { namespace esphome {
namespace airthings_ble { namespace airthings_ble {
static const char *TAG = "airthings_ble"; static const char *const TAG = "airthings_ble";
bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
for (auto &it : device.get_manufacturer_datas()) { for (auto &it : device.get_manufacturer_datas()) {

View file

@ -1,10 +1,9 @@
#pragma once #pragma once
#ifdef ARDUINO_ARCH_ESP32 #ifdef USE_ESP32
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include <BLEDevice.h>
namespace esphome { namespace esphome {
namespace airthings_ble { namespace airthings_ble {

View file

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

View file

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

View file

@ -0,0 +1,65 @@
#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

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

View file

@ -1,10 +1,12 @@
#include "airthings_wave_plus.h" #include "airthings_wave_plus.h"
#ifdef ARDUINO_ARCH_ESP32 #ifdef USE_ESP32
namespace esphome { namespace esphome {
namespace airthings_wave_plus { 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, void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) { esp_ble_gattc_cb_param_t *param) {
switch (event) { switch (event) {
@ -21,15 +23,15 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
} }
case ESP_GATTC_SEARCH_CMPL_EVT: { case ESP_GATTC_SEARCH_CMPL_EVT: {
this->handle = 0; this->handle_ = 0;
auto chr = this->parent()->get_characteristic(service_uuid, sensors_data_characteristic_uuid); auto chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_);
if (chr == nullptr) { if (chr == nullptr) {
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid.to_string().c_str(), 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()); sensors_data_characteristic_uuid_.to_string().c_str());
break; break;
} }
this->handle = chr->handle; this->handle_ = chr->handle;
this->node_state = espbt::ClientState::Established; this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED;
request_read_values_(); request_read_values_();
break; break;
@ -42,7 +44,7 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
break; break;
} }
if (param->read.handle == this->handle) { if (param->read.handle == this->handle_) {
read_sensors_(param->read.value, param->read.value_len); read_sensors_(param->read.value, param->read.value_len);
} }
break; break;
@ -88,16 +90,14 @@ void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) {
} }
} }
bool AirthingsWavePlus::is_valid_radon_value_(short radon) { return 0 <= radon && radon <= 16383; } bool AirthingsWavePlus::is_valid_radon_value_(uint16_t radon) { return 0 <= radon && radon <= 16383; }
bool AirthingsWavePlus::is_valid_voc_value_(short voc) { return 0 <= voc && voc <= 16383; } bool AirthingsWavePlus::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; }
bool AirthingsWavePlus::is_valid_co2_value_(short co2) { return 0 <= co2 && co2 <= 16383; } bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return 0 <= co2 && co2 <= 16383; }
void AirthingsWavePlus::loop() {}
void AirthingsWavePlus::update() { void AirthingsWavePlus::update() {
if (this->node_state != espbt::ClientState::Established) { if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) {
if (!parent()->enabled) { if (!parent()->enabled) {
ESP_LOGW(TAG, "Reconnecting to device"); ESP_LOGW(TAG, "Reconnecting to device");
parent()->set_enabled(true); parent()->set_enabled(true);
@ -110,7 +110,7 @@ void AirthingsWavePlus::update() {
void AirthingsWavePlus::request_read_values_() { void AirthingsWavePlus::request_read_values_() {
auto status = auto status =
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE); esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE);
if (status) { if (status) {
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
} }
@ -126,17 +126,12 @@ void AirthingsWavePlus::dump_config() {
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_); LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
} }
AirthingsWavePlus::AirthingsWavePlus() : PollingComponent(10000) { AirthingsWavePlus::AirthingsWavePlus()
auto service_bt = *BLEUUID::fromString(std::string("b42e1c08-ade7-11e4-89d3-123b93f75cba")).getNative(); : PollingComponent(10000),
auto characteristic_bt = *BLEUUID::fromString(std::string("b42e2a68-ade7-11e4-89d3-123b93f75cba")).getNative(); service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)),
sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {}
service_uuid = espbt::ESPBTUUID::from_uuid(service_bt);
sensors_data_characteristic_uuid = espbt::ESPBTUUID::from_uuid(characteristic_bt);
}
void AirthingsWavePlus::setup() {}
} // namespace airthings_wave_plus } // namespace airthings_wave_plus
} // namespace esphome } // namespace esphome
#endif // ARDUINO_ARCH_ESP32 #endif // USE_ESP32

View file

@ -1,32 +1,28 @@
#pragma once #pragma once
#include "esphome/core/component.h" #ifdef USE_ESP32
#include <esp_gattc_api.h>
#include <algorithm>
#include <iterator>
#include "esphome/components/ble_client/ble_client.h" #include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include <algorithm>
#include <iterator>
#ifdef ARDUINO_ARCH_ESP32
#include <esp_gattc_api.h>
#include <BLEDevice.h>
using namespace esphome::ble_client;
namespace esphome { namespace esphome {
namespace airthings_wave_plus { namespace airthings_wave_plus {
static const char *TAG = "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 BLEClientNode { class AirthingsWavePlus : public PollingComponent, public ble_client::BLEClientNode {
public: public:
AirthingsWavePlus(); AirthingsWavePlus();
void setup() override;
void dump_config() override; void dump_config() override;
void update() override; void update() override;
void loop() override;
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override; esp_ble_gattc_cb_param_t *param) override;
@ -40,9 +36,9 @@ class AirthingsWavePlus : public PollingComponent, public BLEClientNode {
void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; } void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
protected: protected:
bool is_valid_radon_value_(short radon); bool is_valid_radon_value_(uint16_t radon);
bool is_valid_voc_value_(short voc); bool is_valid_voc_value_(uint16_t voc);
bool is_valid_co2_value_(short co2); bool is_valid_co2_value_(uint16_t co2);
void read_sensors_(uint8_t *value, uint16_t value_len); void read_sensors_(uint8_t *value, uint16_t value_len);
void request_read_values_(); void request_read_values_();
@ -55,9 +51,9 @@ class AirthingsWavePlus : public PollingComponent, public BLEClientNode {
sensor::Sensor *co2_sensor_{nullptr}; sensor::Sensor *co2_sensor_{nullptr};
sensor::Sensor *tvoc_sensor_{nullptr}; sensor::Sensor *tvoc_sensor_{nullptr};
uint16_t handle; uint16_t handle_;
espbt::ESPBTUUID service_uuid; esp32_ble_tracker::ESPBTUUID service_uuid_;
espbt::ESPBTUUID sensors_data_characteristic_uuid; esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_;
struct WavePlusReadings { struct WavePlusReadings {
uint8_t version; uint8_t version;
@ -76,4 +72,4 @@ class AirthingsWavePlus : public PollingComponent, public BLEClientNode {
} // namespace airthings_wave_plus } // namespace airthings_wave_plus
} // namespace esphome } // namespace esphome
#endif // ARDUINO_ARCH_ESP32 #endif // USE_ESP32

View file

@ -34,7 +34,7 @@ AirthingsWavePlus = airthings_wave_plus_ns.class_(
) )
CONFIG_SCHEMA = ( CONFIG_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(AirthingsWavePlus), cv.GenerateID(): cv.declare_id(AirthingsWavePlus),
@ -82,8 +82,8 @@ CONFIG_SCHEMA = (
), ),
} }
) )
.extend(cv.polling_component_schema("5mins")) .extend(cv.polling_component_schema("5min"))
.extend(ble_client.BLE_CLIENT_SCHEMA) .extend(ble_client.BLE_CLIENT_SCHEMA),
) )

View file

@ -5,6 +5,7 @@
#include "am2320.h" #include "am2320.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome { namespace esphome {
namespace am2320 { namespace am2320 {
@ -77,7 +78,7 @@ bool AM2320Component::read_bytes_(uint8_t a_register, uint8_t *data, uint8_t len
if (conversion > 0) if (conversion > 0)
delay(conversion); delay(conversion);
return this->parent_->raw_receive(this->address_, data, len); return this->read(data, len) == i2c::ERROR_OK;
} }
bool AM2320Component::read_data_(uint8_t *data) { bool AM2320Component::read_data_(uint8_t *data) {

View file

@ -1,12 +1,13 @@
#include "am43.h" #include "am43.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/hal.h"
#ifdef ARDUINO_ARCH_ESP32 #ifdef USE_ESP32
namespace esphome { namespace esphome {
namespace am43 { namespace am43 {
static const char *TAG = "am43"; static const char *const TAG = "am43";
void Am43::dump_config() { void Am43::dump_config() {
ESP_LOGCONFIG(TAG, "AM43"); ESP_LOGCONFIG(TAG, "AM43");
@ -15,8 +16,8 @@ void Am43::dump_config() {
} }
void Am43::setup() { void Am43::setup() {
this->encoder_ = new Am43Encoder(); this->encoder_ = make_unique<Am43Encoder>();
this->decoder_ = new Am43Decoder(); this->decoder_ = make_unique<Am43Decoder>();
this->logged_in_ = false; this->logged_in_ = false;
this->last_battery_update_ = 0; this->last_battery_update_ = 0;
this->current_sensor_ = 0; this->current_sensor_ = 0;
@ -30,7 +31,7 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i
} }
case ESP_GATTC_DISCONNECT_EVT: { case ESP_GATTC_DISCONNECT_EVT: {
this->logged_in_ = false; this->logged_in_ = false;
this->node_state = espbt::ClientState::Idle; this->node_state = espbt::ClientState::IDLE;
if (this->battery_ != nullptr) if (this->battery_ != nullptr)
this->battery_->publish_state(NAN); this->battery_->publish_state(NAN);
if (this->illuminance_ != nullptr) if (this->illuminance_ != nullptr)
@ -53,7 +54,7 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i
break; break;
} }
case ESP_GATTC_REG_FOR_NOTIFY_EVT: { case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
this->node_state = espbt::ClientState::Established; this->node_state = espbt::ClientState::ESTABLISHED;
this->update(); this->update();
break; break;
} }
@ -92,7 +93,7 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i
} }
void Am43::update() { void Am43::update() {
if (this->node_state != espbt::ClientState::Established) { if (this->node_state != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str()); ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str());
return; return;
} }

View file

@ -6,7 +6,7 @@
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
#include "esphome/components/am43/am43_base.h" #include "esphome/components/am43/am43_base.h"
#ifdef ARDUINO_ARCH_ESP32 #ifdef USE_ESP32
#include <esp_gattc_api.h> #include <esp_gattc_api.h>
@ -28,8 +28,8 @@ class Am43 : public esphome::ble_client::BLEClientNode, public PollingComponent
protected: protected:
uint16_t char_handle_; uint16_t char_handle_;
Am43Encoder *encoder_; std::unique_ptr<Am43Encoder> encoder_;
Am43Decoder *decoder_; std::unique_ptr<Am43Decoder> decoder_;
bool logged_in_; bool logged_in_;
sensor::Sensor *battery_{nullptr}; sensor::Sensor *battery_{nullptr};
sensor::Sensor *illuminance_{nullptr}; sensor::Sensor *illuminance_{nullptr};

View file

@ -1,4 +1,6 @@
#include "am43_base.h" #include "am43_base.h"
#include <cstring>
#include <cstdio>
namespace esphome { namespace esphome {
namespace am43 { namespace am43 {

View file

@ -1,12 +1,12 @@
#include "am43_cover.h" #include "am43_cover.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#ifdef ARDUINO_ARCH_ESP32 #ifdef USE_ESP32
namespace esphome { namespace esphome {
namespace am43 { namespace am43 {
static const char *TAG = "am43_cover"; static const char *const TAG = "am43_cover";
using namespace esphome::cover; using namespace esphome::cover;
@ -18,13 +18,13 @@ void Am43Component::dump_config() {
void Am43Component::setup() { void Am43Component::setup() {
this->position = COVER_OPEN; this->position = COVER_OPEN;
this->encoder_ = new Am43Encoder(); this->encoder_ = make_unique<Am43Encoder>();
this->decoder_ = new Am43Decoder(); this->decoder_ = make_unique<Am43Decoder>();
this->logged_in_ = false; this->logged_in_ = false;
} }
void Am43Component::loop() { void Am43Component::loop() {
if (this->node_state == espbt::ClientState::Established && !this->logged_in_) { if (this->node_state == espbt::ClientState::ESTABLISHED && !this->logged_in_) {
auto packet = this->encoder_->get_send_pin_request(this->pin_); auto packet = this->encoder_->get_send_pin_request(this->pin_);
auto status = auto status =
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
@ -46,7 +46,7 @@ CoverTraits Am43Component::get_traits() {
} }
void Am43Component::control(const CoverCall &call) { void Am43Component::control(const CoverCall &call) {
if (this->node_state != espbt::ClientState::Established) { if (this->node_state != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "[%s] Cannot send cover control, not connected", this->get_name().c_str()); ESP_LOGW(TAG, "[%s] Cannot send cover control, not connected", this->get_name().c_str());
return; return;
} }
@ -98,7 +98,7 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
break; break;
} }
case ESP_GATTC_REG_FOR_NOTIFY_EVT: { case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
this->node_state = espbt::ClientState::Established; this->node_state = espbt::ClientState::ESTABLISHED;
break; break;
} }
case ESP_GATTC_NOTIFY_EVT: { case ESP_GATTC_NOTIFY_EVT: {

View file

@ -6,7 +6,7 @@
#include "esphome/components/cover/cover.h" #include "esphome/components/cover/cover.h"
#include "esphome/components/am43/am43_base.h" #include "esphome/components/am43/am43_base.h"
#ifdef ARDUINO_ARCH_ESP32 #ifdef USE_ESP32
#include <esp_gattc_api.h> #include <esp_gattc_api.h>
@ -32,8 +32,8 @@ class Am43Component : public cover::Cover, public esphome::ble_client::BLEClient
uint16_t char_handle_; uint16_t char_handle_;
uint16_t pin_; uint16_t pin_;
bool invert_position_; bool invert_position_;
Am43Encoder *encoder_; std::unique_ptr<Am43Encoder> encoder_;
Am43Decoder *decoder_; std::unique_ptr<Am43Decoder> decoder_;
bool logged_in_; bool logged_in_;
float position_; float position_;

View file

@ -5,7 +5,7 @@ from esphome.components import display, font
import esphome.components.image as espImage import esphome.components.image as espImage
import esphome.config_validation as cv import esphome.config_validation as cv
import esphome.codegen as cg import esphome.codegen as cg
from esphome.const import CONF_FILE, CONF_ID, CONF_TYPE, CONF_RESIZE from esphome.const import CONF_FILE, CONF_ID, CONF_RAW_DATA_ID, CONF_RESIZE, CONF_TYPE
from esphome.core import CORE, HexInt from esphome.core import CORE, HexInt
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -15,8 +15,6 @@ MULTI_CONF = True
Animation_ = display.display_ns.class_("Animation") Animation_ = display.display_ns.class_("Animation")
CONF_RAW_DATA_ID = "raw_data_id"
ANIMATION_SCHEMA = cv.Schema( ANIMATION_SCHEMA = cv.Schema(
{ {
cv.Required(CONF_ID): cv.declare_id(Animation_), cv.Required(CONF_ID): cv.declare_id(Animation_),

View file

@ -1,19 +1,19 @@
#include "anova.h" #include "anova.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#ifdef ARDUINO_ARCH_ESP32 #ifdef USE_ESP32
namespace esphome { namespace esphome {
namespace anova { namespace anova {
static const char *TAG = "anova"; static const char *const TAG = "anova";
using namespace esphome::climate; using namespace esphome::climate;
void Anova::dump_config() { LOG_CLIMATE("", "Anova BLE Cooker", this); } void Anova::dump_config() { LOG_CLIMATE("", "Anova BLE Cooker", this); }
void Anova::setup() { void Anova::setup() {
this->codec_ = new AnovaCodec(); this->codec_ = make_unique<AnovaCodec>();
this->current_request_ = 0; 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; break;
} }
case ESP_GATTC_REG_FOR_NOTIFY_EVT: { case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
this->node_state = espbt::ClientState::Established; this->node_state = espbt::ClientState::ESTABLISHED;
this->current_request_ = 0; this->current_request_ = 0;
this->update(); this->update();
break; 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::set_unit_of_measurement(const char *unit) { this->fahrenheit_ = !strncmp(unit, "f", 1); }
void Anova::update() { void Anova::update() {
if (this->node_state != espbt::ClientState::Established) if (this->node_state != espbt::ClientState::ESTABLISHED)
return; return;
if (this->current_request_ < 2) { if (this->current_request_ < 2) {
auto pkt = this->codec_->get_read_device_status_request(); auto pkt = this->codec_->get_read_device_status_request();
if (this->current_request_ == 0) if (this->current_request_ == 0)
auto pkt = this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c'); 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_, 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); pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) if (status)

View file

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

View file

@ -1,4 +1,6 @@
#include "anova_base.h" #include "anova_base.h"
#include <cstdio>
#include <cstring>
namespace esphome { namespace esphome {
namespace anova { namespace anova {

View file

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

View file

@ -215,6 +215,7 @@ message ListEntitiesBinarySensorResponse {
string device_class = 5; string device_class = 5;
bool is_status_binary_sensor = 6; bool is_status_binary_sensor = 6;
bool disabled_by_default = 7; bool disabled_by_default = 7;
string icon = 8;
} }
message BinarySensorStateResponse { message BinarySensorStateResponse {
option (id) = 21; option (id) = 21;
@ -245,6 +246,7 @@ message ListEntitiesCoverResponse {
bool supports_tilt = 7; bool supports_tilt = 7;
string device_class = 8; string device_class = 8;
bool disabled_by_default = 9; bool disabled_by_default = 9;
string icon = 10;
} }
enum LegacyCoverState { enum LegacyCoverState {
@ -313,6 +315,7 @@ message ListEntitiesFanResponse {
bool supports_direction = 7; bool supports_direction = 7;
int32 supported_speed_count = 8; int32 supported_speed_count = 8;
bool disabled_by_default = 9; bool disabled_by_default = 9;
string icon = 10;
} }
enum FanSpeed { enum FanSpeed {
FAN_SPEED_LOW = 0; FAN_SPEED_LOW = 0;
@ -388,6 +391,7 @@ message ListEntitiesLightResponse {
float max_mireds = 10; float max_mireds = 10;
repeated string effects = 11; repeated string effects = 11;
bool disabled_by_default = 13; bool disabled_by_default = 13;
string icon = 14;
} }
message LightStateResponse { message LightStateResponse {
option (id) = 24; option (id) = 24;
@ -790,6 +794,7 @@ message ListEntitiesClimateResponse {
repeated ClimatePreset supported_presets = 16; repeated ClimatePreset supported_presets = 16;
repeated string supported_custom_presets = 17; repeated string supported_custom_presets = 17;
bool disabled_by_default = 18; bool disabled_by_default = 18;
string icon = 19;
} }
message ClimateStateResponse { message ClimateStateResponse {
option (id) = 47; option (id) = 47;

View file

@ -1,7 +1,9 @@
#include "api_connection.h" #include "api_connection.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/util.h" #include "esphome/components/network/util.h"
#include "esphome/core/version.h" #include "esphome/core/version.h"
#include "esphome/core/hal.h"
#include <cerrno> #include <cerrno>
#ifdef USE_DEEP_SLEEP #ifdef USE_DEEP_SLEEP
@ -48,7 +50,7 @@ void APIConnection::loop() {
if (this->remove_) if (this->remove_)
return; return;
if (!network_is_connected()) { if (!network::is_connected()) {
// when network is disconnected force disconnect immediately // when network is disconnected force disconnect immediately
// don't wait for timeout // don't wait for timeout
this->on_fatal_error(); this->on_fatal_error();
@ -142,8 +144,21 @@ void APIConnection::loop() {
} }
} }
std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) { std::string get_default_unique_id(const std::string &component_type, EntityBase *entity) {
return App.get_name() + component_type + nameable->get_object_id(); 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
} }
DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) { DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) {
@ -179,6 +194,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_
msg.device_class = binary_sensor->get_device_class(); msg.device_class = binary_sensor->get_device_class();
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor(); msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
msg.disabled_by_default = binary_sensor->is_disabled_by_default(); msg.disabled_by_default = binary_sensor->is_disabled_by_default();
msg.icon = binary_sensor->get_icon();
return this->send_list_entities_binary_sensor_response(msg); return this->send_list_entities_binary_sensor_response(msg);
} }
#endif #endif
@ -211,6 +227,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
msg.supports_tilt = traits.get_supports_tilt(); msg.supports_tilt = traits.get_supports_tilt();
msg.device_class = cover->get_device_class(); msg.device_class = cover->get_device_class();
msg.disabled_by_default = cover->is_disabled_by_default(); msg.disabled_by_default = cover->is_disabled_by_default();
msg.icon = cover->get_icon();
return this->send_list_entities_cover_response(msg); return this->send_list_entities_cover_response(msg);
} }
void APIConnection::cover_command(const CoverCommandRequest &msg) { void APIConnection::cover_command(const CoverCommandRequest &msg) {
@ -276,6 +293,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) {
msg.supports_direction = traits.supports_direction(); msg.supports_direction = traits.supports_direction();
msg.supported_speed_count = traits.supported_speed_count(); msg.supported_speed_count = traits.supported_speed_count();
msg.disabled_by_default = fan->is_disabled_by_default(); msg.disabled_by_default = fan->is_disabled_by_default();
msg.icon = fan->get_icon();
return this->send_list_entities_fan_response(msg); return this->send_list_entities_fan_response(msg);
} }
void APIConnection::fan_command(const FanCommandRequest &msg) { void APIConnection::fan_command(const FanCommandRequest &msg) {
@ -338,6 +356,7 @@ bool APIConnection::send_light_info(light::LightState *light) {
msg.unique_id = get_default_unique_id("light", light); msg.unique_id = get_default_unique_id("light", light);
msg.disabled_by_default = light->is_disabled_by_default(); msg.disabled_by_default = light->is_disabled_by_default();
msg.icon = light->get_icon();
for (auto mode : traits.get_supported_color_modes()) for (auto mode : traits.get_supported_color_modes())
msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode)); msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode));
@ -528,6 +547,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
msg.unique_id = get_default_unique_id("climate", climate); msg.unique_id = get_default_unique_id("climate", climate);
msg.disabled_by_default = climate->is_disabled_by_default(); msg.disabled_by_default = climate->is_disabled_by_default();
msg.icon = climate->get_icon();
msg.supports_current_temperature = traits.get_supports_current_temperature(); msg.supports_current_temperature = traits.get_supports_current_temperature();
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature(); msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
@ -600,7 +620,7 @@ bool APIConnection::send_number_info(number::Number *number) {
msg.object_id = number->get_object_id(); msg.object_id = number->get_object_id();
msg.name = number->get_name(); msg.name = number->get_name();
msg.unique_id = get_default_unique_id("number", number); msg.unique_id = get_default_unique_id("number", number);
msg.icon = number->traits.get_icon(); msg.icon = number->get_icon();
msg.disabled_by_default = number->is_disabled_by_default(); msg.disabled_by_default = number->is_disabled_by_default();
msg.min_value = number->traits.get_min_value(); msg.min_value = number->traits.get_min_value();
@ -637,7 +657,7 @@ bool APIConnection::send_select_info(select::Select *select) {
msg.object_id = select->get_object_id(); msg.object_id = select->get_object_id();
msg.name = select->get_name(); msg.name = select->get_name();
msg.unique_id = get_default_unique_id("select", select); msg.unique_id = get_default_unique_id("select", select);
msg.icon = select->traits.get_icon(); msg.icon = select->get_icon();
msg.disabled_by_default = select->is_disabled_by_default(); msg.disabled_by_default = select->is_disabled_by_default();
for (const auto &option : select->traits.get_options()) for (const auto &option : select->traits.get_options())
@ -662,7 +682,7 @@ void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage>
return; return;
if (this->image_reader_.available()) if (this->image_reader_.available())
return; return;
this->image_reader_.set_image(image); this->image_reader_.set_image(std::move(image));
} }
bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
ListEntitiesCameraResponse msg; ListEntitiesCameraResponse msg;

View file

@ -3,6 +3,7 @@
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "proto.h" #include "proto.h"
#include <cstring>
namespace esphome { namespace esphome {
namespace api { namespace api {

View file

@ -1,7 +1,8 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <vector>
#include <deque> #include <deque>
#include <utility>
#include <vector>
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
@ -75,8 +76,8 @@ class APIFrameHelper {
class APINoiseFrameHelper : public APIFrameHelper { class APINoiseFrameHelper : public APIFrameHelper {
public: public:
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx) APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx)
: socket_(std::move(socket)), ctx_(ctx) {} : socket_(std::move(socket)), ctx_(std::move(std::move(ctx))) {}
~APINoiseFrameHelper(); ~APINoiseFrameHelper() override;
APIError init() override; APIError init() override;
APIError loop() override; APIError loop() override;
APIError read_packet(ReadPacketBuffer *buffer) override; APIError read_packet(ReadPacketBuffer *buffer) override;
@ -136,7 +137,7 @@ class APINoiseFrameHelper : public APIFrameHelper {
class APIPlaintextFrameHelper : public APIFrameHelper { class APIPlaintextFrameHelper : public APIFrameHelper {
public: public:
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_(std::move(socket)) {} APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_(std::move(socket)) {}
~APIPlaintextFrameHelper() = default; ~APIPlaintextFrameHelper() override = default;
APIError init() override; APIError init() override;
APIError loop() override; APIError loop() override;
APIError read_packet(ReadPacketBuffer *buffer) override; APIError read_packet(ReadPacketBuffer *buffer) override;

View file

@ -11,7 +11,7 @@ using psk_t = std::array<uint8_t, 32>;
class APINoiseContext { class APINoiseContext {
public: public:
void set_psk(psk_t psk) { psk_ = std::move(psk); } void set_psk(psk_t psk) { psk_ = psk; }
const psk_t &get_psk() const { return psk_; } const psk_t &get_psk() const { return psk_; }
protected: protected:

View file

@ -531,6 +531,10 @@ bool ListEntitiesBinarySensorResponse::decode_length(uint32_t field_id, ProtoLen
this->device_class = value.as_string(); this->device_class = value.as_string();
return true; return true;
} }
case 8: {
this->icon = value.as_string();
return true;
}
default: default:
return false; return false;
} }
@ -553,6 +557,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(5, this->device_class); buffer.encode_string(5, this->device_class);
buffer.encode_bool(6, this->is_status_binary_sensor); buffer.encode_bool(6, this->is_status_binary_sensor);
buffer.encode_bool(7, this->disabled_by_default); buffer.encode_bool(7, this->disabled_by_default);
buffer.encode_string(8, this->icon);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
@ -586,6 +591,10 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: "); out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default)); out.append(YESNO(this->disabled_by_default));
out.append("\n"); out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -677,6 +686,10 @@ bool ListEntitiesCoverResponse::decode_length(uint32_t field_id, ProtoLengthDeli
this->device_class = value.as_string(); this->device_class = value.as_string();
return true; return true;
} }
case 10: {
this->icon = value.as_string();
return true;
}
default: default:
return false; return false;
} }
@ -701,6 +714,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(7, this->supports_tilt); buffer.encode_bool(7, this->supports_tilt);
buffer.encode_string(8, this->device_class); buffer.encode_string(8, this->device_class);
buffer.encode_bool(9, this->disabled_by_default); buffer.encode_bool(9, this->disabled_by_default);
buffer.encode_string(10, this->icon);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesCoverResponse::dump_to(std::string &out) const { void ListEntitiesCoverResponse::dump_to(std::string &out) const {
@ -742,6 +756,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: "); out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default)); out.append(YESNO(this->disabled_by_default));
out.append("\n"); out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -948,6 +966,10 @@ bool ListEntitiesFanResponse::decode_length(uint32_t field_id, ProtoLengthDelimi
this->unique_id = value.as_string(); this->unique_id = value.as_string();
return true; return true;
} }
case 10: {
this->icon = value.as_string();
return true;
}
default: default:
return false; return false;
} }
@ -972,6 +994,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(7, this->supports_direction); buffer.encode_bool(7, this->supports_direction);
buffer.encode_int32(8, this->supported_speed_count); buffer.encode_int32(8, this->supported_speed_count);
buffer.encode_bool(9, this->disabled_by_default); buffer.encode_bool(9, this->disabled_by_default);
buffer.encode_string(10, this->icon);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesFanResponse::dump_to(std::string &out) const { void ListEntitiesFanResponse::dump_to(std::string &out) const {
@ -1014,6 +1037,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: "); out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default)); out.append(YESNO(this->disabled_by_default));
out.append("\n"); out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -1262,6 +1289,10 @@ bool ListEntitiesLightResponse::decode_length(uint32_t field_id, ProtoLengthDeli
this->effects.push_back(value.as_string()); this->effects.push_back(value.as_string());
return true; return true;
} }
case 14: {
this->icon = value.as_string();
return true;
}
default: default:
return false; return false;
} }
@ -1302,6 +1333,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(11, it, true); buffer.encode_string(11, it, true);
} }
buffer.encode_bool(13, this->disabled_by_default); buffer.encode_bool(13, this->disabled_by_default);
buffer.encode_string(14, this->icon);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesLightResponse::dump_to(std::string &out) const { void ListEntitiesLightResponse::dump_to(std::string &out) const {
@ -1365,6 +1397,10 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: "); out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default)); out.append(YESNO(this->disabled_by_default));
out.append("\n"); out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -3072,6 +3108,10 @@ bool ListEntitiesClimateResponse::decode_length(uint32_t field_id, ProtoLengthDe
this->supported_custom_presets.push_back(value.as_string()); this->supported_custom_presets.push_back(value.as_string());
return true; return true;
} }
case 19: {
this->icon = value.as_string();
return true;
}
default: default:
return false; return false;
} }
@ -3129,6 +3169,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(17, it, true); buffer.encode_string(17, it, true);
} }
buffer.encode_bool(18, this->disabled_by_default); buffer.encode_bool(18, this->disabled_by_default);
buffer.encode_string(19, this->icon);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesClimateResponse::dump_to(std::string &out) const { void ListEntitiesClimateResponse::dump_to(std::string &out) const {
@ -3221,6 +3262,10 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: "); out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default)); out.append(YESNO(this->disabled_by_default));
out.append("\n"); out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif

View file

@ -269,6 +269,7 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage {
std::string device_class{}; std::string device_class{};
bool is_status_binary_sensor{false}; bool is_status_binary_sensor{false};
bool disabled_by_default{false}; bool disabled_by_default{false};
std::string icon{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -304,6 +305,7 @@ class ListEntitiesCoverResponse : public ProtoMessage {
bool supports_tilt{false}; bool supports_tilt{false};
std::string device_class{}; std::string device_class{};
bool disabled_by_default{false}; bool disabled_by_default{false};
std::string icon{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -360,6 +362,7 @@ class ListEntitiesFanResponse : public ProtoMessage {
bool supports_direction{false}; bool supports_direction{false};
int32_t supported_speed_count{0}; int32_t supported_speed_count{0};
bool disabled_by_default{false}; bool disabled_by_default{false};
std::string icon{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -424,6 +427,7 @@ class ListEntitiesLightResponse : public ProtoMessage {
float max_mireds{0.0f}; float max_mireds{0.0f};
std::vector<std::string> effects{}; std::vector<std::string> effects{};
bool disabled_by_default{false}; bool disabled_by_default{false};
std::string icon{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -856,6 +860,7 @@ class ListEntitiesClimateResponse : public ProtoMessage {
std::vector<enums::ClimatePreset> supported_presets{}; std::vector<enums::ClimatePreset> supported_presets{};
std::vector<std::string> supported_custom_presets{}; std::vector<std::string> supported_custom_presets{};
bool disabled_by_default{false}; bool disabled_by_default{false};
std::string icon{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;

View file

@ -5,6 +5,8 @@
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/util.h" #include "esphome/core/util.h"
#include "esphome/core/version.h" #include "esphome/core/version.h"
#include "esphome/core/hal.h"
#include "esphome/components/network/util.h"
#include <cerrno> #include <cerrno>
#ifdef USE_LOGGER #ifdef USE_LOGGER
@ -64,7 +66,7 @@ void APIServer::setup() {
#ifdef USE_LOGGER #ifdef USE_LOGGER
if (logger::global_logger != nullptr) { if (logger::global_logger != nullptr) {
logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) { 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_) if (!c->remove_)
c->send_log_message(level, tag, message); c->send_log_message(level, tag, message);
} }
@ -76,8 +78,9 @@ void APIServer::setup() {
#ifdef USE_ESP32_CAMERA #ifdef USE_ESP32_CAMERA
if (esp32_camera::global_esp32_camera != nullptr) { if (esp32_camera::global_esp32_camera != nullptr) {
esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr<esp32_camera::CameraImage> image) { esp32_camera::global_esp32_camera->add_image_callback(
for (auto *c : this->clients_) [this](const std::shared_ptr<esp32_camera::CameraImage> &image) {
for (auto &c : this->clients_)
if (!c->remove_) if (!c->remove_)
c->send_camera_state(image); c->send_camera_state(image);
}); });
@ -95,25 +98,21 @@ void APIServer::loop() {
ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str()); ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str());
auto *conn = new APIConnection(std::move(sock), this); auto *conn = new APIConnection(std::move(sock), this);
clients_.push_back(conn); clients_.emplace_back(conn);
conn->start(); conn->start();
} }
// Partition clients into remove and active // Partition clients into remove and active
auto new_end = auto new_end = std::partition(this->clients_.begin(), this->clients_.end(),
std::partition(this->clients_.begin(), this->clients_.end(), [](APIConnection *conn) { return !conn->remove_; }); [](const std::unique_ptr<APIConnection> &conn) { return !conn->remove_; });
// print disconnection messages // print disconnection messages
for (auto it = new_end; it != this->clients_.end(); ++it) { for (auto it = new_end; it != this->clients_.end(); ++it) {
ESP_LOGV(TAG, "Removing connection to %s", (*it)->client_info_.c_str()); ESP_LOGV(TAG, "Removing connection to %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 // resize vector
this->clients_.erase(new_end, this->clients_.end()); this->clients_.erase(new_end, this->clients_.end());
for (auto *client : this->clients_) { for (auto &client : this->clients_) {
client->loop(); client->loop();
} }
@ -133,7 +132,7 @@ void APIServer::loop() {
} }
void APIServer::dump_config() { void APIServer::dump_config() {
ESP_LOGCONFIG(TAG, "API Server:"); ESP_LOGCONFIG(TAG, "API Server:");
ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->port_); ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->port_);
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
ESP_LOGCONFIG(TAG, " Using noise encryption: YES"); ESP_LOGCONFIG(TAG, " Using noise encryption: YES");
#else #else
@ -174,7 +173,7 @@ void APIServer::handle_disconnect(APIConnection *conn) {}
void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) { void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) {
if (obj->is_internal()) if (obj->is_internal())
return; return;
for (auto *c : this->clients_) for (auto &c : this->clients_)
c->send_binary_sensor_state(obj, state); c->send_binary_sensor_state(obj, state);
} }
#endif #endif
@ -183,7 +182,7 @@ void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool s
void APIServer::on_cover_update(cover::Cover *obj) { void APIServer::on_cover_update(cover::Cover *obj) {
if (obj->is_internal()) if (obj->is_internal())
return; return;
for (auto *c : this->clients_) for (auto &c : this->clients_)
c->send_cover_state(obj); c->send_cover_state(obj);
} }
#endif #endif
@ -192,7 +191,7 @@ void APIServer::on_cover_update(cover::Cover *obj) {
void APIServer::on_fan_update(fan::FanState *obj) { void APIServer::on_fan_update(fan::FanState *obj) {
if (obj->is_internal()) if (obj->is_internal())
return; return;
for (auto *c : this->clients_) for (auto &c : this->clients_)
c->send_fan_state(obj); c->send_fan_state(obj);
} }
#endif #endif
@ -201,7 +200,7 @@ void APIServer::on_fan_update(fan::FanState *obj) {
void APIServer::on_light_update(light::LightState *obj) { void APIServer::on_light_update(light::LightState *obj) {
if (obj->is_internal()) if (obj->is_internal())
return; return;
for (auto *c : this->clients_) for (auto &c : this->clients_)
c->send_light_state(obj); c->send_light_state(obj);
} }
#endif #endif
@ -210,7 +209,7 @@ void APIServer::on_light_update(light::LightState *obj) {
void APIServer::on_sensor_update(sensor::Sensor *obj, float state) { void APIServer::on_sensor_update(sensor::Sensor *obj, float state) {
if (obj->is_internal()) if (obj->is_internal())
return; return;
for (auto *c : this->clients_) for (auto &c : this->clients_)
c->send_sensor_state(obj, state); c->send_sensor_state(obj, state);
} }
#endif #endif
@ -219,7 +218,7 @@ void APIServer::on_sensor_update(sensor::Sensor *obj, float state) {
void APIServer::on_switch_update(switch_::Switch *obj, bool state) { void APIServer::on_switch_update(switch_::Switch *obj, bool state) {
if (obj->is_internal()) if (obj->is_internal())
return; return;
for (auto *c : this->clients_) for (auto &c : this->clients_)
c->send_switch_state(obj, state); c->send_switch_state(obj, state);
} }
#endif #endif
@ -228,7 +227,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) { void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) {
if (obj->is_internal()) if (obj->is_internal())
return; return;
for (auto *c : this->clients_) for (auto &c : this->clients_)
c->send_text_sensor_state(obj, state); c->send_text_sensor_state(obj, state);
} }
#endif #endif
@ -237,7 +236,7 @@ void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::s
void APIServer::on_climate_update(climate::Climate *obj) { void APIServer::on_climate_update(climate::Climate *obj) {
if (obj->is_internal()) if (obj->is_internal())
return; return;
for (auto *c : this->clients_) for (auto &c : this->clients_)
c->send_climate_state(obj); c->send_climate_state(obj);
} }
#endif #endif
@ -246,7 +245,7 @@ void APIServer::on_climate_update(climate::Climate *obj) {
void APIServer::on_number_update(number::Number *obj, float state) { void APIServer::on_number_update(number::Number *obj, float state) {
if (obj->is_internal()) if (obj->is_internal())
return; return;
for (auto *c : this->clients_) for (auto &c : this->clients_)
c->send_number_state(obj, state); c->send_number_state(obj, state);
} }
#endif #endif
@ -255,7 +254,7 @@ void APIServer::on_number_update(number::Number *obj, float state) {
void APIServer::on_select_update(select::Select *obj, const std::string &state) { void APIServer::on_select_update(select::Select *obj, const std::string &state) {
if (obj->is_internal()) if (obj->is_internal())
return; return;
for (auto *c : this->clients_) for (auto &c : this->clients_)
c->send_select_state(obj, state); c->send_select_state(obj, state);
} }
#endif #endif
@ -266,7 +265,7 @@ APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-c
void APIServer::set_password(const std::string &password) { this->password_ = password; } void APIServer::set_password(const std::string &password) { this->password_ = password; }
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) { 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); client->send_homeassistant_service_call(call);
} }
} }
@ -286,7 +285,7 @@ uint16_t APIServer::get_port() const { return this->port_; }
void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; } void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
#ifdef USE_HOMEASSISTANT_TIME #ifdef USE_HOMEASSISTANT_TIME
void APIServer::request_time() { void APIServer::request_time() {
for (auto *client : this->clients_) { for (auto &client : this->clients_) {
if (!client->remove_ && client->connection_state_ == APIConnection::ConnectionState::CONNECTED) if (!client->remove_ && client->connection_state_ == APIConnection::ConnectionState::CONNECTED)
client->send_time_request(); client->send_time_request();
} }
@ -294,7 +293,7 @@ void APIServer::request_time() {
#endif #endif
bool APIServer::is_connected() const { return !this->clients_.empty(); } bool APIServer::is_connected() const { return !this->clients_.empty(); }
void APIServer::on_shutdown() { void APIServer::on_shutdown() {
for (auto *c : this->clients_) { for (auto &c : this->clients_) {
c->send_disconnect_request(DisconnectRequest()); c->send_disconnect_request(DisconnectRequest());
} }
delay(10); delay(10);

View file

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

View file

@ -49,7 +49,7 @@ class CustomAPIDevice {
template<typename T, typename... Ts> template<typename T, typename... Ts>
void register_service(void (T::*callback)(Ts...), const std::string &name, void register_service(void (T::*callback)(Ts...), const std::string &name,
const std::array<std::string, sizeof...(Ts)> &arg_names) { const std::array<std::string, sizeof...(Ts)> &arg_names) {
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT
global_api_server->register_user_service(service); 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. * @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) { template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT
global_api_server->register_user_service(service); global_api_server->register_user_service(service);
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -265,27 +265,57 @@ float ATM90E32Component::get_power_factor_c_() {
} }
float ATM90E32Component::get_forward_active_energy_a_() { float ATM90E32Component::get_forward_active_energy_a_() {
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYA); uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYA);
return (float) val * 10 / 3200; // convert register value to WattHours 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);
} }
float ATM90E32Component::get_forward_active_energy_b_() { float ATM90E32Component::get_forward_active_energy_b_() {
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYB); uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYB);
return (float) val * 10 / 3200; 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);
} }
float ATM90E32Component::get_forward_active_energy_c_() { float ATM90E32Component::get_forward_active_energy_c_() {
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYC); uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYC);
return (float) val * 10 / 3200; 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);
} }
float ATM90E32Component::get_reverse_active_energy_a_() { float ATM90E32Component::get_reverse_active_energy_a_() {
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYA); uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYA);
return (float) val * 10 / 3200; 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);
} }
float ATM90E32Component::get_reverse_active_energy_b_() { float ATM90E32Component::get_reverse_active_energy_b_() {
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYB); uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYB);
return (float) val * 10 / 3200; 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);
} }
float ATM90E32Component::get_reverse_active_energy_c_() { float ATM90E32Component::get_reverse_active_energy_c_() {
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYC); uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYC);
return (float) val * 10 / 3200; 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);
} }
float ATM90E32Component::get_frequency_() { float ATM90E32Component::get_frequency_() {
uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ); uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ);

View file

@ -77,6 +77,8 @@ class ATM90E32Component : public PollingComponent,
sensor::Sensor *power_factor_sensor_{nullptr}; sensor::Sensor *power_factor_sensor_{nullptr};
sensor::Sensor *forward_active_energy_sensor_{nullptr}; sensor::Sensor *forward_active_energy_sensor_{nullptr};
sensor::Sensor *reverse_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]; } phase_[3];
sensor::Sensor *freq_sensor_{nullptr}; sensor::Sensor *freq_sensor_{nullptr};
sensor::Sensor *chip_temperature_sensor_{nullptr}; sensor::Sensor *chip_temperature_sensor_{nullptr};

View file

@ -1,7 +1,7 @@
#include "b_parasite.h" #include "b_parasite.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#ifdef ARDUINO_ARCH_ESP32 #ifdef USE_ESP32
namespace esphome { namespace esphome {
namespace b_parasite { namespace b_parasite {
@ -14,6 +14,7 @@ void BParasite::dump_config() {
LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Humidity", this->humidity_);
LOG_SENSOR(" ", "Soil Moisture", this->soil_moisture_); LOG_SENSOR(" ", "Soil Moisture", this->soil_moisture_);
LOG_SENSOR(" ", "Illuminance", this->illuminance_);
} }
bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
@ -36,6 +37,15 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
const auto &data = service_data.data; 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. // Counter for deduplicating messages.
uint8_t counter = data[1] & 0x0f; uint8_t counter = data[1] & 0x0f;
if (last_processed_counter_ == counter) { if (last_processed_counter_ == counter) {
@ -59,6 +69,9 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
uint16_t soil_moisture = data[8] << 8 | data[9]; uint16_t soil_moisture = data[8] << 8 | data[9];
float moisture_percent = (100.0f * soil_moisture) / (1 << 16); 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) { if (battery_voltage_ != nullptr) {
battery_voltage_->publish_state(battery_voltage); battery_voltage_->publish_state(battery_voltage);
} }
@ -71,6 +84,13 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
if (soil_moisture_ != nullptr) { if (soil_moisture_ != nullptr) {
soil_moisture_->publish_state(moisture_percent); 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; last_processed_counter_ = counter;
return true; return true;
@ -79,4 +99,4 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
} // namespace b_parasite } // namespace b_parasite
} // namespace esphome } // namespace esphome
#endif // ARDUINO_ARCH_ESP32 #endif // USE_ESP32

View file

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

View file

@ -5,14 +5,17 @@ from esphome.const import (
CONF_BATTERY_VOLTAGE, CONF_BATTERY_VOLTAGE,
CONF_HUMIDITY, CONF_HUMIDITY,
CONF_ID, CONF_ID,
CONF_ILLUMINANCE,
CONF_MOISTURE, CONF_MOISTURE,
CONF_MAC_ADDRESS, CONF_MAC_ADDRESS,
CONF_TEMPERATURE, CONF_TEMPERATURE,
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS, UNIT_CELSIUS,
UNIT_LUX,
UNIT_PERCENT, UNIT_PERCENT,
UNIT_VOLT, UNIT_VOLT,
) )
@ -55,6 +58,12 @@ CONFIG_SCHEMA = (
device_class=DEVICE_CLASS_HUMIDITY, device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT, 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) .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
@ -74,6 +83,7 @@ async def to_code(config):
(CONF_HUMIDITY, var.set_humidity), (CONF_HUMIDITY, var.set_humidity),
(CONF_BATTERY_VOLTAGE, var.set_battery_voltage), (CONF_BATTERY_VOLTAGE, var.set_battery_voltage),
(CONF_MOISTURE, var.set_soil_moisture), (CONF_MOISTURE, var.set_soil_moisture),
(CONF_ILLUMINANCE, var.set_illuminance),
]: ]:
if config_key in config: if config_key in config:
sens = await sensor.new_sensor(config[config_key]) sens = await sensor.new_sensor(config[config_key])

View file

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

View file

@ -71,10 +71,11 @@ void BH1750Sensor::update() {
float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; } float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; }
void BH1750Sensor::read_data_() { void BH1750Sensor::read_data_() {
uint16_t raw_value; uint16_t raw_value;
if (!this->parent_->raw_receive_16(this->address_, &raw_value, 1)) { if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
this->status_set_warning(); this->status_set_warning();
return; return;
} }
raw_value = i2c::i2ctohs(raw_value);
float lx = float(raw_value) / 1.2f; float lx = float(raw_value) / 1.2f;
lx *= 69.0f / this->measurement_duration_; lx *= 69.0f / this->measurement_duration_;

View file

@ -1,15 +1,14 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.cpp_helpers import setup_entity
from esphome import automation, core from esphome import automation, core
from esphome.automation import Condition, maybe_simple_id from esphome.automation import Condition, maybe_simple_id
from esphome.components import mqtt from esphome.components import mqtt
from esphome.const import ( from esphome.const import (
CONF_DELAY, CONF_DELAY,
CONF_DEVICE_CLASS, CONF_DEVICE_CLASS,
CONF_DISABLED_BY_DEFAULT,
CONF_FILTERS, CONF_FILTERS,
CONF_ID, CONF_ID,
CONF_INTERNAL,
CONF_INVALID_COOLDOWN, CONF_INVALID_COOLDOWN,
CONF_INVERTED, CONF_INVERTED,
CONF_MAX_LENGTH, CONF_MAX_LENGTH,
@ -88,7 +87,7 @@ DEVICE_CLASSES = [
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor") binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor")
BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.Nameable) BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.EntityBase)
BinarySensorInitiallyOff = binary_sensor_ns.class_( BinarySensorInitiallyOff = binary_sensor_ns.class_(
"BinarySensorInitiallyOff", BinarySensor "BinarySensorInitiallyOff", BinarySensor
) )
@ -231,17 +230,16 @@ def parse_multi_click_timing_str(value):
parts = value.lower().split(" ") parts = value.lower().split(" ")
if len(parts) != 5: if len(parts) != 5:
raise cv.Invalid( raise cv.Invalid(
"Multi click timing grammar consists of exactly 5 words, not {}" f"Multi click timing grammar consists of exactly 5 words, not {len(parts)}"
"".format(len(parts))
) )
try: try:
state = cv.boolean(parts[0]) state = cv.boolean(parts[0])
except cv.Invalid: except cv.Invalid:
# pylint: disable=raise-missing-from # pylint: disable=raise-missing-from
raise cv.Invalid("First word must either be ON or OFF, not {}".format(parts[0])) raise cv.Invalid(f"First word must either be ON or OFF, not {parts[0]}")
if parts[1] != "for": if parts[1] != "for":
raise cv.Invalid("Second word must be 'for', got {}".format(parts[1])) raise cv.Invalid(f"Second word must be 'for', got {parts[1]}")
if parts[2] == "at": if parts[2] == "at":
if parts[3] == "least": if parts[3] == "least":
@ -250,8 +248,7 @@ def parse_multi_click_timing_str(value):
key = CONF_MAX_LENGTH key = CONF_MAX_LENGTH
else: else:
raise cv.Invalid( raise cv.Invalid(
"Third word after at must either be 'least' or 'most', got {}" f"Third word after at must either be 'least' or 'most', got {parts[3]}"
"".format(parts[3])
) )
try: try:
length = cv.positive_time_period_milliseconds(parts[4]) length = cv.positive_time_period_milliseconds(parts[4])
@ -296,13 +293,11 @@ def validate_multi_click_timing(value):
new_state = v_.get(CONF_STATE, not state) new_state = v_.get(CONF_STATE, not state)
if new_state == state: if new_state == state:
raise cv.Invalid( raise cv.Invalid(
"Timings must have alternating state. Indices {} and {} have " f"Timings must have alternating state. Indices {i} and {i + 1} have the same state {state}"
"the same state {}".format(i, i + 1, state)
) )
if max_length is not None and max_length < min_length: if max_length is not None and max_length < min_length:
raise cv.Invalid( raise cv.Invalid(
"Max length ({}) must be larger than min length ({})." f"Max length ({max_length}) must be larger than min length ({min_length})."
"".format(max_length, min_length)
) )
state = new_state state = new_state
@ -318,7 +313,7 @@ def validate_multi_click_timing(value):
device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
BINARY_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
{ {
cv.GenerateID(): cv.declare_id(BinarySensor), cv.GenerateID(): cv.declare_id(BinarySensor),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id( cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
@ -379,10 +374,8 @@ BINARY_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).exten
async def setup_binary_sensor_core_(var, config): async def setup_binary_sensor_core_(var, config):
cg.add(var.set_name(config[CONF_NAME])) await setup_entity(var, config)
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: if CONF_DEVICE_CLASS in config:
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
if CONF_INVERTED in config: if CONF_INVERTED in config:

View file

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

View file

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

View file

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

View file

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

View file

@ -4,7 +4,7 @@
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "ble_client.h" #include "ble_client.h"
#ifdef ARDUINO_ARCH_ESP32 #ifdef USE_ESP32
namespace esphome { namespace esphome {
namespace ble_client { namespace ble_client {
@ -17,12 +17,12 @@ void BLEClient::setup() {
ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret); ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
this->mark_failed(); this->mark_failed();
} }
this->set_states(espbt::ClientState::Idle); this->set_states_(espbt::ClientState::IDLE);
this->enabled = true; this->enabled = true;
} }
void BLEClient::loop() { void BLEClient::loop() {
if (this->state() == espbt::ClientState::Discovered) { if (this->state() == espbt::ClientState::DISCOVERED) {
this->connect(); this->connect();
} }
for (auto *node : this->nodes_) for (auto *node : this->nodes_)
@ -39,11 +39,11 @@ bool BLEClient::parse_device(const espbt::ESPBTDevice &device) {
return false; return false;
if (device.address_uint64() != this->address) if (device.address_uint64() != this->address)
return false; return false;
if (this->state() != espbt::ClientState::Idle) if (this->state() != espbt::ClientState::IDLE)
return false; return false;
ESP_LOGD(TAG, "Found device at MAC address [%s]", device.address_str().c_str()); 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(); auto addr = device.address_uint64();
this->remote_bda[0] = (addr >> 40) & 0xFF; this->remote_bda[0] = (addr >> 40) & 0xFF;
@ -69,7 +69,7 @@ std::string BLEClient::address_str() const {
void BLEClient::set_enabled(bool enabled) { void BLEClient::set_enabled(bool enabled) {
if (enabled == this->enabled) if (enabled == this->enabled)
return; 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()); ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str());
auto ret = esp_ble_gattc_close(this->gattc_if, this->conn_id); auto ret = esp_ble_gattc_close(this->gattc_if, this->conn_id);
if (ret) { if (ret) {
@ -84,9 +84,9 @@ void BLEClient::connect() {
auto ret = esp_ble_gattc_open(this->gattc_if, this->remote_bda, BLE_ADDR_TYPE_PUBLIC, true); auto ret = esp_ble_gattc_open(this->gattc_if, this->remote_bda, BLE_ADDR_TYPE_PUBLIC, true);
if (ret) { if (ret) {
ESP_LOGW(TAG, "esp_ble_gattc_open error, address=%s status=%d", this->address_str().c_str(), 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 { } else {
this->set_states(espbt::ClientState::Connecting); this->set_states_(espbt::ClientState::CONNECTING);
} }
} }
@ -97,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) if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if)
return; return;
bool all_established = this->all_nodes_established(); bool all_established = this->all_nodes_established_();
switch (event) { switch (event) {
case ESP_GATTC_REG_EVT: { case ESP_GATTC_REG_EVT: {
@ -113,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()); ESP_LOGV(TAG, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str());
if (param->open.status != ESP_GATT_OK) { if (param->open.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status); 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; break;
} }
this->conn_id = param->open.conn_id; this->conn_id = param->open.conn_id;
@ -126,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: { case ESP_GATTC_CFG_MTU_EVT: {
if (param->cfg_mtu.status != ESP_GATT_OK) { 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); 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; break;
} }
ESP_LOGV(TAG, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, param->cfg_mtu.mtu); 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, NULL); esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
break; break;
} }
case ESP_GATTC_DISCONNECT_EVT: { case ESP_GATTC_DISCONNECT_EVT: {
@ -139,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()); ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT", this->address_str().c_str());
for (auto &svc : this->services_) for (auto &svc : this->services_)
delete svc; delete svc; // NOLINT(cppcoreguidelines-owning-memory)
this->services_.clear(); this->services_.clear();
this->set_states(espbt::ClientState::Idle); this->set_states_(espbt::ClientState::IDLE);
break; break;
} }
case ESP_GATTC_SEARCH_RES_EVT: { case ESP_GATTC_SEARCH_RES_EVT: {
BLEService *ble_service = new BLEService(); BLEService *ble_service = new BLEService(); // NOLINT(cppcoreguidelines-owning-memory)
ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid); ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid);
ble_service->start_handle = param->search_res.start_handle; ble_service->start_handle = param->search_res.start_handle;
ble_service->end_handle = param->search_res.end_handle; ble_service->end_handle = param->search_res.end_handle;
@ -160,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); ESP_LOGI(TAG, " start_handle: 0x%x end_handle: 0x%x", svc->start_handle, svc->end_handle);
svc->parse_characteristics(); svc->parse_characteristics();
} }
this->set_states(espbt::ClientState::Connected); this->set_states_(espbt::ClientState::CONNECTED);
this->set_state(espbt::ClientState::Established); this->set_state(espbt::ClientState::ESTABLISHED);
break; break;
} }
case ESP_GATTC_REG_FOR_NOTIFY_EVT: { case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
@ -192,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); node->gattc_event_handler(event, esp_gattc_if, param);
// Delete characteristics after clients have used them to save RAM. // 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_) for (auto &svc : this->services_)
delete svc; delete svc; // NOLINT(cppcoreguidelines-owning-memory)
this->services_.clear(); this->services_.clear();
} }
} }
@ -307,7 +307,7 @@ BLEDescriptor *BLEClient::get_descriptor(uint16_t service, uint16_t chr, uint16_
BLEService::~BLEService() { BLEService::~BLEService() {
for (auto &chr : this->characteristics) for (auto &chr : this->characteristics)
delete chr; delete chr; // NOLINT(cppcoreguidelines-owning-memory)
} }
void BLEService::parse_characteristics() { void BLEService::parse_characteristics() {
@ -329,7 +329,7 @@ void BLEService::parse_characteristics() {
break; break;
} }
BLECharacteristic *characteristic = new BLECharacteristic(); BLECharacteristic *characteristic = new BLECharacteristic(); // NOLINT(cppcoreguidelines-owning-memory)
characteristic->uuid = espbt::ESPBTUUID::from_uuid(result.uuid); characteristic->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
characteristic->properties = result.properties; characteristic->properties = result.properties;
characteristic->handle = result.char_handle; characteristic->handle = result.char_handle;
@ -344,7 +344,7 @@ void BLEService::parse_characteristics() {
BLECharacteristic::~BLECharacteristic() { BLECharacteristic::~BLECharacteristic() {
for (auto &desc : this->descriptors) for (auto &desc : this->descriptors)
delete desc; delete desc; // NOLINT(cppcoreguidelines-owning-memory)
} }
void BLECharacteristic::parse_descriptors() { void BLECharacteristic::parse_descriptors() {
@ -366,7 +366,7 @@ void BLECharacteristic::parse_descriptors() {
break; break;
} }
BLEDescriptor *desc = new BLEDescriptor(); BLEDescriptor *desc = new BLEDescriptor(); // NOLINT(cppcoreguidelines-owning-memory)
desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid); desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
desc->handle = result.handle; desc->handle = result.handle;
desc->characteristic = this; desc->characteristic = this;

View file

@ -4,7 +4,7 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#ifdef ARDUINO_ARCH_ESP32 #ifdef USE_ESP32
#include <string> #include <string>
#include <array> #include <array>
@ -82,10 +82,11 @@ class BLEClient : public espbt::ESPBTClient, public Component {
void dump_config() override; void dump_config() override;
void loop() 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); void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
bool parse_device(const espbt::ESPBTDevice &device) override; bool parse_device(const espbt::ESPBTDevice &device) override;
void on_scan_end() override {} void on_scan_end() override {}
void connect(); void connect() override;
void set_address(uint64_t address) { this->address = address; } void set_address(uint64_t address) { this->address = address; }
@ -116,16 +117,16 @@ class BLEClient : public espbt::ESPBTClient, public Component {
std::string address_str() const; std::string address_str() const;
protected: protected:
void set_states(espbt::ClientState st) { void set_states_(espbt::ClientState st) {
this->set_state(st); this->set_state(st);
for (auto &node : nodes_) for (auto &node : nodes_)
node->node_state = st; node->node_state = st;
} }
bool all_nodes_established() { bool all_nodes_established_() {
if (this->state() != espbt::ClientState::Established) if (this->state() != espbt::ClientState::ESTABLISHED)
return false; return false;
for (auto &node : nodes_) for (auto &node : nodes_)
if (node->node_state != espbt::ClientState::Established) if (node->node_state != espbt::ClientState::ESTABLISHED)
return false; return false;
return true; return true;
} }

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