Merge branch 'dev' into jesserockz-2023-232

This commit is contained in:
Jesse Hills 2023-09-21 10:27:33 +12:00 committed by GitHub
commit d7b0060265
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
601 changed files with 26520 additions and 6006 deletions

View file

@ -5,9 +5,12 @@ Checks: >-
-altera-*, -altera-*,
-android-*, -android-*,
-boost-*, -boost-*,
-bugprone-easily-swappable-parameters,
-bugprone-implicit-widening-of-multiplication-result,
-bugprone-narrowing-conversions, -bugprone-narrowing-conversions,
-bugprone-signed-char-misuse, -bugprone-signed-char-misuse,
-cert-dcl50-cpp, -cert-dcl50-cpp,
-cert-err33-c,
-cert-err58-cpp, -cert-err58-cpp,
-cert-oop57-cpp, -cert-oop57-cpp,
-cert-str34-c, -cert-str34-c,
@ -15,6 +18,7 @@ Checks: >-
-clang-analyzer-osx.*, -clang-analyzer-osx.*,
-clang-diagnostic-delete-abstract-non-virtual-dtor, -clang-diagnostic-delete-abstract-non-virtual-dtor,
-clang-diagnostic-delete-non-abstract-non-virtual-dtor, -clang-diagnostic-delete-non-abstract-non-virtual-dtor,
-clang-diagnostic-ignored-optimization-argument,
-clang-diagnostic-shadow-field, -clang-diagnostic-shadow-field,
-clang-diagnostic-unused-const-variable, -clang-diagnostic-unused-const-variable,
-clang-diagnostic-unused-parameter, -clang-diagnostic-unused-parameter,
@ -25,6 +29,7 @@ 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-prefer-member-initializer,
-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,
@ -36,6 +41,7 @@ Checks: >-
-cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-special-member-functions, -cppcoreguidelines-special-member-functions,
-cppcoreguidelines-virtual-class-destructor,
-fuchsia-multiple-inheritance, -fuchsia-multiple-inheritance,
-fuchsia-overloaded-operator, -fuchsia-overloaded-operator,
-fuchsia-statically-constructed-objects, -fuchsia-statically-constructed-objects,
@ -68,6 +74,7 @@ Checks: >-
-modernize-use-nodiscard, -modernize-use-nodiscard,
-mpi-*, -mpi-*,
-objc-*, -objc-*,
-readability-container-data-pointer,
-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-function-cognitive-complexity,
@ -82,8 +89,6 @@ WarningsAsErrors: '*'
AnalyzeTemporaryDtors: false AnalyzeTemporaryDtors: false
FormatStyle: google FormatStyle: google
CheckOptions: CheckOptions:
- key: google-readability-braces-around-statements.ShortStatementLines
value: '1'
- key: google-readability-function-size.StatementThreshold - key: google-readability-function-size.StatementThreshold
value: '800' value: '800'
- key: google-runtime-int.TypeSuffix - key: google-runtime-int.TypeSuffix
@ -158,3 +163,9 @@ CheckOptions:
value: '' value: ''
- key: readability-qualified-auto.AddConstToQualified - key: readability-qualified-auto.AddConstToQualified
value: 0 value: 0
- key: readability-identifier-length.MinimumVariableNameLength
value: 0
- key: readability-identifier-length.MinimumParameterNameLength
value: 0
- key: readability-identifier-length.MinimumLoopCounterNameLength
value: 0

View file

@ -0,0 +1,38 @@
name: Restore Python
inputs:
python-version:
description: Python version to restore
required: true
type: string
cache-key:
description: Cache key to use
required: true
type: string
outputs:
python-version:
description: Python version restored
value: ${{ steps.python.outputs.python-version }}
runs:
using: "composite"
steps:
- name: Set up Python ${{ inputs.python-version }}
id: python
uses: actions/setup-python@v4.7.0
with:
python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache/restore@v3.3.2
with:
path: venv
# yamllint disable-line rule:line-length
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ inputs.cache-key }}
- name: Create Python virtual environment
if: steps.cache-venv.outputs.cache-hit != 'true'
shell: bash
run: |
python -m venv venv
. venv/bin/activate
python --version
pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
pip install -e .

View file

@ -8,7 +8,7 @@ on:
branches: [dev, beta, release] branches: [dev, beta, release]
paths: paths:
- "docker/**" - "docker/**"
- ".github/workflows/**" - ".github/workflows/ci-docker.yml"
- "requirements*.txt" - "requirements*.txt"
- "platformio.ini" - "platformio.ini"
- "script/platformio_install_deps.py" - "script/platformio_install_deps.py"
@ -16,7 +16,7 @@ on:
pull_request: pull_request:
paths: paths:
- "docker/**" - "docker/**"
- ".github/workflows/**" - ".github/workflows/ci-docker.yml"
- "requirements*.txt" - "requirements*.txt"
- "platformio.ini" - "platformio.ini"
- "script/platformio_install_deps.py" - "script/platformio_install_deps.py"
@ -40,15 +40,15 @@ jobs:
arch: [amd64, armv7, aarch64] arch: [amd64, armv7, aarch64]
build_type: ["ha-addon", "docker", "lint"] build_type: ["ha-addon", "docker", "lint"]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4.0.0
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4.7.0
with: with:
python-version: "3.9" python-version: "3.9"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3.0.0
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v3.0.0
- name: Set TAG - name: Set TAG
run: | run: |

View file

@ -7,6 +7,10 @@ on:
branches: [dev, beta, release] branches: [dev, beta, release]
pull_request: pull_request:
paths:
- "**"
- "!.github/workflows/*.yml"
- ".github/workflows/ci.yml"
merge_group: merge_group:
permissions: permissions:
@ -26,20 +30,26 @@ jobs:
common: common:
name: Create common environment name: Create common environment
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs:
cache-key: ${{ steps.cache-key.outputs.key }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v4.0.0
- name: Generate cache-key
id: cache-key
run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.6.0 id: python
uses: actions/setup-python@v4.7.0
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v3.3.1 uses: actions/cache@v3.3.2
with: with:
path: venv path: venv
# yamllint disable-line rule:line-length # yamllint disable-line rule:line-length
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }} key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ steps.cache-key.outputs.key }}
- name: Create Python virtual environment - name: Create Python virtual environment
if: steps.cache-venv.outputs.cache-hit != 'true' if: steps.cache-venv.outputs.cache-hit != 'true'
run: | run: |
@ -49,15 +59,6 @@ jobs:
pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
pip install -e . pip install -e .
yamllint:
name: yamllint
runs-on: ubuntu-latest
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.2
- name: Run yamllint
uses: frenck/action-yamllint@v1.4.1
black: black:
name: Check black name: Check black
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -65,13 +66,12 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v4.0.0
- name: Restore Python virtual environment - name: Restore Python
uses: actions/cache/restore@v3.3.1 uses: ./.github/actions/restore-python
with: with:
path: venv python-version: ${{ env.DEFAULT_PYTHON }}
# yamllint disable-line rule:line-length cache-key: ${{ needs.common.outputs.cache-key }}
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
- name: Run black - name: Run black
run: | run: |
. venv/bin/activate . venv/bin/activate
@ -87,13 +87,12 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v4.0.0
- name: Restore Python virtual environment - name: Restore Python
uses: actions/cache/restore@v3.3.1 uses: ./.github/actions/restore-python
with: with:
path: venv python-version: ${{ env.DEFAULT_PYTHON }}
# yamllint disable-line rule:line-length cache-key: ${{ needs.common.outputs.cache-key }}
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
- name: Run flake8 - name: Run flake8
run: | run: |
. venv/bin/activate . venv/bin/activate
@ -109,13 +108,12 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v4.0.0
- name: Restore Python virtual environment - name: Restore Python
uses: actions/cache/restore@v3.3.1 uses: ./.github/actions/restore-python
with: with:
path: venv python-version: ${{ env.DEFAULT_PYTHON }}
# yamllint disable-line rule:line-length cache-key: ${{ needs.common.outputs.cache-key }}
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
- name: Run pylint - name: Run pylint
run: | run: |
. venv/bin/activate . venv/bin/activate
@ -131,13 +129,12 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v4.0.0
- name: Restore Python virtual environment - name: Restore Python
uses: actions/cache/restore@v3.3.1 uses: ./.github/actions/restore-python
with: with:
path: venv python-version: ${{ env.DEFAULT_PYTHON }}
# yamllint disable-line rule:line-length cache-key: ${{ needs.common.outputs.cache-key }}
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
- name: Run pyupgrade - name: Run pyupgrade
run: | run: |
. venv/bin/activate . venv/bin/activate
@ -153,13 +150,12 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v4.0.0
- name: Restore Python virtual environment - name: Restore Python
uses: actions/cache/restore@v3.3.1 uses: ./.github/actions/restore-python
with: with:
path: venv python-version: ${{ env.DEFAULT_PYTHON }}
# yamllint disable-line rule:line-length cache-key: ${{ needs.common.outputs.cache-key }}
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
- name: Register matcher - name: Register matcher
run: echo "::add-matcher::.github/workflows/matchers/ci-custom.json" run: echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
- name: Run script/ci-custom - name: Run script/ci-custom
@ -175,13 +171,12 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v4.0.0
- name: Restore Python virtual environment - name: Restore Python
uses: actions/cache/restore@v3.3.1 uses: ./.github/actions/restore-python
with: with:
path: venv python-version: ${{ env.DEFAULT_PYTHON }}
# yamllint disable-line rule:line-length cache-key: ${{ needs.common.outputs.cache-key }}
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
- name: Register matcher - name: Register matcher
run: echo "::add-matcher::.github/workflows/matchers/pytest.json" run: echo "::add-matcher::.github/workflows/matchers/pytest.json"
- name: Run pytest - name: Run pytest
@ -196,13 +191,12 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v4.0.0
- name: Restore Python virtual environment - name: Restore Python
uses: actions/cache/restore@v3.3.1 uses: ./.github/actions/restore-python
with: with:
path: venv python-version: ${{ env.DEFAULT_PYTHON }}
# yamllint disable-line rule:line-length cache-key: ${{ needs.common.outputs.cache-key }}
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
- name: Install clang-format - name: Install clang-format
run: | run: |
. venv/bin/activate . venv/bin/activate
@ -216,6 +210,17 @@ jobs:
run: script/ci-suggest-changes run: script/ci-suggest-changes
if: always() if: always()
compile-tests-list:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.0.0
- name: Find all YAML test files
id: set-matrix
run: echo "matrix=$(ls tests/test*.yaml | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
compile-tests: compile-tests:
name: Run YAML test ${{ matrix.file }} name: Run YAML test ${{ matrix.file }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -228,31 +233,24 @@ jobs:
- pylint - pylint
- pytest - pytest
- pyupgrade - pyupgrade
- yamllint - compile-tests-list
strategy: strategy:
fail-fast: false fail-fast: false
max-parallel: 2 max-parallel: 2
matrix: matrix:
file: [1, 2, 3, 3.1, 4, 5, 6, 7, 8] file: ${{ fromJson(needs.compile-tests-list.outputs.matrix) }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v4.0.0
- name: Restore Python virtual environment - name: Restore Python
uses: actions/cache/restore@v3.3.1 uses: ./.github/actions/restore-python
with: with:
path: venv python-version: ${{ env.DEFAULT_PYTHON }}
# yamllint disable-line rule:line-length cache-key: ${{ needs.common.outputs.cache-key }}
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }} - name: Run esphome compile ${{ matrix.file }}
- name: Cache platformio
uses: actions/cache@v3.3.1
with:
path: ~/.platformio
# yamllint disable-line rule:line-length
key: platformio-test${{ matrix.file }}-${{ hashFiles('platformio.ini') }}
- name: Run esphome compile tests/test${{ matrix.file }}.yaml
run: | run: |
. venv/bin/activate . venv/bin/activate
esphome compile tests/test${{ matrix.file }}.yaml esphome compile ${{ matrix.file }}
clang-tidy: clang-tidy:
name: ${{ matrix.name }} name: ${{ matrix.name }}
@ -266,7 +264,6 @@ jobs:
- pylint - pylint
- pytest - pytest
- pyupgrade - pyupgrade
- yamllint
strategy: strategy:
fail-fast: false fail-fast: false
max-parallel: 2 max-parallel: 2
@ -299,23 +296,21 @@ jobs:
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v4.0.0
- name: Restore Python virtual environment - name: Restore Python
uses: actions/cache/restore@v3.3.1 uses: ./.github/actions/restore-python
with: with:
path: venv python-version: ${{ env.DEFAULT_PYTHON }}
# yamllint disable-line rule:line-length cache-key: ${{ needs.common.outputs.cache-key }}
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
# Use per check platformio cache because checks use different parts
- name: Cache platformio - name: Cache platformio
uses: actions/cache@v3.3.1 uses: actions/cache@v3.3.2
with: with:
path: ~/.platformio path: ~/.platformio
# yamllint disable-line rule:line-length # yamllint disable-line rule:line-length
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
- name: Install clang-tidy - name: Install clang-tidy
run: sudo apt-get install clang-tidy-11 run: sudo apt-get install clang-tidy-14
- name: Register problem matchers - name: Register problem matchers
run: | run: |
@ -347,7 +342,6 @@ jobs:
- pylint - pylint
- pytest - pytest
- pyupgrade - pyupgrade
- yamllint
- compile-tests - compile-tests
- clang-tidy - clang-tidy
if: always() if: always()

View file

@ -18,7 +18,7 @@ jobs:
lock: lock:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: dessant/lock-threads@v4 - uses: dessant/lock-threads@v4.0.1
with: with:
pr-inactive-days: "1" pr-inactive-days: "1"
pr-lock-reason: "" pr-lock-reason: ""

View file

@ -19,7 +19,7 @@ jobs:
outputs: outputs:
tag: ${{ steps.tag.outputs.tag }} tag: ${{ steps.tag.outputs.tag }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4.0.0
- name: Get tag - name: Get tag
id: tag id: tag
# yamllint disable rule:line-length # yamllint disable rule:line-length
@ -43,15 +43,17 @@ jobs:
if: github.repository == 'esphome/esphome' && github.event_name == 'release' if: github.repository == 'esphome/esphome' && github.event_name == 'release'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4.0.0
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4.7.0
with: with:
python-version: "3.x" python-version: "3.x"
- name: Set up python environment - name: Set up python environment
env:
ESPHOME_NO_VENV: 1
run: | run: |
script/setup script/setup
pip install setuptools wheel twine pip install twine
- name: Build - name: Build
run: python setup.py sdist bdist_wheel run: python setup.py sdist bdist_wheel
- name: Upload - name: Upload
@ -86,24 +88,24 @@ jobs:
target: "lint" target: "lint"
baseimg: "docker" baseimg: "docker"
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4.0.0
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4.7.0
with: with:
python-version: "3.9" python-version: "3.9"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3.0.0
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v3.0.0
- name: Log in to docker hub - name: Log in to docker hub
uses: docker/login-action@v2 uses: docker/login-action@v3.0.0
with: with:
username: ${{ secrets.DOCKER_USER }} username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the GitHub container registry - name: Log in to the GitHub container registry
uses: docker/login-action@v2 uses: docker/login-action@v3.0.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
@ -117,7 +119,7 @@ jobs:
--suffix "${{ matrix.image.suffix }}" --suffix "${{ matrix.image.suffix }}"
- name: Build and push - name: Build and push
uses: docker/build-push-action@v4 uses: docker/build-push-action@v5.0.0
with: with:
context: . context: .
file: ./docker/Dockerfile file: ./docker/Dockerfile
@ -139,7 +141,7 @@ jobs:
needs: [deploy-docker] needs: [deploy-docker]
steps: steps:
- name: Trigger Workflow - name: Trigger Workflow
uses: actions/github-script@v6 uses: actions/github-script@v6.4.1
with: with:
github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }} github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
script: | script: |

View file

@ -18,7 +18,7 @@ jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v8 - uses: actions/stale@v8.0.0
with: with:
days-before-pr-stale: 90 days-before-pr-stale: 90
days-before-pr-close: 7 days-before-pr-close: 7
@ -38,7 +38,7 @@ jobs:
close-issues: close-issues:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v8 - uses: actions/stale@v8.0.0
with: with:
days-before-pr-stale: -1 days-before-pr-stale: -1
days-before-pr-close: -1 days-before-pr-close: -1

View file

@ -4,28 +4,25 @@ name: Synchronise Device Classes from Home Assistant
on: on:
workflow_dispatch: workflow_dispatch:
schedule: schedule:
- cron: '45 6 * * *' - cron: "45 6 * * *"
permissions:
contents: write
pull-requests: write
jobs: jobs:
sync: sync:
name: Sync Device Classes name: Sync Device Classes
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'esphome/esphome'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4.0.0
- name: Checkout Home Assistant - name: Checkout Home Assistant
uses: actions/checkout@v3 uses: actions/checkout@v4.0.0
with: with:
repository: home-assistant/core repository: home-assistant/core
path: lib/home-assistant path: lib/home-assistant
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4.7.0
with: with:
python-version: 3.11 python-version: 3.11
@ -38,17 +35,8 @@ jobs:
run: | run: |
python ./script/sync-device_class.py python ./script/sync-device_class.py
- name: Get PR template
id: pr-template-body
run: |
body=$(cat .github/PULL_REQUEST_TEMPLATE.md)
delimiter="$(openssl rand -hex 8)"
echo "body<<$delimiter" >> $GITHUB_OUTPUT
echo "$body" >> $GITHUB_OUTPUT
echo "$delimiter" >> $GITHUB_OUTPUT
- name: Commit changes - name: Commit changes
uses: peter-evans/create-pull-request@v5 uses: peter-evans/create-pull-request@v5.0.2
with: with:
commit-message: "Synchronise Device Classes from Home Assistant" commit-message: "Synchronise Device Classes from Home Assistant"
committer: esphomebot <esphome@nabucasa.com> committer: esphomebot <esphome@nabucasa.com>
@ -56,5 +44,5 @@ jobs:
branch: sync/device-classes branch: sync/device-classes
delete-branch: true delete-branch: true
title: "Synchronise Device Classes from Home Assistant" title: "Synchronise Device Classes from Home Assistant"
body: ${{ steps.pr-template-body.outputs.body }} body-path: .github/PULL_REQUEST_TEMPLATE.md
token: ${{ secrets.DEVICE_CLASS_SYNC_TOKEN }} token: ${{ secrets.DEVICE_CLASS_SYNC_TOKEN }}

22
.github/workflows/yaml-lint.yml vendored Normal file
View file

@ -0,0 +1,22 @@
name: YAML lint
on:
push:
branches: [dev, beta, release]
paths:
- "**.yaml"
- "**.yml"
pull_request:
paths:
- "**.yaml"
- "**.yml"
jobs:
yamllint:
name: yamllint
runs-on: ubuntu-latest
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.0.0
- name: Run yamllint
uses: frenck/action-yamllint@v1.4.1

8
.gitignore vendored
View file

@ -13,6 +13,12 @@ __pycache__/
# Intellij Idea # Intellij Idea
.idea .idea
# Eclipse
.project
.cproject
.pydevproject
.settings/
# Vim # Vim
*.swp *.swp
@ -130,3 +136,5 @@ sdkconfig.*
!sdkconfig.defaults !sdkconfig.defaults
.tests/ .tests/
/components

View file

@ -2,8 +2,8 @@
# See https://pre-commit.com for more information # See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks # See https://pre-commit.com/hooks.html for more hooks
repos: repos:
- repo: https://github.com/psf/black - repo: https://github.com/psf/black-pre-commit-mirror
rev: 23.3.0 rev: 23.9.1
hooks: hooks:
- id: black - id: black
args: args:
@ -27,7 +27,7 @@ repos:
- --branch=release - --branch=release
- --branch=beta - --branch=beta
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v3.4.0 rev: v3.10.1
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py39-plus] args: [--py39-plus]

View file

@ -11,15 +11,18 @@ esphome/*.py @esphome/core
esphome/core/* @esphome/core esphome/core/* @esphome/core
# Integrations # Integrations
esphome/components/a01nyub/* @MrSuicideParrot
esphome/components/absolute_humidity/* @DAVe3283 esphome/components/absolute_humidity/* @DAVe3283
esphome/components/ac_dimmer/* @glmnet esphome/components/ac_dimmer/* @glmnet
esphome/components/adc/* @esphome/core esphome/components/adc/* @esphome/core
esphome/components/adc128s102/* @DeerMaximum esphome/components/adc128s102/* @DeerMaximum
esphome/components/addressable_light/* @justfalter esphome/components/addressable_light/* @justfalter
esphome/components/airthings_ble/* @jeromelaban esphome/components/airthings_ble/* @jeromelaban
esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau
esphome/components/airthings_wave_mini/* @ncareau esphome/components/airthings_wave_mini/* @ncareau
esphome/components/airthings_wave_plus/* @jeromelaban esphome/components/airthings_wave_plus/* @jeromelaban
esphome/components/alarm_control_panel/* @grahambrown11 esphome/components/alarm_control_panel/* @grahambrown11
esphome/components/alpha3/* @jan-hofmeier
esphome/components/am43/* @buxtronix esphome/components/am43/* @buxtronix
esphome/components/am43/cover/* @buxtronix esphome/components/am43/cover/* @buxtronix
esphome/components/am43/sensor/* @buxtronix esphome/components/am43/sensor/* @buxtronix
@ -30,6 +33,7 @@ esphome/components/api/* @OttoWinter
esphome/components/as7341/* @mrgnr esphome/components/as7341/* @mrgnr
esphome/components/async_tcp/* @OttoWinter esphome/components/async_tcp/* @OttoWinter
esphome/components/atc_mithermometer/* @ahpohl esphome/components/atc_mithermometer/* @ahpohl
esphome/components/atm90e26/* @danieltwagner
esphome/components/b_parasite/* @rbaron esphome/components/b_parasite/* @rbaron
esphome/components/ballu/* @bazuchan esphome/components/ballu/* @bazuchan
esphome/components/bang_bang/* @OttoWinter esphome/components/bang_bang/* @OttoWinter
@ -38,18 +42,21 @@ esphome/components/bedjet/climate/* @jhansche
esphome/components/bedjet/fan/* @jhansche esphome/components/bedjet/fan/* @jhansche
esphome/components/bh1750/* @OttoWinter esphome/components/bh1750/* @OttoWinter
esphome/components/binary_sensor/* @esphome/core esphome/components/binary_sensor/* @esphome/core
esphome/components/bk72xx/* @kuba2k2
esphome/components/bl0939/* @ziceva esphome/components/bl0939/* @ziceva
esphome/components/bl0940/* @tobias- esphome/components/bl0940/* @tobias-
esphome/components/bl0942/* @dbuezas esphome/components/bl0942/* @dbuezas
esphome/components/ble_client/* @buxtronix esphome/components/ble_client/* @buxtronix
esphome/components/bluetooth_proxy/* @jesserockz esphome/components/bluetooth_proxy/* @jesserockz
esphome/components/bme680_bsec/* @trvrnrth esphome/components/bme680_bsec/* @trvrnrth
esphome/components/bmi160/* @flaviut
esphome/components/bmp3xx/* @martgras esphome/components/bmp3xx/* @martgras
esphome/components/bmp581/* @kahrendt
esphome/components/bp1658cj/* @Cossid esphome/components/bp1658cj/* @Cossid
esphome/components/bp5758d/* @Cossid esphome/components/bp5758d/* @Cossid
esphome/components/button/* @esphome/core esphome/components/button/* @esphome/core
esphome/components/canbus/* @danielschramm @mvturnho esphome/components/canbus/* @danielschramm @mvturnho
esphome/components/cap1188/* @MrEditor97 esphome/components/cap1188/* @mreditor97
esphome/components/captive_portal/* @OttoWinter esphome/components/captive_portal/* @OttoWinter
esphome/components/ccs811/* @habbie esphome/components/ccs811/* @habbie
esphome/components/cd74hc4067/* @asoehlke esphome/components/cd74hc4067/* @asoehlke
@ -75,13 +82,14 @@ esphome/components/display_menu_base/* @numo68
esphome/components/dps310/* @kbx81 esphome/components/dps310/* @kbx81
esphome/components/ds1307/* @badbadc0ffee esphome/components/ds1307/* @badbadc0ffee
esphome/components/dsmr/* @glmnet @zuidwijk esphome/components/dsmr/* @glmnet @zuidwijk
esphome/components/duty_time/* @dudanov
esphome/components/ee895/* @Stock-M esphome/components/ee895/* @Stock-M
esphome/components/ektf2232/* @jesserockz esphome/components/ektf2232/* @jesserockz
esphome/components/ens210/* @itn3rd77 esphome/components/ens210/* @itn3rd77
esphome/components/esp32/* @esphome/core esphome/components/esp32/* @esphome/core
esphome/components/esp32_ble/* @jesserockz esphome/components/esp32_ble/* @jesserockz
esphome/components/esp32_ble_client/* @jesserockz esphome/components/esp32_ble_client/* @jesserockz
esphome/components/esp32_ble_server/* @jesserockz esphome/components/esp32_ble_server/* @clydebarrow @jesserockz
esphome/components/esp32_camera_web_server/* @ayufan esphome/components/esp32_camera_web_server/* @ayufan
esphome/components/esp32_can/* @Sympatron esphome/components/esp32_can/* @Sympatron
esphome/components/esp32_improv/* @jesserockz esphome/components/esp32_improv/* @jesserockz
@ -96,11 +104,13 @@ esphome/components/fastled_base/* @OttoWinter
esphome/components/feedback/* @ianchi esphome/components/feedback/* @ianchi
esphome/components/fingerprint_grow/* @OnFreund @loongyh esphome/components/fingerprint_grow/* @OnFreund @loongyh
esphome/components/fs3000/* @kahrendt esphome/components/fs3000/* @kahrendt
esphome/components/gcja5/* @gcormier
esphome/components/globals/* @esphome/core esphome/components/globals/* @esphome/core
esphome/components/gp8403/* @jesserockz esphome/components/gp8403/* @jesserockz
esphome/components/gpio/* @esphome/core esphome/components/gpio/* @esphome/core
esphome/components/gps/* @coogle esphome/components/gps/* @coogle
esphome/components/graph/* @synco esphome/components/graph/* @synco
esphome/components/grove_tb6612fng/* @max246
esphome/components/growatt_solar/* @leeuwte esphome/components/growatt_solar/* @leeuwte
esphome/components/haier/* @paveldn esphome/components/haier/* @paveldn
esphome/components/havells_solar/* @sourabhjaiswal esphome/components/havells_solar/* @sourabhjaiswal
@ -124,7 +134,7 @@ esphome/components/i2s_audio/speaker/* @jesserockz
esphome/components/ili9xxx/* @nielsnl68 esphome/components/ili9xxx/* @nielsnl68
esphome/components/improv_base/* @esphome/core esphome/components/improv_base/* @esphome/core
esphome/components/improv_serial/* @esphome/core esphome/components/improv_serial/* @esphome/core
esphome/components/ina260/* @MrEditor97 esphome/components/ina260/* @mreditor97
esphome/components/inkbird_ibsth1_mini/* @fkirill esphome/components/inkbird_ibsth1_mini/* @fkirill
esphome/components/inkplate6/* @jesserockz esphome/components/inkplate6/* @jesserockz
esphome/components/integration/* @OttoWinter esphome/components/integration/* @OttoWinter
@ -136,9 +146,12 @@ esphome/components/key_collector/* @ssieb
esphome/components/key_provider/* @ssieb esphome/components/key_provider/* @ssieb
esphome/components/kuntze/* @ssieb esphome/components/kuntze/* @ssieb
esphome/components/lcd_menu/* @numo68 esphome/components/lcd_menu/* @numo68
esphome/components/ld2410/* @sebcaps esphome/components/ld2410/* @regevbr @sebcaps
esphome/components/ledc/* @OttoWinter esphome/components/ledc/* @OttoWinter
esphome/components/libretiny/* @kuba2k2
esphome/components/libretiny_pwm/* @kuba2k2
esphome/components/light/* @esphome/core esphome/components/light/* @esphome/core
esphome/components/lightwaverf/* @max246
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
esphome/components/lock/* @esphome/core esphome/components/lock/* @esphome/core
esphome/components/logger/* @esphome/core esphome/components/logger/* @esphome/core
@ -160,7 +173,7 @@ esphome/components/mcp2515/* @danielschramm @mvturnho
esphome/components/mcp3204/* @rsumner esphome/components/mcp3204/* @rsumner
esphome/components/mcp4728/* @berfenger esphome/components/mcp4728/* @berfenger
esphome/components/mcp47a1/* @jesserockz esphome/components/mcp47a1/* @jesserockz
esphome/components/mcp9600/* @MrEditor97 esphome/components/mcp9600/* @mreditor97
esphome/components/mcp9808/* @k7hpn esphome/components/mcp9808/* @k7hpn
esphome/components/md5/* @esphome/core esphome/components/md5/* @esphome/core
esphome/components/mdns/* @esphome/core esphome/components/mdns/* @esphome/core
@ -199,10 +212,12 @@ esphome/components/output/* @esphome/core
esphome/components/pca6416a/* @Mat931 esphome/components/pca6416a/* @Mat931
esphome/components/pca9554/* @hwstar esphome/components/pca9554/* @hwstar
esphome/components/pcf85063/* @brogon esphome/components/pcf85063/* @brogon
esphome/components/pcf8563/* @KoenBreeman
esphome/components/pid/* @OttoWinter esphome/components/pid/* @OttoWinter
esphome/components/pipsolar/* @andreashergert1984 esphome/components/pipsolar/* @andreashergert1984
esphome/components/pm1006/* @habbie esphome/components/pm1006/* @habbie
esphome/components/pmsa003i/* @sjtrny esphome/components/pmsa003i/* @sjtrny
esphome/components/pmwcs3/* @SeByDocKy
esphome/components/pn532/* @OttoWinter @jesserockz 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
@ -224,6 +239,7 @@ esphome/components/rgbct/* @jesserockz
esphome/components/rp2040/* @jesserockz esphome/components/rp2040/* @jesserockz
esphome/components/rp2040_pio_led_strip/* @Papa-DMan esphome/components/rp2040_pio_led_strip/* @Papa-DMan
esphome/components/rp2040_pwm/* @jesserockz esphome/components/rp2040_pwm/* @jesserockz
esphome/components/rtl87xx/* @kuba2k2
esphome/components/rtttl/* @glmnet esphome/components/rtttl/* @glmnet
esphome/components/safe_mode/* @jsuanet @paulmonigatti esphome/components/safe_mode/* @jsuanet @paulmonigatti
esphome/components/scd4x/* @martgras @sjtrny esphome/components/scd4x/* @martgras @sjtrny
@ -232,6 +248,7 @@ esphome/components/sdm_meter/* @jesserockz @polyfaces
esphome/components/sdp3x/* @Azimath esphome/components/sdp3x/* @Azimath
esphome/components/selec_meter/* @sourabhjaiswal esphome/components/selec_meter/* @sourabhjaiswal
esphome/components/select/* @esphome/core esphome/components/select/* @esphome/core
esphome/components/sen0321/* @notjj
esphome/components/sen21231/* @shreyaskarnik esphome/components/sen21231/* @shreyaskarnik
esphome/components/sen5x/* @martgras esphome/components/sen5x/* @martgras
esphome/components/sensirion_common/* @martgras esphome/components/sensirion_common/* @martgras
@ -254,6 +271,8 @@ esphome/components/socket/* @esphome/core
esphome/components/sonoff_d1/* @anatoly-savchenkov esphome/components/sonoff_d1/* @anatoly-savchenkov
esphome/components/speaker/* @jesserockz esphome/components/speaker/* @jesserockz
esphome/components/spi/* @esphome/core esphome/components/spi/* @esphome/core
esphome/components/spi_device/* @clydebarrow
esphome/components/spi_led_strip/* @clydebarrow
esphome/components/sprinkler/* @kbx81 esphome/components/sprinkler/* @kbx81
esphome/components/sps30/* @martgras esphome/components/sps30/* @martgras
esphome/components/ssd1322_base/* @kbx81 esphome/components/ssd1322_base/* @kbx81
@ -293,6 +312,7 @@ esphome/components/tof10120/* @wstrzalka
esphome/components/toshiba/* @kbx81 esphome/components/toshiba/* @kbx81
esphome/components/touchscreen/* @jesserockz esphome/components/touchscreen/* @jesserockz
esphome/components/tsl2591/* @wjcarpenter esphome/components/tsl2591/* @wjcarpenter
esphome/components/tt21100/* @kroimon
esphome/components/tuya/binary_sensor/* @jesserockz esphome/components/tuya/binary_sensor/* @jesserockz
esphome/components/tuya/climate/* @jesserockz esphome/components/tuya/climate/* @jesserockz
esphome/components/tuya/number/* @frankiboy1 esphome/components/tuya/number/* @frankiboy1
@ -309,13 +329,17 @@ esphome/components/version/* @esphome/core
esphome/components/voice_assistant/* @jesserockz esphome/components/voice_assistant/* @jesserockz
esphome/components/wake_on_lan/* @willwill2will54 esphome/components/wake_on_lan/* @willwill2will54
esphome/components/web_server_base/* @OttoWinter esphome/components/web_server_base/* @OttoWinter
esphome/components/web_server_idf/* @dentra
esphome/components/whirlpool/* @glmnet esphome/components/whirlpool/* @glmnet
esphome/components/whynter/* @aeonsablaze esphome/components/whynter/* @aeonsablaze
esphome/components/wiegand/* @ssieb esphome/components/wiegand/* @ssieb
esphome/components/wireguard/* @droscy @lhoracek @thomas0bernard
esphome/components/wl_134/* @hobbypunk90 esphome/components/wl_134/* @hobbypunk90
esphome/components/x9c/* @EtienneMD esphome/components/x9c/* @EtienneMD
esphome/components/xiaomi_lywsd03mmc/* @ahpohl esphome/components/xiaomi_lywsd03mmc/* @ahpohl
esphome/components/xiaomi_mhoc303/* @drug123 esphome/components/xiaomi_mhoc303/* @drug123
esphome/components/xiaomi_mhoc401/* @vevsvevs esphome/components/xiaomi_mhoc401/* @vevsvevs
esphome/components/xiaomi_rtcgq02lm/* @jesserockz esphome/components/xiaomi_rtcgq02lm/* @jesserockz
esphome/components/xl9535/* @mreditor97
esphome/components/xpt2046/* @nielsnl68 @numo68 esphome/components/xpt2046/* @nielsnl68 @numo68
esphome/components/zio_ultrasonic/* @kahrendt

View file

@ -22,16 +22,24 @@ RUN \
python3=3.9.2-3 \ python3=3.9.2-3 \
python3-pip=20.3.4-4+deb11u1 \ python3-pip=20.3.4-4+deb11u1 \
python3-setuptools=52.0.0-4 \ python3-setuptools=52.0.0-4 \
python3-pil=8.1.2+dfsg-0.3+deb11u1 \
python3-cryptography=3.3.2-1 \ python3-cryptography=3.3.2-1 \
python3-venv=3.9.2-3 \ python3-venv=3.9.2-3 \
iputils-ping=3:20210202-1 \ iputils-ping=3:20210202-1 \
git=1:2.30.2-1+deb11u2 \ git=1:2.30.2-1+deb11u2 \
curl=7.74.0-1.3+deb11u7 \ curl=7.74.0-1.3+deb11u7 \
openssh-client=1:8.4p1-5+deb11u1 \ openssh-client=1:8.4p1-5+deb11u1 \
libcairo2=1.16.0-5 \
python3-cffi=1.14.5-1 \ python3-cffi=1.14.5-1 \
&& rm -rf \ libcairo2=1.16.0-5 \
patch=2.7.6-7; \
if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
apt-get install -y --no-install-recommends \
build-essential=12.9 \
python3-dev=3.9.2-3 \
zlib1g-dev=1:1.2.11.dfsg-2+deb11u2 \
libjpeg-dev=1:2.0.6-4 \
libfreetype-dev=2.10.4+dfsg-1+deb11u1; \
fi; \
rm -rf \
/tmp/* \ /tmp/* \
/var/{cache,log}/* \ /var/{cache,log}/* \
/var/lib/apt/lists/* /var/lib/apt/lists/*
@ -54,7 +62,7 @@ RUN \
# Ubuntu python3-pip is missing wheel # Ubuntu python3-pip is missing wheel
pip3 install --no-cache-dir \ pip3 install --no-cache-dir \
wheel==0.37.1 \ wheel==0.37.1 \
platformio==6.1.7 \ platformio==6.1.11 \
# Change some platformio settings # Change some platformio settings
&& platformio settings set enable_telemetry No \ && platformio settings set enable_telemetry No \
&& platformio settings set check_platformio_interval 1000000 \ && platformio settings set check_platformio_interval 1000000 \

View file

@ -35,11 +35,16 @@ if bashio::config.has_value 'default_compile_process_limit'; then
export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=$(bashio::config 'default_compile_process_limit') export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=$(bashio::config 'default_compile_process_limit')
else else
if grep -q 'Raspberry Pi 3' /proc/cpuinfo; then if grep -q 'Raspberry Pi 3' /proc/cpuinfo; then
export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=1; export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=1
fi fi
fi fi
mkdir -p "${pio_cache_base}" mkdir -p "${pio_cache_base}"
if bashio::fs.directory_exists '/config/esphome/.esphome'; then
bashio::log.info "Removing old .esphome directory..."
rm -rf /config/esphome/.esphome
fi
bashio::log.info "Starting ESPHome dashboard..." bashio::log.info "Starting ESPHome dashboard..."
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --ha-addon exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --ha-addon

View file

@ -26,13 +26,15 @@ from esphome.const import (
CONF_ESPHOME, CONF_ESPHOME,
CONF_PLATFORMIO_OPTIONS, CONF_PLATFORMIO_OPTIONS,
CONF_SUBSTITUTIONS, CONF_SUBSTITUTIONS,
PLATFORM_BK72XX,
PLATFORM_RTL87XX,
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_ESP8266, PLATFORM_ESP8266,
PLATFORM_RP2040, PLATFORM_RP2040,
SECRETS_FILES, SECRETS_FILES,
) )
from esphome.core import CORE, EsphomeError, coroutine from esphome.core import CORE, EsphomeError, coroutine
from esphome.helpers import indent from esphome.helpers import indent, is_ip_address
from esphome.util import ( from esphome.util import (
run_external_command, run_external_command,
run_external_process, run_external_process,
@ -83,6 +85,8 @@ def choose_upload_log_host(
options = [] options = []
for port in get_serial_ports(): for port in get_serial_ports():
options.append((f"{port.path} ({port.description})", port.path)) options.append((f"{port.path} ({port.description})", port.path))
if default == "SERIAL":
return choose_prompt(options, purpose=purpose)
if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config): if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config):
options.append((f"Over The Air ({CORE.address})", CORE.address)) options.append((f"Over The Air ({CORE.address})", CORE.address))
if default == "OTA": if default == "OTA":
@ -216,14 +220,16 @@ def compile_program(args, config):
return 0 if idedata is not None else 1 return 0 if idedata is not None else 1
def upload_using_esptool(config, port): def upload_using_esptool(config, port, file):
from esphome import platformio_api 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): if file is not None:
flash_images = [platformio_api.FlashImage(path=file, offset="0x0")]
else:
idedata = platformio_api.get_idedata(config) idedata = platformio_api.get_idedata(config)
firmware_offset = "0x10000" if CORE.is_esp32 else "0x0" firmware_offset = "0x10000" if CORE.is_esp32 else "0x0"
@ -240,6 +246,7 @@ def upload_using_esptool(config, port):
mcu = get_esp32_variant().lower() mcu = get_esp32_variant().lower()
def run_esptool(baud_rate):
cmd = [ cmd = [
"esptool.py", "esptool.py",
"--before", "--before",
@ -278,20 +285,26 @@ def upload_using_esptool(config, port):
return run_esptool(115200) return run_esptool(115200)
def upload_using_platformio(config, port):
from esphome import platformio_api
upload_args = ["-t", "upload", "-t", "nobuild"]
if port is not None:
upload_args += ["--upload-port", port]
return platformio_api.run_platformio_cli_run(config, CORE.verbose, *upload_args)
def upload_program(config, args, host): def upload_program(config, args, host):
if get_port_type(host) == "SERIAL": if get_port_type(host) == "SERIAL":
if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266): if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
return upload_using_esptool(config, host) file = getattr(args, "file", None)
return upload_using_esptool(config, host, file)
if CORE.target_platform in (PLATFORM_RP2040): if CORE.target_platform in (PLATFORM_RP2040):
from esphome import platformio_api return upload_using_platformio(config, args.device)
upload_args = ["-t", "upload"] if CORE.target_platform in (PLATFORM_BK72XX, PLATFORM_RTL87XX):
if args.device is not None: return upload_using_platformio(config, host)
upload_args += ["--upload-port", args.device]
return platformio_api.run_platformio_cli_run(
config, CORE.verbose, *upload_args
)
return 1 # Unknown target platform return 1 # Unknown target platform
@ -308,8 +321,10 @@ def upload_program(config, args, host):
password = ota_conf.get(CONF_PASSWORD, "") password = ota_conf.get(CONF_PASSWORD, "")
if ( if (
get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED] not is_ip_address(CORE.address)
) and CONF_MQTT in config: and (get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED])
and CONF_MQTT in config
):
from esphome import mqtt from esphome import mqtt
host = mqtt.get_esphome_device_ip( host = mqtt.get_esphome_device_ip(
@ -363,10 +378,16 @@ def command_wizard(args):
def command_config(args, config): def command_config(args, config):
_LOGGER.info("Configuration is valid!")
if not CORE.verbose: if not CORE.verbose:
config = strip_default_ids(config) config = strip_default_ids(config)
safe_print(yaml_util.dump(config, args.show_secrets)) output = yaml_util.dump(config, args.show_secrets)
# add the console decoration so the front-end can hide the secrets
if not args.show_secrets:
output = re.sub(
r"(password|key|psk|ssid)\: (.+)", r"\1: \\033[5m\2\\033[6m", output
)
safe_print(output)
_LOGGER.info("Configuration is valid!")
return 0 return 0

View file

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

View file

@ -0,0 +1,57 @@
// Datasheet https://wiki.dfrobot.com/A01NYUB%20Waterproof%20Ultrasonic%20Sensor%20SKU:%20SEN0313
#include "a01nyub.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace a01nyub {
static const char *const TAG = "a01nyub.sensor";
static const uint8_t MAX_DATA_LENGTH_BYTES = 4;
void A01nyubComponent::loop() {
uint8_t data;
while (this->available() > 0) {
if (this->read_byte(&data)) {
buffer_.push_back(data);
this->check_buffer_();
}
}
}
void A01nyubComponent::check_buffer_() {
if (this->buffer_.size() >= MAX_DATA_LENGTH_BYTES) {
size_t i;
for (i = 0; i < this->buffer_.size(); i++) {
// Look for the first packet
if (this->buffer_[i] == 0xFF) {
if (i + 1 + 3 < this->buffer_.size()) { // Packet is not complete
return; // Wait for completion
}
uint8_t checksum = (this->buffer_[i] + this->buffer_[i + 1] + this->buffer_[i + 2]) & 0xFF;
if (this->buffer_[i + 3] == checksum) {
float distance = (this->buffer_[i + 1] << 8) + this->buffer_[i + 2];
if (distance > 280) {
float meters = distance / 1000.0;
ESP_LOGV(TAG, "Distance from sensor: %f mm, %f m", distance, meters);
this->publish_state(meters);
} else {
ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str());
}
}
break;
}
}
this->buffer_.clear();
}
}
void A01nyubComponent::dump_config() {
ESP_LOGCONFIG(TAG, "A01nyub Sensor:");
LOG_SENSOR(" ", "Distance", this);
}
} // namespace a01nyub
} // namespace esphome

View file

@ -0,0 +1,27 @@
#pragma once
#include <vector>
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h"
namespace esphome {
namespace a01nyub {
class A01nyubComponent : public sensor::Sensor, public Component, public uart::UARTDevice {
public:
// Nothing really public.
// ========== INTERNAL METHODS ==========
void loop() override;
void dump_config() override;
protected:
void check_buffer_();
std::vector<uint8_t> buffer_;
};
} // namespace a01nyub
} // namespace esphome

View file

@ -0,0 +1,41 @@
import esphome.codegen as cg
from esphome.components import sensor, uart
from esphome.const import (
STATE_CLASS_MEASUREMENT,
UNIT_METER,
ICON_ARROW_EXPAND_VERTICAL,
DEVICE_CLASS_DISTANCE,
)
CODEOWNERS = ["@MrSuicideParrot"]
DEPENDENCIES = ["uart"]
a01nyub_ns = cg.esphome_ns.namespace("a01nyub")
A01nyubComponent = a01nyub_ns.class_(
"A01nyubComponent", sensor.Sensor, cg.Component, uart.UARTDevice
)
CONFIG_SCHEMA = sensor.sensor_schema(
A01nyubComponent,
unit_of_measurement=UNIT_METER,
icon=ICON_ARROW_EXPAND_VERTICAL,
accuracy_decimals=3,
state_class=STATE_CLASS_MEASUREMENT,
device_class=DEVICE_CLASS_DISTANCE,
).extend(uart.UART_DEVICE_SCHEMA)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"a01nyub",
baud_rate=9600,
require_tx=False,
require_rx=True,
data_bits=8,
parity=None,
stop_bits=1,
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
await uart.register_uart_device(var, config)

View file

@ -28,6 +28,6 @@ async def to_code(config):
dir_pin = await cg.gpio_pin_expression(config[CONF_DIR_PIN]) dir_pin = await cg.gpio_pin_expression(config[CONF_DIR_PIN])
cg.add(var.set_dir_pin(dir_pin)) cg.add(var.set_dir_pin(dir_pin))
if CONF_SLEEP_PIN in config: if sleep_pin_config := config.get(CONF_SLEEP_PIN):
sleep_pin = await cg.gpio_pin_expression(config[CONF_SLEEP_PIN]) sleep_pin = await cg.gpio_pin_expression(sleep_pin_config)
cg.add(var.set_sleep_pin(sleep_pin)) cg.add(var.set_sleep_pin(sleep_pin))

View file

@ -1,13 +1,16 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import pins from esphome import pins
from esphome.const import CONF_INPUT from esphome.const import CONF_ANALOG, CONF_INPUT
from esphome.core import CORE from esphome.core import CORE
from esphome.components.esp32 import get_esp32_variant from esphome.components.esp32 import get_esp32_variant
from esphome.const import PLATFORM_ESP8266
from esphome.components.esp32.const import ( from esphome.components.esp32.const import (
VARIANT_ESP32, VARIANT_ESP32,
VARIANT_ESP32C2,
VARIANT_ESP32C3, VARIANT_ESP32C3,
VARIANT_ESP32C6,
VARIANT_ESP32H2, VARIANT_ESP32H2,
VARIANT_ESP32S2, VARIANT_ESP32S2,
VARIANT_ESP32S3, VARIANT_ESP32S3,
@ -24,6 +27,7 @@ ATTENUATION_MODES = {
} }
adc1_channel_t = cg.global_ns.enum("adc1_channel_t") adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
adc2_channel_t = cg.global_ns.enum("adc2_channel_t")
# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h # From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
# pin to adc1 channel mapping # pin to adc1 channel mapping
@ -69,6 +73,22 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
3: adc1_channel_t.ADC1_CHANNEL_3, 3: adc1_channel_t.ADC1_CHANNEL_3,
4: adc1_channel_t.ADC1_CHANNEL_4, 4: adc1_channel_t.ADC1_CHANNEL_4,
}, },
VARIANT_ESP32C2: {
0: adc1_channel_t.ADC1_CHANNEL_0,
1: adc1_channel_t.ADC1_CHANNEL_1,
2: adc1_channel_t.ADC1_CHANNEL_2,
3: adc1_channel_t.ADC1_CHANNEL_3,
4: adc1_channel_t.ADC1_CHANNEL_4,
},
VARIANT_ESP32C6: {
0: adc1_channel_t.ADC1_CHANNEL_0,
1: adc1_channel_t.ADC1_CHANNEL_1,
2: adc1_channel_t.ADC1_CHANNEL_2,
3: adc1_channel_t.ADC1_CHANNEL_3,
4: adc1_channel_t.ADC1_CHANNEL_4,
5: adc1_channel_t.ADC1_CHANNEL_5,
6: adc1_channel_t.ADC1_CHANNEL_6,
},
VARIANT_ESP32H2: { VARIANT_ESP32H2: {
0: adc1_channel_t.ADC1_CHANNEL_0, 0: adc1_channel_t.ADC1_CHANNEL_0,
1: adc1_channel_t.ADC1_CHANNEL_1, 1: adc1_channel_t.ADC1_CHANNEL_1,
@ -78,10 +98,55 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
}, },
} }
ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
# TODO: add other variants
VARIANT_ESP32: {
4: adc2_channel_t.ADC2_CHANNEL_0,
0: adc2_channel_t.ADC2_CHANNEL_1,
2: adc2_channel_t.ADC2_CHANNEL_2,
15: adc2_channel_t.ADC2_CHANNEL_3,
13: adc2_channel_t.ADC2_CHANNEL_4,
12: adc2_channel_t.ADC2_CHANNEL_5,
14: adc2_channel_t.ADC2_CHANNEL_6,
27: adc2_channel_t.ADC2_CHANNEL_7,
25: adc2_channel_t.ADC2_CHANNEL_8,
26: adc2_channel_t.ADC2_CHANNEL_9,
},
VARIANT_ESP32S2: {
11: adc2_channel_t.ADC2_CHANNEL_0,
12: adc2_channel_t.ADC2_CHANNEL_1,
13: adc2_channel_t.ADC2_CHANNEL_2,
14: adc2_channel_t.ADC2_CHANNEL_3,
15: adc2_channel_t.ADC2_CHANNEL_4,
16: adc2_channel_t.ADC2_CHANNEL_5,
17: adc2_channel_t.ADC2_CHANNEL_6,
18: adc2_channel_t.ADC2_CHANNEL_7,
19: adc2_channel_t.ADC2_CHANNEL_8,
20: adc2_channel_t.ADC2_CHANNEL_9,
},
VARIANT_ESP32S3: {
11: adc2_channel_t.ADC2_CHANNEL_0,
12: adc2_channel_t.ADC2_CHANNEL_1,
13: adc2_channel_t.ADC2_CHANNEL_2,
14: adc2_channel_t.ADC2_CHANNEL_3,
15: adc2_channel_t.ADC2_CHANNEL_4,
16: adc2_channel_t.ADC2_CHANNEL_5,
17: adc2_channel_t.ADC2_CHANNEL_6,
18: adc2_channel_t.ADC2_CHANNEL_7,
19: adc2_channel_t.ADC2_CHANNEL_8,
20: adc2_channel_t.ADC2_CHANNEL_9,
},
VARIANT_ESP32C3: {
5: adc2_channel_t.ADC2_CHANNEL_0,
},
}
def validate_adc_pin(value): def validate_adc_pin(value):
if str(value).upper() == "VCC": if str(value).upper() == "VCC":
return cv.only_on_esp8266("VCC") if CORE.is_rp2040:
return pins.internal_gpio_input_pin_schema(29)
return cv.only_on([PLATFORM_ESP8266])("VCC")
if str(value).upper() == "TEMPERATURE": if str(value).upper() == "TEMPERATURE":
return cv.only_on_rp2040("TEMPERATURE") return cv.only_on_rp2040("TEMPERATURE")
@ -89,22 +154,27 @@ def validate_adc_pin(value):
if CORE.is_esp32: if CORE.is_esp32:
value = pins.internal_gpio_input_pin_number(value) value = pins.internal_gpio_input_pin_number(value)
variant = get_esp32_variant() variant = get_esp32_variant()
if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL: if (
variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL
and variant not in ESP32_VARIANT_ADC2_PIN_TO_CHANNEL
):
raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported") raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported")
if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]: if (
value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]
and value not in ESP32_VARIANT_ADC2_PIN_TO_CHANNEL[variant]
):
raise cv.Invalid(f"{variant} doesn't support ADC on this pin") raise cv.Invalid(f"{variant} doesn't support ADC on this pin")
return pins.internal_gpio_input_pin_schema(value) return pins.internal_gpio_input_pin_schema(value)
if CORE.is_esp8266: if CORE.is_esp8266:
from esphome.components.esp8266.gpio import CONF_ANALOG
value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})( value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})(
value value
) )
if value != 17: # A0 if value != 17: # A0
raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.") raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC")
return pins.gpio_pin_schema( return pins.gpio_pin_schema(
{CONF_ANALOG: True, CONF_INPUT: True}, internal=True {CONF_ANALOG: True, CONF_INPUT: True}, internal=True
)(value) )(value)
@ -112,7 +182,12 @@ def validate_adc_pin(value):
if CORE.is_rp2040: if CORE.is_rp2040:
value = pins.internal_gpio_input_pin_number(value) value = pins.internal_gpio_input_pin_number(value)
if value not in (26, 27, 28, 29): if value not in (26, 27, 28, 29):
raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC.") raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC")
return pins.internal_gpio_input_pin_schema(value) return pins.internal_gpio_input_pin_schema(value)
if CORE.is_libretiny:
return pins.gpio_pin_schema(
{CONF_ANALOG: True, CONF_INPUT: True}, internal=True
)(value)
raise NotImplementedError raise NotImplementedError

View file

@ -1,6 +1,6 @@
#include "adc_sensor.h" #include "adc_sensor.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#ifdef USE_ESP8266 #ifdef USE_ESP8266
#ifdef USE_ADC_SENSOR_VCC #ifdef USE_ADC_SENSOR_VCC
@ -12,6 +12,9 @@ ADC_MODE(ADC_VCC)
#endif #endif
#ifdef USE_RP2040 #ifdef USE_RP2040
#ifdef CYW43_USES_VSYS_PIN
#include "pico/cyw43_arch.h"
#endif
#include <hardware/adc.h> #include <hardware/adc.h>
#endif #endif
@ -20,15 +23,15 @@ namespace adc {
static const char *const TAG = "adc"; static const char *const TAG = "adc";
// 13bit for S2, and 12bit for all other esp32 variants // 13-bit for S2, 12-bit for all other ESP32 variants
#ifdef USE_ESP32 #ifdef USE_ESP32
static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_width_t>(ADC_WIDTH_MAX - 1); static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_width_t>(ADC_WIDTH_MAX - 1);
#ifndef SOC_ADC_RTC_MAX_BITWIDTH #ifndef SOC_ADC_RTC_MAX_BITWIDTH
#if USE_ESP32_VARIANT_ESP32S2 #if USE_ESP32_VARIANT_ESP32S2
static const int SOC_ADC_RTC_MAX_BITWIDTH = 13; static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 13;
#else #else
static const int SOC_ADC_RTC_MAX_BITWIDTH = 12; static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 12;
#endif #endif
#endif #endif
@ -47,14 +50,21 @@ extern "C"
#endif #endif
#ifdef USE_ESP32 #ifdef USE_ESP32
if (channel1_ != ADC1_CHANNEL_MAX) {
adc1_config_width(ADC_WIDTH_MAX_SOC_BITS); adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);
if (!autorange_) { if (!autorange_) {
adc1_config_channel_atten(channel_, attenuation_); adc1_config_channel_atten(channel1_, attenuation_);
}
} else if (channel2_ != ADC2_CHANNEL_MAX) {
if (!autorange_) {
adc2_config_channel_atten(channel2_, attenuation_);
}
} }
// load characteristics for each attenuation // load characteristics for each attenuation
for (int i = 0; i < (int) ADC_ATTEN_MAX; i++) { for (int32_t i = 0; i <= ADC_ATTEN_DB_11; i++) {
auto cal_value = esp_adc_cal_characterize(ADC_UNIT_1, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS, auto adc_unit = channel1_ != ADC1_CHANNEL_MAX ? ADC_UNIT_1 : ADC_UNIT_2;
auto cal_value = esp_adc_cal_characterize(adc_unit, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS,
1100, // default vref 1100, // default vref
&cal_characteristics_[i]); &cal_characteristics_[i]);
switch (cal_value) { switch (cal_value) {
@ -85,13 +95,13 @@ extern "C"
void ADCSensor::dump_config() { void ADCSensor::dump_config() {
LOG_SENSOR("", "ADC Sensor", this); LOG_SENSOR("", "ADC Sensor", this);
#ifdef USE_ESP8266 #if defined(USE_ESP8266) || defined(USE_LIBRETINY)
#ifdef USE_ADC_SENSOR_VCC #ifdef USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Pin: VCC"); ESP_LOGCONFIG(TAG, " Pin: VCC");
#else #else
LOG_PIN(" Pin: ", pin_); LOG_PIN(" Pin: ", pin_);
#endif #endif
#endif // USE_ESP8266 #endif // USE_ESP8266 || USE_LIBRETINY
#ifdef USE_ESP32 #ifdef USE_ESP32
LOG_PIN(" Pin: ", pin_); LOG_PIN(" Pin: ", pin_);
@ -116,13 +126,19 @@ void ADCSensor::dump_config() {
} }
} }
#endif // USE_ESP32 #endif // USE_ESP32
#ifdef USE_RP2040 #ifdef USE_RP2040
if (this->is_temperature_) { if (this->is_temperature_) {
ESP_LOGCONFIG(TAG, " Pin: Temperature"); ESP_LOGCONFIG(TAG, " Pin: Temperature");
} else { } else {
#ifdef USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Pin: VCC");
#else
LOG_PIN(" Pin: ", pin_); LOG_PIN(" Pin: ", pin_);
#endif // USE_ADC_SENSOR_VCC
} }
#endif #endif // USE_RP2040
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
} }
@ -136,9 +152,9 @@ void ADCSensor::update() {
#ifdef USE_ESP8266 #ifdef USE_ESP8266
float ADCSensor::sample() { float ADCSensor::sample() {
#ifdef USE_ADC_SENSOR_VCC #ifdef USE_ADC_SENSOR_VCC
int raw = ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance) int32_t raw = ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance)
#else #else
int raw = analogRead(this->pin_->get_pin()); // NOLINT int32_t raw = analogRead(this->pin_->get_pin()); // NOLINT
#endif #endif
if (output_raw_) { if (output_raw_) {
return raw; return raw;
@ -150,29 +166,53 @@ float ADCSensor::sample() {
#ifdef USE_ESP32 #ifdef USE_ESP32
float ADCSensor::sample() { float ADCSensor::sample() {
if (!autorange_) { if (!autorange_) {
int raw = adc1_get_raw(channel_); int raw = -1;
if (channel1_ != ADC1_CHANNEL_MAX) {
raw = adc1_get_raw(channel1_);
} else if (channel2_ != ADC2_CHANNEL_MAX) {
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw);
}
if (raw == -1) { if (raw == -1) {
return NAN; return NAN;
} }
if (output_raw_) { if (output_raw_) {
return raw; return raw;
} }
uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int) attenuation_]); uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int32_t) attenuation_]);
return mv / 1000.0f; return mv / 1000.0f;
} }
int raw11, raw6 = ADC_MAX, raw2 = ADC_MAX, raw0 = ADC_MAX; int raw11 = ADC_MAX, raw6 = ADC_MAX, raw2 = ADC_MAX, raw0 = ADC_MAX;
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_11);
raw11 = adc1_get_raw(channel_); if (channel1_ != ADC1_CHANNEL_MAX) {
adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_11);
raw11 = adc1_get_raw(channel1_);
if (raw11 < ADC_MAX) { if (raw11 < ADC_MAX) {
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_6); adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_6);
raw6 = adc1_get_raw(channel_); raw6 = adc1_get_raw(channel1_);
if (raw6 < ADC_MAX) { if (raw6 < ADC_MAX) {
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_2_5); adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_2_5);
raw2 = adc1_get_raw(channel_); raw2 = adc1_get_raw(channel1_);
if (raw2 < ADC_MAX) { if (raw2 < ADC_MAX) {
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_0); adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_0);
raw0 = adc1_get_raw(channel_); raw0 = adc1_get_raw(channel1_);
}
}
}
} else if (channel2_ != ADC2_CHANNEL_MAX) {
adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_11);
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw11);
if (raw11 < ADC_MAX) {
adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_6);
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw6);
if (raw6 < ADC_MAX) {
adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_2_5);
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw2);
if (raw2 < ADC_MAX) {
adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_0);
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw0);
}
} }
} }
} }
@ -181,10 +221,10 @@ float ADCSensor::sample() {
return NAN; return NAN;
} }
uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int) ADC_ATTEN_DB_11]); uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_11]);
uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int) ADC_ATTEN_DB_6]); uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_6]);
uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int) ADC_ATTEN_DB_2_5]); uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_2_5]);
uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int) ADC_ATTEN_DB_0]); uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_0]);
// Contribution of each value, in range 0-2048 (12 bit ADC) or 0-4096 (13 bit ADC) // Contribution of each value, in range 0-2048 (12 bit ADC) or 0-4096 (13 bit ADC)
uint32_t c11 = std::min(raw11, ADC_HALF); uint32_t c11 = std::min(raw11, ADC_HALF);
@ -206,23 +246,54 @@ float ADCSensor::sample() {
adc_set_temp_sensor_enabled(true); adc_set_temp_sensor_enabled(true);
delay(1); delay(1);
adc_select_input(4); adc_select_input(4);
} else {
uint8_t pin = this->pin_->get_pin();
adc_gpio_init(pin);
adc_select_input(pin - 26);
}
int raw = adc_read(); int32_t raw = adc_read();
if (this->is_temperature_) {
adc_set_temp_sensor_enabled(false); adc_set_temp_sensor_enabled(false);
} if (this->output_raw_) {
if (output_raw_) {
return raw; return raw;
} }
return raw * 3.3f / 4096.0f; return raw * 3.3f / 4096.0f;
} else {
uint8_t pin = this->pin_->get_pin();
#ifdef CYW43_USES_VSYS_PIN
if (pin == PICO_VSYS_PIN) {
// Measuring VSYS on Raspberry Pico W needs to be wrapped with
// `cyw43_thread_enter()`/`cyw43_thread_exit()` as discussed in
// https://github.com/raspberrypi/pico-sdk/issues/1222, since Wifi chip and
// VSYS ADC both share GPIO29
cyw43_thread_enter();
}
#endif // CYW43_USES_VSYS_PIN
adc_gpio_init(pin);
adc_select_input(pin - 26);
int32_t raw = adc_read();
#ifdef CYW43_USES_VSYS_PIN
if (pin == PICO_VSYS_PIN) {
cyw43_thread_exit();
}
#endif // CYW43_USES_VSYS_PIN
if (output_raw_) {
return raw;
}
float coeff = pin == PICO_VSYS_PIN ? 3.0 : 1.0;
return raw * 3.3f / 4096.0f * coeff;
}
} }
#endif #endif
#ifdef USE_LIBRETINY
float ADCSensor::sample() {
if (output_raw_) {
return analogRead(this->pin_->get_pin()); // NOLINT
}
return analogReadVoltage(this->pin_->get_pin()) / 1000.0f; // NOLINT
}
#endif // USE_LIBRETINY
#ifdef USE_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

@ -19,16 +19,23 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
#ifdef USE_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) { attenuation_ = attenuation; } void set_attenuation(adc_atten_t attenuation) { attenuation_ = attenuation; }
void set_channel(adc1_channel_t channel) { channel_ = channel; } void set_channel1(adc1_channel_t channel) {
channel1_ = channel;
channel2_ = ADC2_CHANNEL_MAX;
}
void set_channel2(adc2_channel_t channel) {
channel2_ = channel;
channel1_ = ADC1_CHANNEL_MAX;
}
void set_autorange(bool autorange) { autorange_ = autorange; } void set_autorange(bool autorange) { autorange_ = autorange; }
#endif #endif
/// Update adc values. /// Update ADC values
void update() override; void update() override;
/// Setup ADc /// Setup ADC
void setup() override; void setup() override;
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(InternalGPIOPin *pin) { this->pin_ = pin; } void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
void set_output_raw(bool output_raw) { output_raw_ = output_raw; } void set_output_raw(bool output_raw) { output_raw_ = output_raw; }
@ -52,9 +59,14 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
#ifdef USE_ESP32 #ifdef USE_ESP32
adc_atten_t attenuation_{ADC_ATTEN_DB_0}; adc_atten_t attenuation_{ADC_ATTEN_DB_0};
adc1_channel_t channel_{}; adc1_channel_t channel1_{ADC1_CHANNEL_MAX};
adc2_channel_t channel2_{ADC2_CHANNEL_MAX};
bool autorange_{false}; bool autorange_{false};
esp_adc_cal_characteristics_t cal_characteristics_[(int) ADC_ATTEN_MAX] = {}; #if ESP_IDF_VERSION_MAJOR >= 5
esp_adc_cal_characteristics_t cal_characteristics_[SOC_ADC_ATTEN_NUM] = {};
#else
esp_adc_cal_characteristics_t cal_characteristics_[ADC_ATTEN_MAX] = {};
#endif
#endif #endif
}; };

View file

@ -1,5 +1,7 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome.core import CORE
from esphome.components import sensor, voltage_sampler from esphome.components import sensor, voltage_sampler
from esphome.components.esp32 import get_esp32_variant from esphome.components.esp32 import get_esp32_variant
from esphome.const import ( from esphome.const import (
@ -8,15 +10,15 @@ from esphome.const import (
CONF_NUMBER, CONF_NUMBER,
CONF_PIN, CONF_PIN,
CONF_RAW, CONF_RAW,
CONF_WIFI,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_VOLT, UNIT_VOLT,
) )
from esphome.core import CORE
from . import ( from . import (
ATTENUATION_MODES, ATTENUATION_MODES,
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, ESP32_VARIANT_ADC1_PIN_TO_CHANNEL,
ESP32_VARIANT_ADC2_PIN_TO_CHANNEL,
validate_adc_pin, validate_adc_pin,
) )
@ -25,7 +27,23 @@ AUTO_LOAD = ["voltage_sampler"]
def validate_config(config): def validate_config(config):
if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto": if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto":
raise cv.Invalid("Automatic attenuation cannot be used when raw output is set.") raise cv.Invalid("Automatic attenuation cannot be used when raw output is set")
return config
def final_validate_config(config):
if CORE.is_esp32:
variant = get_esp32_variant()
if (
CONF_WIFI in fv.full_config.get()
and config[CONF_PIN][CONF_NUMBER]
in ESP32_VARIANT_ADC2_PIN_TO_CHANNEL[variant]
):
raise cv.Invalid(
f"{variant} doesn't support ADC on this pin when Wi-Fi is configured"
)
return config return config
@ -55,6 +73,8 @@ CONFIG_SCHEMA = cv.All(
validate_config, validate_config,
) )
FINAL_VALIDATE_SCHEMA = final_validate_config
async def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
@ -69,17 +89,26 @@ async def to_code(config):
pin = await cg.gpio_pin_expression(config[CONF_PIN]) pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(pin)) cg.add(var.set_pin(pin))
if CONF_RAW in config:
cg.add(var.set_output_raw(config[CONF_RAW])) cg.add(var.set_output_raw(config[CONF_RAW]))
if CONF_ATTENUATION in config: if attenuation := config.get(CONF_ATTENUATION):
if config[CONF_ATTENUATION] == "auto": if attenuation == "auto":
cg.add(var.set_autorange(cg.global_ns.true)) cg.add(var.set_autorange(cg.global_ns.true))
else: else:
cg.add(var.set_attenuation(config[CONF_ATTENUATION])) cg.add(var.set_attenuation(attenuation))
if CORE.is_esp32: if CORE.is_esp32:
variant = get_esp32_variant() variant = get_esp32_variant()
pin_num = config[CONF_PIN][CONF_NUMBER] pin_num = config[CONF_PIN][CONF_NUMBER]
if (
variant in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL
and pin_num in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]
):
chan = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num] chan = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num]
cg.add(var.set_channel(chan)) cg.add(var.set_channel1(chan))
elif (
variant in ESP32_VARIANT_ADC2_PIN_TO_CHANNEL
and pin_num in ESP32_VARIANT_ADC2_PIN_TO_CHANNEL[variant]
):
chan = ESP32_VARIANT_ADC2_PIN_TO_CHANNEL[variant][pin_num]
cg.add(var.set_channel2(chan))

View file

@ -48,16 +48,16 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await display.register_display(var, config) await display.register_display(var, config)
if CONF_PIXEL_MAPPER in config: if pixel_mapper := config.get(CONF_PIXEL_MAPPER):
pixel_mapper_template_ = await cg.process_lambda( pixel_mapper_template_ = await cg.process_lambda(
config[CONF_PIXEL_MAPPER], pixel_mapper,
[(int, "x"), (int, "y")], [(int, "x"), (int, "y")],
return_type=cg.int_, return_type=cg.int_,
) )
cg.add(var.set_pixel_mapper(pixel_mapper_template_)) cg.add(var.set_pixel_mapper(pixel_mapper_template_))
if CONF_LAMBDA in config: if lambda_config := config.get(CONF_LAMBDA):
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void lambda_config, [(display.DisplayRef, "it")], return_type=cg.void
) )
cg.add(var.set_writer(lambda_)) cg.add(var.set_writer(lambda_))

View file

@ -72,8 +72,8 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
if CONF_IRQ_PIN in config: if irq_pin_config := config.get(CONF_IRQ_PIN):
irq_pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN]) irq_pin = await cg.gpio_pin_expression(irq_pin_config)
cg.add(var.set_irq_pin(irq_pin)) cg.add(var.set_irq_pin(irq_pin))
for key in [ for key in [

View file

@ -45,10 +45,10 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config: if temperature := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) sens = await sensor.new_sensor(temperature)
cg.add(var.set_temperature_sensor(sens)) cg.add(var.set_temperature_sensor(sens))
if CONF_HUMIDITY in config: if humidity := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(config[CONF_HUMIDITY]) sens = await sensor.new_sensor(humidity)
cg.add(var.set_humidity_sensor(sens)) cg.add(var.set_humidity_sensor(sens))

View file

@ -1,5 +1,6 @@
#include "airthings_listener.h" #include "airthings_listener.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include <cinttypes>
#ifdef USE_ESP32 #ifdef USE_ESP32
@ -19,7 +20,7 @@ bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &devic
sn |= ((uint32_t) it.data[2] << 16); sn |= ((uint32_t) it.data[2] << 16);
sn |= ((uint32_t) it.data[3] << 24); sn |= ((uint32_t) it.data[3] << 24);
ESP_LOGD(TAG, "Found AirThings device Serial:%u (MAC: %s)", sn, device.address_str().c_str()); ESP_LOGD(TAG, "Found AirThings device Serial:%" PRIu32 " (MAC: %s)", sn, device.address_str().c_str());
return true; return true;
} }
} }

View file

@ -0,0 +1,103 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, ble_client
from esphome.const import (
CONF_BATTERY_VOLTAGE,
CONF_HUMIDITY,
CONF_PRESSURE,
CONF_TEMPERATURE,
CONF_TVOC,
DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
UNIT_PARTS_PER_BILLION,
UNIT_PERCENT,
UNIT_VOLT,
)
CODEOWNERS = ["@ncareau", "@jeromelaban", "@kpfleming"]
DEPENDENCIES = ["ble_client"]
CONF_BATTERY_UPDATE_INTERVAL = "battery_update_interval"
airthings_wave_base_ns = cg.esphome_ns.namespace("airthings_wave_base")
AirthingsWaveBase = airthings_wave_base_ns.class_(
"AirthingsWaveBase", cg.PollingComponent, ble_client.BLEClientNode
)
BASE_SCHEMA = (
sensor.SENSOR_SCHEMA.extend(
{
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement=UNIT_HECTOPASCAL,
accuracy_decimals=1,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TVOC): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_BILLION,
accuracy_decimals=0,
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(
CONF_BATTERY_UPDATE_INTERVAL,
default="24h",
): cv.update_interval,
}
)
.extend(cv.polling_component_schema("5min"))
.extend(ble_client.BLE_CLIENT_SCHEMA)
)
async def wave_base_to_code(var, config):
await cg.register_component(var, config)
await ble_client.register_ble_node(var, config)
if config_humidity := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(config_humidity)
cg.add(var.set_humidity(sens))
if config_temperature := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(config_temperature)
cg.add(var.set_temperature(sens))
if config_pressure := config.get(CONF_PRESSURE):
sens = await sensor.new_sensor(config_pressure)
cg.add(var.set_pressure(sens))
if config_tvoc := config.get(CONF_TVOC):
sens = await sensor.new_sensor(config_tvoc)
cg.add(var.set_tvoc(sens))
if config_battery_voltage := config.get(CONF_BATTERY_VOLTAGE):
sens = await sensor.new_sensor(config_battery_voltage)
cg.add(var.set_battery_voltage(sens))
if config_battery_update_interval := config.get(CONF_BATTERY_UPDATE_INTERVAL):
cg.add(var.set_battery_update_interval(config_battery_update_interval))

View file

@ -0,0 +1,211 @@
#include "airthings_wave_base.h"
// All information related to reading battery information came from the sensors.airthings_wave
// project by Sverre Hamre (https://github.com/sverrham/sensor.airthings_wave)
#ifdef USE_ESP32
namespace esphome {
namespace airthings_wave_base {
static const char *const TAG = "airthings_wave_base";
void AirthingsWaveBase::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: {
this->handle_ = 0;
this->acp_handle_ = 0;
this->cccd_handle_ = 0;
ESP_LOGW(TAG, "Disconnected!");
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
if (this->request_read_values_()) {
if (!this->read_battery_next_update_) {
this->node_state = espbt::ClientState::ESTABLISHED;
} else {
// delay setting node_state to ESTABLISHED until confirmation of the notify registration
this->request_battery_();
}
}
// ensure that the client will be disconnected even if no responses arrive
this->set_response_timeout_();
break;
}
case ESP_GATTC_READ_CHAR_EVT: {
if (param->read.conn_id != this->parent()->get_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_) {
this->read_sensors(param->read.value, param->read.value_len);
}
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
this->node_state = espbt::ClientState::ESTABLISHED;
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.conn_id != this->parent()->get_conn_id())
break;
if (param->notify.handle == this->acp_handle_) {
this->read_battery_(param->notify.value, param->notify.value_len);
}
break;
}
default:
break;
}
}
bool AirthingsWaveBase::is_valid_voc_value_(uint16_t voc) { return voc <= 16383; }
void AirthingsWaveBase::update() {
if (this->node_state != espbt::ClientState::ESTABLISHED) {
if (!this->parent()->enabled) {
ESP_LOGW(TAG, "Reconnecting to device");
this->parent()->set_enabled(true);
this->parent()->connect();
} else {
ESP_LOGW(TAG, "Connection in progress");
}
}
}
bool AirthingsWaveBase::request_read_values_() {
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->sensors_data_characteristic_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(),
this->sensors_data_characteristic_uuid_.to_string().c_str());
return false;
}
this->handle_ = chr->handle;
auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_,
ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
return false;
}
this->response_pending_();
return true;
}
bool AirthingsWaveBase::request_battery_() {
uint8_t battery_command = ACCESS_CONTROL_POINT_COMMAND;
uint8_t cccd_value[2] = {1, 0};
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->access_control_point_characteristic_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "No access control point characteristic found at service %s char %s",
this->service_uuid_.to_string().c_str(),
this->access_control_point_characteristic_uuid_.to_string().c_str());
return false;
}
auto *descr = this->parent()->get_descriptor(this->service_uuid_, this->access_control_point_characteristic_uuid_,
CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID);
if (descr == nullptr) {
ESP_LOGW(TAG, "No CCC descriptor found at service %s char %s", this->service_uuid_.to_string().c_str(),
this->access_control_point_characteristic_uuid_.to_string().c_str());
return false;
}
auto reg_status =
esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(), this->parent()->get_remote_bda(), chr->handle);
if (reg_status) {
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", reg_status);
return false;
}
this->acp_handle_ = chr->handle;
this->cccd_handle_ = descr->handle;
auto descr_status =
esp_ble_gattc_write_char_descr(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->cccd_handle_,
2, cccd_value, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
if (descr_status) {
ESP_LOGW(TAG, "Error sending CCC descriptor write request, status=%d", descr_status);
return false;
}
auto chr_status =
esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->acp_handle_, 1,
&battery_command, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
if (chr_status) {
ESP_LOGW(TAG, "Error sending read request for battery, status=%d", chr_status);
return false;
}
this->response_pending_();
return true;
}
void AirthingsWaveBase::read_battery_(uint8_t *raw_value, uint16_t value_len) {
auto *value = (AccessControlPointResponse *) (&raw_value[2]);
if ((value_len >= (sizeof(AccessControlPointResponse) + 2)) && (raw_value[0] == ACCESS_CONTROL_POINT_COMMAND)) {
ESP_LOGD(TAG, "Battery received: %u mV", (unsigned int) value->battery);
if (this->battery_voltage_ != nullptr) {
float voltage = value->battery / 1000.0f;
this->battery_voltage_->publish_state(voltage);
}
// read the battery again at the configured update interval
if (this->battery_update_interval_ != this->update_interval_) {
this->read_battery_next_update_ = false;
this->set_timeout("battery", this->battery_update_interval_,
[this]() { this->read_battery_next_update_ = true; });
}
}
this->response_received_();
}
void AirthingsWaveBase::response_pending_() {
this->responses_pending_++;
this->set_response_timeout_();
}
void AirthingsWaveBase::response_received_() {
if (--this->responses_pending_ == 0) {
// This instance must not stay connected
// so other clients can connect to it (e.g. the
// mobile app).
this->parent()->set_enabled(false);
}
}
void AirthingsWaveBase::set_response_timeout_() {
this->set_timeout("response_timeout", 30 * 1000, [this]() {
this->responses_pending_ = 1;
this->response_received_();
});
}
} // namespace airthings_wave_base
} // namespace esphome
#endif // USE_ESP32

View file

@ -0,0 +1,90 @@
#pragma once
// All information related to reading battery levels came from the sensors.airthings_wave
// project by Sverre Hamre (https://github.com/sverrham/sensor.airthings_wave)
#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_base {
namespace espbt = esphome::esp32_ble_tracker;
static const uint8_t ACCESS_CONTROL_POINT_COMMAND = 0x6d;
static const auto CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID = espbt::ESPBTUUID::from_uint16(0x2902);
class AirthingsWaveBase : public PollingComponent, public ble_client::BLEClientNode {
public:
AirthingsWaveBase() = default;
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; }
void set_battery_voltage(sensor::Sensor *voltage) {
battery_voltage_ = voltage;
this->read_battery_next_update_ = true;
}
void set_battery_update_interval(uint32_t interval) { battery_update_interval_ = interval; }
protected:
bool is_valid_voc_value_(uint16_t voc);
bool request_read_values_();
virtual void read_sensors(uint8_t *raw_value, uint16_t value_len) = 0;
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
sensor::Sensor *pressure_sensor_{nullptr};
sensor::Sensor *tvoc_sensor_{nullptr};
sensor::Sensor *battery_voltage_{nullptr};
uint16_t handle_;
espbt::ESPBTUUID service_uuid_;
espbt::ESPBTUUID sensors_data_characteristic_uuid_;
uint16_t acp_handle_{0};
uint16_t cccd_handle_{0};
espbt::ESPBTUUID access_control_point_characteristic_uuid_;
uint8_t responses_pending_{0};
void response_pending_();
void response_received_();
void set_response_timeout_();
// default to *not* reading battery voltage from the device; the
// set_* function for the battery sensor will set this to 'true'
bool read_battery_next_update_{false};
bool request_battery_();
void read_battery_(uint8_t *raw_value, uint16_t value_len);
uint32_t battery_update_interval_{};
struct AccessControlPointResponse {
uint32_t unused1;
uint8_t unused2;
uint8_t illuminance;
uint8_t unused3[10];
uint16_t unused4[4];
uint16_t battery;
uint16_t unused5;
};
};
} // namespace airthings_wave_base
} // namespace esphome
#endif // USE_ESP32

View file

@ -7,105 +7,47 @@ namespace airthings_wave_mini {
static const char *const TAG = "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, void AirthingsWaveMini::read_sensors(uint8_t *raw_value, uint16_t value_len) {
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()->get_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; auto *value = (WaveMiniReadings *) raw_value;
if (sizeof(WaveMiniReadings) <= value_len) { if (sizeof(WaveMiniReadings) <= value_len) {
if (this->humidity_sensor_ != nullptr) {
this->humidity_sensor_->publish_state(value->humidity / 100.0f); this->humidity_sensor_->publish_state(value->humidity / 100.0f);
}
if (this->pressure_sensor_ != nullptr) {
this->pressure_sensor_->publish_state(value->pressure / 50.0f); this->pressure_sensor_->publish_state(value->pressure / 50.0f);
}
if (this->temperature_sensor_ != nullptr) {
this->temperature_sensor_->publish_state(value->temperature / 100.0f - 273.15f); this->temperature_sensor_->publish_state(value->temperature / 100.0f - 273.15f);
if (is_valid_voc_value_(value->voc)) { }
if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) {
this->tvoc_sensor_->publish_state(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; } this->response_received_();
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()->get_gattc_if(), this->parent()->get_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() { void AirthingsWaveMini::dump_config() {
// these really don't belong here, but there doesn't seem to be a
// practical way to have the base class use LOG_SENSOR and include
// the TAG from this component
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_); LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_);
} }
AirthingsWaveMini::AirthingsWaveMini() AirthingsWaveMini::AirthingsWaveMini() {
: PollingComponent(10000), this->service_uuid_ = espbt::ESPBTUUID::from_raw(SERVICE_UUID);
service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)), this->sensors_data_characteristic_uuid_ = espbt::ESPBTUUID::from_raw(CHARACTERISTIC_UUID);
sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {} this->access_control_point_characteristic_uuid_ =
espbt::ESPBTUUID::from_raw(ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID);
}
} // namespace airthings_wave_mini } // namespace airthings_wave_mini
} // namespace esphome } // namespace esphome

View file

@ -2,50 +2,25 @@
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <esp_gattc_api.h> #include "esphome/components/airthings_wave_base/airthings_wave_base.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 esphome {
namespace airthings_wave_mini { namespace airthings_wave_mini {
namespace espbt = esphome::esp32_ble_tracker;
static const char *const SERVICE_UUID = "b42e3882-ade7-11e4-89d3-123b93f75cba"; static const char *const SERVICE_UUID = "b42e3882-ade7-11e4-89d3-123b93f75cba";
static const char *const CHARACTERISTIC_UUID = "b42e3b98-ade7-11e4-89d3-123b93f75cba"; static const char *const CHARACTERISTIC_UUID = "b42e3b98-ade7-11e4-89d3-123b93f75cba";
static const char *const ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID = "b42e3ef4-ade7-11e4-89d3-123b93f75cba";
class AirthingsWaveMini : public PollingComponent, public ble_client::BLEClientNode { class AirthingsWaveMini : public airthings_wave_base::AirthingsWaveBase {
public: public:
AirthingsWaveMini(); AirthingsWaveMini();
void dump_config() override; 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: protected:
bool is_valid_voc_value_(uint16_t voc); void read_sensors(uint8_t *raw_value, uint16_t value_len) override;
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 { struct WaveMiniReadings {
uint16_t unused01; uint16_t unused01;

View file

@ -1,82 +1,28 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import sensor, ble_client from esphome.components import airthings_wave_base
from esphome.const import ( 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_ID,
CONF_HUMIDITY,
CONF_TVOC,
CONF_PRESSURE,
CONF_TEMPERATURE,
UNIT_PARTS_PER_BILLION,
ICON_RADIATOR,
) )
DEPENDENCIES = ["ble_client"] DEPENDENCIES = airthings_wave_base.DEPENDENCIES
AUTO_LOAD = ["airthings_wave_base"]
airthings_wave_mini_ns = cg.esphome_ns.namespace("airthings_wave_mini") airthings_wave_mini_ns = cg.esphome_ns.namespace("airthings_wave_mini")
AirthingsWaveMini = airthings_wave_mini_ns.class_( AirthingsWaveMini = airthings_wave_mini_ns.class_(
"AirthingsWaveMini", cg.PollingComponent, ble_client.BLEClientNode "AirthingsWaveMini", airthings_wave_base.AirthingsWaveBase
) )
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = airthings_wave_base.BASE_SCHEMA.extend(
cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(AirthingsWaveMini), 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): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config) await airthings_wave_base.wave_base_to_code(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

@ -7,55 +7,7 @@ namespace airthings_wave_plus {
static const char *const TAG = "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::read_sensors(uint8_t *raw_value, uint16_t value_len) {
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()->get_conn_id())
break;
if (param->read.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
break;
}
if (param->read.handle == this->handle_) {
read_sensors_(param->read.value, param->read.value_len);
}
break;
}
default:
break;
}
}
void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) {
auto *value = (WavePlusReadings *) raw_value; auto *value = (WavePlusReadings *) raw_value;
if (sizeof(WavePlusReadings) <= value_len) { if (sizeof(WavePlusReadings) <= value_len) {
@ -64,72 +16,66 @@ void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) {
if (value->version == 1) { if (value->version == 1) {
ESP_LOGD(TAG, "ambient light = %d", value->ambientLight); ESP_LOGD(TAG, "ambient light = %d", value->ambientLight);
if (this->humidity_sensor_ != nullptr) {
this->humidity_sensor_->publish_state(value->humidity / 2.0f); this->humidity_sensor_->publish_state(value->humidity / 2.0f);
if (is_valid_radon_value_(value->radon)) {
this->radon_sensor_->publish_state(value->radon);
}
if (is_valid_radon_value_(value->radon_lt)) {
this->radon_long_term_sensor_->publish_state(value->radon_lt);
}
this->temperature_sensor_->publish_state(value->temperature / 100.0f);
this->pressure_sensor_->publish_state(value->pressure / 50.0f);
if (is_valid_co2_value_(value->co2)) {
this->co2_sensor_->publish_state(value->co2);
}
if (is_valid_voc_value_(value->voc)) {
this->tvoc_sensor_->publish_state(value->voc);
} }
// This instance must not stay connected if ((this->radon_sensor_ != nullptr) && this->is_valid_radon_value_(value->radon)) {
// so other clients can connect to it (e.g. the this->radon_sensor_->publish_state(value->radon);
// mobile app). }
parent()->set_enabled(false);
if ((this->radon_long_term_sensor_ != nullptr) && this->is_valid_radon_value_(value->radon_lt)) {
this->radon_long_term_sensor_->publish_state(value->radon_lt);
}
if (this->temperature_sensor_ != nullptr) {
this->temperature_sensor_->publish_state(value->temperature / 100.0f);
}
if (this->pressure_sensor_ != nullptr) {
this->pressure_sensor_->publish_state(value->pressure / 50.0f);
}
if ((this->co2_sensor_ != nullptr) && this->is_valid_co2_value_(value->co2)) {
this->co2_sensor_->publish_state(value->co2);
}
if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) {
this->tvoc_sensor_->publish_state(value->voc);
}
} else { } else {
ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version); ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version);
} }
} }
this->response_received_();
} }
bool AirthingsWavePlus::is_valid_radon_value_(uint16_t radon) { return 0 <= radon && radon <= 16383; } bool AirthingsWavePlus::is_valid_radon_value_(uint16_t radon) { return radon <= 16383; }
bool AirthingsWavePlus::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; } bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return co2 <= 16383; }
bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return 0 <= co2 && co2 <= 16383; }
void AirthingsWavePlus::update() {
if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) {
if (!parent()->enabled) {
ESP_LOGW(TAG, "Reconnecting to device");
parent()->set_enabled(true);
parent()->connect();
} else {
ESP_LOGW(TAG, "Connection in progress");
}
}
}
void AirthingsWavePlus::request_read_values_() {
auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_,
ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
}
}
void AirthingsWavePlus::dump_config() { void AirthingsWavePlus::dump_config() {
// these really don't belong here, but there doesn't seem to be a
// practical way to have the base class use LOG_SENSOR and include
// the TAG from this component
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
LOG_SENSOR(" ", "Radon", this->radon_sensor_);
LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
LOG_SENSOR(" ", "CO2", this->co2_sensor_);
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_); LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_);
LOG_SENSOR(" ", "Radon", this->radon_sensor_);
LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_);
LOG_SENSOR(" ", "CO2", this->co2_sensor_);
} }
AirthingsWavePlus::AirthingsWavePlus() AirthingsWavePlus::AirthingsWavePlus() {
: PollingComponent(10000), this->service_uuid_ = espbt::ESPBTUUID::from_raw(SERVICE_UUID);
service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)), this->sensors_data_characteristic_uuid_ = espbt::ESPBTUUID::from_raw(CHARACTERISTIC_UUID);
sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {} this->access_control_point_characteristic_uuid_ =
espbt::ESPBTUUID::from_raw(ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID);
}
} // namespace airthings_wave_plus } // namespace airthings_wave_plus
} // namespace esphome } // namespace esphome

View file

@ -2,58 +2,36 @@
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <esp_gattc_api.h> #include "esphome/components/airthings_wave_base/airthings_wave_base.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 esphome {
namespace airthings_wave_plus { namespace airthings_wave_plus {
namespace espbt = esphome::esp32_ble_tracker;
static const char *const SERVICE_UUID = "b42e1c08-ade7-11e4-89d3-123b93f75cba"; static const char *const SERVICE_UUID = "b42e1c08-ade7-11e4-89d3-123b93f75cba";
static const char *const CHARACTERISTIC_UUID = "b42e2a68-ade7-11e4-89d3-123b93f75cba"; static const char *const CHARACTERISTIC_UUID = "b42e2a68-ade7-11e4-89d3-123b93f75cba";
static const char *const ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID = "b42e2d06-ade7-11e4-89d3-123b93f75cba";
class AirthingsWavePlus : public PollingComponent, public ble_client::BLEClientNode { class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase {
public: public:
AirthingsWavePlus(); AirthingsWavePlus();
void dump_config() override; void dump_config() override;
void update() override;
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
void set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; } void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; }
void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; } void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; }
void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; }
void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; } void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; }
void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
protected: protected:
bool is_valid_radon_value_(uint16_t radon); bool is_valid_radon_value_(uint16_t radon);
bool is_valid_voc_value_(uint16_t voc);
bool is_valid_co2_value_(uint16_t co2); bool is_valid_co2_value_(uint16_t co2);
void read_sensors_(uint8_t *value, uint16_t value_len); void read_sensors(uint8_t *raw_value, uint16_t value_len) override;
void request_read_values_();
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *radon_sensor_{nullptr}; sensor::Sensor *radon_sensor_{nullptr};
sensor::Sensor *radon_long_term_sensor_{nullptr}; sensor::Sensor *radon_long_term_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
sensor::Sensor *pressure_sensor_{nullptr};
sensor::Sensor *co2_sensor_{nullptr}; sensor::Sensor *co2_sensor_{nullptr};
sensor::Sensor *tvoc_sensor_{nullptr};
uint16_t handle_;
esp32_ble_tracker::ESPBTUUID service_uuid_;
esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_;
struct WavePlusReadings { struct WavePlusReadings {
uint8_t version; uint8_t version;

View file

@ -1,49 +1,32 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import sensor, ble_client from esphome.components import sensor, airthings_wave_base
from esphome.const import ( from esphome.const import (
DEVICE_CLASS_CARBON_DIOXIDE, DEVICE_CLASS_CARBON_DIOXIDE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_PERCENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
ICON_RADIOACTIVE, ICON_RADIOACTIVE,
CONF_ID, CONF_ID,
CONF_RADON, CONF_RADON,
CONF_RADON_LONG_TERM, CONF_RADON_LONG_TERM,
CONF_HUMIDITY,
CONF_TVOC,
CONF_CO2, CONF_CO2,
CONF_PRESSURE,
CONF_TEMPERATURE,
UNIT_BECQUEREL_PER_CUBIC_METER, UNIT_BECQUEREL_PER_CUBIC_METER,
UNIT_PARTS_PER_MILLION, UNIT_PARTS_PER_MILLION,
UNIT_PARTS_PER_BILLION,
ICON_RADIATOR,
) )
DEPENDENCIES = ["ble_client"] DEPENDENCIES = airthings_wave_base.DEPENDENCIES
AUTO_LOAD = ["airthings_wave_base"]
airthings_wave_plus_ns = cg.esphome_ns.namespace("airthings_wave_plus") airthings_wave_plus_ns = cg.esphome_ns.namespace("airthings_wave_plus")
AirthingsWavePlus = airthings_wave_plus_ns.class_( AirthingsWavePlus = airthings_wave_plus_ns.class_(
"AirthingsWavePlus", cg.PollingComponent, ble_client.BLEClientNode "AirthingsWavePlus", airthings_wave_base.AirthingsWaveBase
) )
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = airthings_wave_base.BASE_SCHEMA.extend(
cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(AirthingsWavePlus), cv.GenerateID(): cv.declare_id(AirthingsWavePlus),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=0,
),
cv.Optional(CONF_RADON): sensor.sensor_schema( cv.Optional(CONF_RADON): sensor.sensor_schema(
unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER, unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
icon=ICON_RADIOACTIVE, icon=ICON_RADIOACTIVE,
@ -56,61 +39,26 @@ CONFIG_SCHEMA = cv.All(
accuracy_decimals=0, accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement=UNIT_HECTOPASCAL,
accuracy_decimals=1,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CO2): sensor.sensor_schema( cv.Optional(CONF_CO2): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION, unit_of_measurement=UNIT_PARTS_PER_MILLION,
accuracy_decimals=0, accuracy_decimals=0,
device_class=DEVICE_CLASS_CARBON_DIOXIDE, device_class=DEVICE_CLASS_CARBON_DIOXIDE,
state_class=STATE_CLASS_MEASUREMENT, 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): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config) await airthings_wave_base.wave_base_to_code(var, config)
await ble_client.register_ble_node(var, config) if config_radon := config.get(CONF_RADON):
sens = await sensor.new_sensor(config_radon)
if CONF_HUMIDITY in config:
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
cg.add(var.set_humidity(sens))
if CONF_RADON in config:
sens = await sensor.new_sensor(config[CONF_RADON])
cg.add(var.set_radon(sens)) cg.add(var.set_radon(sens))
if CONF_RADON_LONG_TERM in config: if config_radon_long_term := config.get(CONF_RADON_LONG_TERM):
sens = await sensor.new_sensor(config[CONF_RADON_LONG_TERM]) sens = await sensor.new_sensor(config_radon_long_term)
cg.add(var.set_radon_long_term(sens)) cg.add(var.set_radon_long_term(sens))
if CONF_TEMPERATURE in config: if config_co2 := config.get(CONF_CO2):
sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) sens = await sensor.new_sensor(config_co2)
cg.add(var.set_temperature(sens))
if CONF_PRESSURE in config:
sens = await sensor.new_sensor(config[CONF_PRESSURE])
cg.add(var.set_pressure(sens))
if CONF_CO2 in config:
sens = await sensor.new_sensor(config[CONF_CO2])
cg.add(var.set_co2(sens)) cg.add(var.set_co2(sens))
if CONF_TVOC in config:
sens = await sensor.new_sensor(config[CONF_TVOC])
cg.add(var.set_tvoc(sens))

View file

@ -16,6 +16,12 @@ IS_PLATFORM_COMPONENT = True
CONF_ON_TRIGGERED = "on_triggered" CONF_ON_TRIGGERED = "on_triggered"
CONF_ON_CLEARED = "on_cleared" CONF_ON_CLEARED = "on_cleared"
CONF_ON_ARMING = "on_arming"
CONF_ON_PENDING = "on_pending"
CONF_ON_ARMED_HOME = "on_armed_home"
CONF_ON_ARMED_NIGHT = "on_armed_night"
CONF_ON_ARMED_AWAY = "on_armed_away"
CONF_ON_DISARMED = "on_disarmed"
alarm_control_panel_ns = cg.esphome_ns.namespace("alarm_control_panel") alarm_control_panel_ns = cg.esphome_ns.namespace("alarm_control_panel")
AlarmControlPanel = alarm_control_panel_ns.class_("AlarmControlPanel", cg.EntityBase) AlarmControlPanel = alarm_control_panel_ns.class_("AlarmControlPanel", cg.EntityBase)
@ -29,8 +35,27 @@ TriggeredTrigger = alarm_control_panel_ns.class_(
ClearedTrigger = alarm_control_panel_ns.class_( ClearedTrigger = alarm_control_panel_ns.class_(
"ClearedTrigger", automation.Trigger.template() "ClearedTrigger", automation.Trigger.template()
) )
ArmingTrigger = alarm_control_panel_ns.class_(
"ArmingTrigger", automation.Trigger.template()
)
PendingTrigger = alarm_control_panel_ns.class_(
"PendingTrigger", automation.Trigger.template()
)
ArmedHomeTrigger = alarm_control_panel_ns.class_(
"ArmedHomeTrigger", automation.Trigger.template()
)
ArmedNightTrigger = alarm_control_panel_ns.class_(
"ArmedNightTrigger", automation.Trigger.template()
)
ArmedAwayTrigger = alarm_control_panel_ns.class_(
"ArmedAwayTrigger", automation.Trigger.template()
)
DisarmedTrigger = alarm_control_panel_ns.class_(
"DisarmedTrigger", automation.Trigger.template()
)
ArmAwayAction = alarm_control_panel_ns.class_("ArmAwayAction", automation.Action) ArmAwayAction = alarm_control_panel_ns.class_("ArmAwayAction", automation.Action)
ArmHomeAction = alarm_control_panel_ns.class_("ArmHomeAction", automation.Action) ArmHomeAction = alarm_control_panel_ns.class_("ArmHomeAction", automation.Action)
ArmNightAction = alarm_control_panel_ns.class_("ArmNightAction", automation.Action)
DisarmAction = alarm_control_panel_ns.class_("DisarmAction", automation.Action) DisarmAction = alarm_control_panel_ns.class_("DisarmAction", automation.Action)
PendingAction = alarm_control_panel_ns.class_("PendingAction", automation.Action) PendingAction = alarm_control_panel_ns.class_("PendingAction", automation.Action)
TriggeredAction = alarm_control_panel_ns.class_("TriggeredAction", automation.Action) TriggeredAction = alarm_control_panel_ns.class_("TriggeredAction", automation.Action)
@ -51,6 +76,36 @@ ALARM_CONTROL_PANEL_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger),
} }
), ),
cv.Optional(CONF_ON_ARMING): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmingTrigger),
}
),
cv.Optional(CONF_ON_PENDING): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PendingTrigger),
}
),
cv.Optional(CONF_ON_ARMED_HOME): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedHomeTrigger),
}
),
cv.Optional(CONF_ON_ARMED_NIGHT): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedNightTrigger),
}
),
cv.Optional(CONF_ON_ARMED_AWAY): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedAwayTrigger),
}
),
cv.Optional(CONF_ON_DISARMED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DisarmedTrigger),
}
),
cv.Optional(CONF_ON_CLEARED): automation.validate_automation( cv.Optional(CONF_ON_CLEARED): automation.validate_automation(
{ {
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger),
@ -81,6 +136,24 @@ async def setup_alarm_control_panel_core_(var, config):
for conf in config.get(CONF_ON_TRIGGERED, []): for conf in config.get(CONF_ON_TRIGGERED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_ARMING, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_PENDING, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_ARMED_HOME, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_ARMED_NIGHT, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_ARMED_AWAY, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_DISARMED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_CLEARED, []): for conf in config.get(CONF_ON_CLEARED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) await automation.build_automation(trigger, [], conf)
@ -99,8 +172,8 @@ async def register_alarm_control_panel(var, config):
async def alarm_action_arm_away_to_code(config, action_id, template_arg, args): async def alarm_action_arm_away_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren) var = cg.new_Pvariable(action_id, template_arg, paren)
if CONF_CODE in config: if code_config := config.get(CONF_CODE):
templatable_ = await cg.templatable(config[CONF_CODE], args, cg.std_string) templatable_ = await cg.templatable(code_config, args, cg.std_string)
cg.add(var.set_code(templatable_)) cg.add(var.set_code(templatable_))
return var return var
@ -109,6 +182,18 @@ async def alarm_action_arm_away_to_code(config, action_id, template_arg, args):
"alarm_control_panel.arm_home", ArmHomeAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA "alarm_control_panel.arm_home", ArmHomeAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
) )
async def alarm_action_arm_home_to_code(config, action_id, template_arg, args): async def alarm_action_arm_home_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
if code_config := config.get(CONF_CODE):
templatable_ = await cg.templatable(code_config, args, cg.std_string)
cg.add(var.set_code(templatable_))
return var
@automation.register_action(
"alarm_control_panel.arm_night", ArmNightAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
)
async def alarm_action_arm_night_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren) var = cg.new_Pvariable(action_id, template_arg, paren)
if CONF_CODE in config: if CONF_CODE in config:
@ -123,8 +208,8 @@ async def alarm_action_arm_home_to_code(config, action_id, template_arg, args):
async def alarm_action_disarm_to_code(config, action_id, template_arg, args): async def alarm_action_disarm_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren) var = cg.new_Pvariable(action_id, template_arg, paren)
if CONF_CODE in config: if code_config := config.get(CONF_CODE):
templatable_ = await cg.templatable(config[CONF_CODE], args, cg.std_string) templatable_ = await cg.templatable(code_config, args, cg.std_string)
cg.add(var.set_code(templatable_)) cg.add(var.set_code(templatable_))
return var return var

View file

@ -36,7 +36,20 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
this->state_callback_.call(); this->state_callback_.call();
if (state == ACP_STATE_TRIGGERED) { if (state == ACP_STATE_TRIGGERED) {
this->triggered_callback_.call(); this->triggered_callback_.call();
} else if (state == ACP_STATE_ARMING) {
this->arming_callback_.call();
} else if (state == ACP_STATE_PENDING) {
this->pending_callback_.call();
} else if (state == ACP_STATE_ARMED_HOME) {
this->armed_home_callback_.call();
} else if (state == ACP_STATE_ARMED_NIGHT) {
this->armed_night_callback_.call();
} else if (state == ACP_STATE_ARMED_AWAY) {
this->armed_away_callback_.call();
} else if (state == ACP_STATE_DISARMED) {
this->disarmed_callback_.call();
} }
if (prev_state == ACP_STATE_TRIGGERED) { if (prev_state == ACP_STATE_TRIGGERED) {
this->cleared_callback_.call(); this->cleared_callback_.call();
} }
@ -55,6 +68,30 @@ void AlarmControlPanel::add_on_triggered_callback(std::function<void()> &&callba
this->triggered_callback_.add(std::move(callback)); this->triggered_callback_.add(std::move(callback));
} }
void AlarmControlPanel::add_on_arming_callback(std::function<void()> &&callback) {
this->arming_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_armed_home_callback(std::function<void()> &&callback) {
this->armed_home_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_armed_night_callback(std::function<void()> &&callback) {
this->armed_night_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_armed_away_callback(std::function<void()> &&callback) {
this->armed_away_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_pending_callback(std::function<void()> &&callback) {
this->pending_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_disarmed_callback(std::function<void()> &&callback) {
this->disarmed_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_cleared_callback(std::function<void()> &&callback) { void AlarmControlPanel::add_on_cleared_callback(std::function<void()> &&callback) {
this->cleared_callback_.add(std::move(callback)); this->cleared_callback_.add(std::move(callback));
} }

View file

@ -47,6 +47,42 @@ class AlarmControlPanel : public EntityBase {
*/ */
void add_on_triggered_callback(std::function<void()> &&callback); void add_on_triggered_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel chanes to arming
*
* @param callback The callback function
*/
void add_on_arming_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to pending
*
* @param callback The callback function
*/
void add_on_pending_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to armed_home
*
* @param callback The callback function
*/
void add_on_armed_home_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to armed_night
*
* @param callback The callback function
*/
void add_on_armed_night_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to armed_away
*
* @param callback The callback function
*/
void add_on_armed_away_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to disarmed
*
* @param callback The callback function
*/
void add_on_disarmed_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel clears from triggered /** Add a callback for when the state of the alarm_control_panel clears from triggered
* *
* @param callback The callback function * @param callback The callback function
@ -128,6 +164,18 @@ class AlarmControlPanel : public EntityBase {
CallbackManager<void()> state_callback_{}; CallbackManager<void()> state_callback_{};
// trigger callback // trigger callback
CallbackManager<void()> triggered_callback_{}; CallbackManager<void()> triggered_callback_{};
// arming callback
CallbackManager<void()> arming_callback_{};
// pending callback
CallbackManager<void()> pending_callback_{};
// armed_home callback
CallbackManager<void()> armed_home_callback_{};
// armed_night callback
CallbackManager<void()> armed_night_callback_{};
// armed_away callback
CallbackManager<void()> armed_away_callback_{};
// disarmed callback
CallbackManager<void()> disarmed_callback_{};
// clear callback // clear callback
CallbackManager<void()> cleared_callback_{}; CallbackManager<void()> cleared_callback_{};
}; };

View file

@ -85,6 +85,11 @@ void AlarmControlPanelCall::validate_() {
this->state_.reset(); this->state_.reset();
return; return;
} }
if (state == ACP_STATE_ARMED_NIGHT && (this->parent_->get_supported_features() & ACP_FEAT_ARM_NIGHT) == 0) {
ESP_LOGW(TAG, "Cannot arm night when not supported");
this->state_.reset();
return;
}
} }
} }

View file

@ -12,7 +12,7 @@ const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState stat
case ACP_STATE_ARMED_AWAY: case ACP_STATE_ARMED_AWAY:
return LOG_STR("ARMED_AWAY"); return LOG_STR("ARMED_AWAY");
case ACP_STATE_ARMED_NIGHT: case ACP_STATE_ARMED_NIGHT:
return LOG_STR("NIGHT"); return LOG_STR("ARMED_NIGHT");
case ACP_STATE_ARMED_VACATION: case ACP_STATE_ARMED_VACATION:
return LOG_STR("ARMED_VACATION"); return LOG_STR("ARMED_VACATION");
case ACP_STATE_ARMED_CUSTOM_BYPASS: case ACP_STATE_ARMED_CUSTOM_BYPASS:

View file

@ -20,6 +20,48 @@ class TriggeredTrigger : public Trigger<> {
} }
}; };
class ArmingTrigger : public Trigger<> {
public:
explicit ArmingTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_arming_callback([this]() { this->trigger(); });
}
};
class PendingTrigger : public Trigger<> {
public:
explicit PendingTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_pending_callback([this]() { this->trigger(); });
}
};
class ArmedHomeTrigger : public Trigger<> {
public:
explicit ArmedHomeTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_armed_home_callback([this]() { this->trigger(); });
}
};
class ArmedNightTrigger : public Trigger<> {
public:
explicit ArmedNightTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_armed_night_callback([this]() { this->trigger(); });
}
};
class ArmedAwayTrigger : public Trigger<> {
public:
explicit ArmedAwayTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_armed_away_callback([this]() { this->trigger(); });
}
};
class DisarmedTrigger : public Trigger<> {
public:
explicit DisarmedTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_disarmed_callback([this]() { this->trigger(); });
}
};
class ClearedTrigger : public Trigger<> { class ClearedTrigger : public Trigger<> {
public: public:
explicit ClearedTrigger(AlarmControlPanel *alarm_control_panel) { explicit ClearedTrigger(AlarmControlPanel *alarm_control_panel) {
@ -67,6 +109,26 @@ template<typename... Ts> class ArmHomeAction : public Action<Ts...> {
AlarmControlPanel *alarm_control_panel_; AlarmControlPanel *alarm_control_panel_;
}; };
template<typename... Ts> class ArmNightAction : public Action<Ts...> {
public:
explicit ArmNightAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
TEMPLATABLE_VALUE(std::string, code)
void play(Ts... x) override {
auto call = this->alarm_control_panel_->make_call();
auto code = this->code_.optional_value(x...);
if (code.has_value()) {
call.set_code(code.value());
}
call.arm_night();
call.perform();
}
protected:
AlarmControlPanel *alarm_control_panel_;
};
template<typename... Ts> class DisarmAction : public Action<Ts...> { template<typename... Ts> class DisarmAction : public Action<Ts...> {
public: public:
explicit DisarmAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {} explicit DisarmAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}

View file

@ -0,0 +1 @@
CODEOWNERS = ["@jan-hofmeier"]

View file

@ -0,0 +1,189 @@
#include "alpha3.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include <lwip/sockets.h> //gives ntohl
#ifdef USE_ESP32
namespace esphome {
namespace alpha3 {
static const char *const TAG = "alpha3";
void Alpha3::dump_config() {
ESP_LOGCONFIG(TAG, "ALPHA3");
LOG_SENSOR(" ", "Flow", this->flow_sensor_);
LOG_SENSOR(" ", "Head", this->head_sensor_);
LOG_SENSOR(" ", "Power", this->power_sensor_);
LOG_SENSOR(" ", "Current", this->current_sensor_);
LOG_SENSOR(" ", "Speed", this->speed_sensor_);
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
}
void Alpha3::setup() {}
void Alpha3::extract_publish_sensor_value_(const uint8_t *response, int16_t length, int16_t response_offset,
int16_t value_offset, sensor::Sensor *sensor, float factor) {
if (sensor == nullptr)
return;
// we need to handle cases where a value is split over two packets
const int16_t value_length = 4; // 32bit float
// offset inside current response packet
auto rel_offset = value_offset - response_offset;
if (rel_offset <= -value_length)
return; // aready passed the value completly
if (rel_offset >= length)
return; // value not in this packet
auto start_offset = std::max(0, rel_offset);
auto end_offset = std::min((int16_t) (rel_offset + value_length), length);
auto copy_length = end_offset - start_offset;
auto buffer_offset = std::max(-rel_offset, 0);
std::memcpy(this->buffer_ + buffer_offset, response + start_offset, copy_length);
if (rel_offset + value_length <= length) {
// we have the whole value
void *buffer = this->buffer_; // to prevent warnings when casting the pointer
*((int32_t *) buffer) = ntohl(*((int32_t *) buffer)); // values are big endian
float fvalue = *((float *) buffer);
sensor->publish_state(fvalue * factor);
}
}
bool Alpha3::is_current_response_type_(const uint8_t *response_type) {
return !std::memcmp(this->response_type_, response_type, GENI_RESPONSE_TYPE_LENGTH);
}
void Alpha3::handle_geni_response_(const uint8_t *response, uint16_t length) {
if (this->response_offset_ >= this->response_length_) {
ESP_LOGD(TAG, "[%s] GENI response begin", this->parent_->address_str().c_str());
if (length < GENI_RESPONSE_HEADER_LENGTH) {
ESP_LOGW(TAG, "[%s] response to short", this->parent_->address_str().c_str());
return;
}
if (response[0] != 36 || response[2] != 248 || response[3] != 231 || response[4] != 10) {
ESP_LOGW(TAG, "[%s] response bytes %d %d %d %d %d don't match GENI HEADER", this->parent_->address_str().c_str(),
response[0], response[1], response[2], response[3], response[4]);
return;
}
this->response_length_ = response[1] - GENI_RESPONSE_HEADER_LENGTH + 2; // maybe 2 byte checksum
this->response_offset_ = -GENI_RESPONSE_HEADER_LENGTH;
std::memcpy(this->response_type_, response + 5, GENI_RESPONSE_TYPE_LENGTH);
}
auto extract_publish_sensor_value = [response, length, this](int16_t value_offset, sensor::Sensor *sensor,
float factor) {
this->extract_publish_sensor_value_(response, length, this->response_offset_, value_offset, sensor, factor);
};
if (this->is_current_response_type_(GENI_RESPONSE_TYPE_FLOW_HEAD)) {
ESP_LOGD(TAG, "[%s] FLOW HEAD Response", this->parent_->address_str().c_str());
extract_publish_sensor_value(GENI_RESPONSE_FLOW_OFFSET, this->flow_sensor_, 3600.0F);
extract_publish_sensor_value(GENI_RESPONSE_HEAD_OFFSET, this->head_sensor_, .0001F);
} else if (this->is_current_response_type_(GENI_RESPONSE_TYPE_POWER)) {
ESP_LOGD(TAG, "[%s] POWER Response", this->parent_->address_str().c_str());
extract_publish_sensor_value(GENI_RESPONSE_POWER_OFFSET, this->power_sensor_, 1.0F);
extract_publish_sensor_value(GENI_RESPONSE_CURRENT_OFFSET, this->current_sensor_, 1.0F);
extract_publish_sensor_value(GENI_RESPONSE_MOTOR_SPEED_OFFSET, this->speed_sensor_, 1.0F);
extract_publish_sensor_value(GENI_RESPONSE_VOLTAGE_AC_OFFSET, this->voltage_sensor_, 1.0F);
} else {
ESP_LOGW(TAG, "unkown GENI response Type %d %d %d %d %d %d %d %d", this->response_type_[0], this->response_type_[1],
this->response_type_[2], this->response_type_[3], this->response_type_[4], this->response_type_[5],
this->response_type_[6], this->response_type_[7]);
}
this->response_offset_ += length;
}
void Alpha3::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
switch (event) {
case ESP_GATTC_OPEN_EVT: {
this->response_offset_ = 0;
this->response_length_ = 0;
ESP_LOGI(TAG, "[%s] connection open", this->parent_->address_str().c_str());
break;
}
case ESP_GATTC_CONNECT_EVT: {
if (std::memcmp(param->connect.remote_bda, this->parent_->get_remote_bda(), 6) != 0)
return;
auto ret = esp_ble_set_encryption(param->connect.remote_bda, ESP_BLE_SEC_ENCRYPT);
if (ret) {
ESP_LOGW(TAG, "esp_ble_set_encryption failed, status=%x", ret);
}
break;
}
case ESP_GATTC_DISCONNECT_EVT: {
this->node_state = espbt::ClientState::IDLE;
if (this->flow_sensor_ != nullptr)
this->flow_sensor_->publish_state(NAN);
if (this->head_sensor_ != nullptr)
this->head_sensor_->publish_state(NAN);
if (this->power_sensor_ != nullptr)
this->power_sensor_->publish_state(NAN);
if (this->current_sensor_ != nullptr)
this->current_sensor_->publish_state(NAN);
if (this->speed_sensor_ != nullptr)
this->speed_sensor_->publish_state(NAN);
if (this->speed_sensor_ != nullptr)
this->voltage_sensor_->publish_state(NAN);
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto *chr = this->parent_->get_characteristic(ALPHA3_GENI_SERVICE_UUID, ALPHA3_GENI_CHARACTERISTIC_UUID);
if (chr == nullptr) {
ESP_LOGE(TAG, "[%s] No GENI service found at device, not an Alpha3..?", this->parent_->address_str().c_str());
break;
}
auto status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
chr->handle);
if (status) {
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status);
}
this->geni_handle_ = chr->handle;
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
this->node_state = espbt::ClientState::ESTABLISHED;
this->update();
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.handle == this->geni_handle_) {
this->handle_geni_response_(param->notify.value, param->notify.value_len);
}
break;
}
default:
break;
}
}
void Alpha3::send_request_(uint8_t *request, size_t len) {
auto status =
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->geni_handle_, len,
request, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status)
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
}
void Alpha3::update() {
if (this->node_state != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str());
return;
}
if (this->flow_sensor_ != nullptr || this->head_sensor_ != nullptr) {
uint8_t geni_request_flow_head[] = {39, 7, 231, 248, 10, 3, 93, 1, 33, 82, 31};
this->send_request_(geni_request_flow_head, sizeof(geni_request_flow_head));
delay(25); // need to wait between requests
}
if (this->power_sensor_ != nullptr || this->current_sensor_ != nullptr || this->speed_sensor_ != nullptr ||
this->voltage_sensor_ != nullptr) {
uint8_t geni_request_power[] = {39, 7, 231, 248, 10, 3, 87, 0, 69, 138, 205};
this->send_request_(geni_request_power, sizeof(geni_request_power));
delay(25); // need to wait between requests
}
}
} // namespace alpha3
} // namespace esphome
#endif

View file

@ -0,0 +1,73 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/sensor/sensor.h"
#ifdef USE_ESP32
#include <esp_gattc_api.h>
namespace esphome {
namespace alpha3 {
namespace espbt = esphome::esp32_ble_tracker;
static const espbt::ESPBTUUID ALPHA3_GENI_SERVICE_UUID = espbt::ESPBTUUID::from_uint16(0xfe5d);
static const espbt::ESPBTUUID ALPHA3_GENI_CHARACTERISTIC_UUID =
espbt::ESPBTUUID::from_raw({static_cast<char>(0xa9), 0x7b, static_cast<char>(0xb8), static_cast<char>(0x85), 0x0,
0x1a, 0x28, static_cast<char>(0xaa), 0x2a, 0x43, 0x6e, 0x3, static_cast<char>(0xd1),
static_cast<char>(0xff), static_cast<char>(0x9c), static_cast<char>(0x85)});
static const int16_t GENI_RESPONSE_HEADER_LENGTH = 13;
static const size_t GENI_RESPONSE_TYPE_LENGTH = 8;
static const uint8_t GENI_RESPONSE_TYPE_FLOW_HEAD[GENI_RESPONSE_TYPE_LENGTH] = {31, 0, 1, 48, 1, 0, 0, 24};
static const int16_t GENI_RESPONSE_FLOW_OFFSET = 0;
static const int16_t GENI_RESPONSE_HEAD_OFFSET = 4;
static const uint8_t GENI_RESPONSE_TYPE_POWER[GENI_RESPONSE_TYPE_LENGTH] = {44, 0, 1, 0, 1, 0, 0, 37};
static const int16_t GENI_RESPONSE_VOLTAGE_AC_OFFSET = 0;
static const int16_t GENI_RESPONSE_VOLTAGE_DC_OFFSET = 4;
static const int16_t GENI_RESPONSE_CURRENT_OFFSET = 8;
static const int16_t GENI_RESPONSE_POWER_OFFSET = 12;
static const int16_t GENI_RESPONSE_MOTOR_POWER_OFFSET = 16; // not sure
static const int16_t GENI_RESPONSE_MOTOR_SPEED_OFFSET = 20;
class Alpha3 : public esphome::ble_client::BLEClientNode, public PollingComponent {
public:
void setup() override;
void update() override;
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_flow_sensor(sensor::Sensor *sensor) { this->flow_sensor_ = sensor; }
void set_head_sensor(sensor::Sensor *sensor) { this->head_sensor_ = sensor; }
void set_power_sensor(sensor::Sensor *sensor) { this->power_sensor_ = sensor; }
void set_current_sensor(sensor::Sensor *sensor) { this->current_sensor_ = sensor; }
void set_speed_sensor(sensor::Sensor *sensor) { this->speed_sensor_ = sensor; }
void set_voltage_sensor(sensor::Sensor *sensor) { this->voltage_sensor_ = sensor; }
protected:
sensor::Sensor *flow_sensor_{nullptr};
sensor::Sensor *head_sensor_{nullptr};
sensor::Sensor *power_sensor_{nullptr};
sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *speed_sensor_{nullptr};
sensor::Sensor *voltage_sensor_{nullptr};
uint16_t geni_handle_;
int16_t response_length_;
int16_t response_offset_;
uint8_t response_type_[GENI_RESPONSE_TYPE_LENGTH];
uint8_t buffer_[4];
void extract_publish_sensor_value_(const uint8_t *response, int16_t length, int16_t response_offset,
int16_t value_offset, sensor::Sensor *sensor, float factor);
void handle_geni_response_(const uint8_t *response, uint16_t length);
void send_request_(uint8_t *request, size_t len);
bool is_current_response_type_(const uint8_t *response_type);
};
} // namespace alpha3
} // namespace esphome
#endif

View file

@ -0,0 +1,85 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, ble_client
from esphome.const import (
CONF_ID,
CONF_CURRENT,
CONF_FLOW,
CONF_HEAD,
CONF_POWER,
CONF_SPEED,
CONF_VOLTAGE,
UNIT_AMPERE,
UNIT_VOLT,
UNIT_WATT,
UNIT_METER,
UNIT_CUBIC_METER_PER_HOUR,
UNIT_REVOLUTIONS_PER_MINUTE,
)
alpha3_ns = cg.esphome_ns.namespace("alpha3")
Alpha3 = alpha3_ns.class_("Alpha3", ble_client.BLEClientNode, cg.PollingComponent)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(Alpha3),
cv.Optional(CONF_FLOW): sensor.sensor_schema(
unit_of_measurement=UNIT_CUBIC_METER_PER_HOUR,
accuracy_decimals=2,
),
cv.Optional(CONF_HEAD): sensor.sensor_schema(
unit_of_measurement=UNIT_METER,
accuracy_decimals=2,
),
cv.Optional(CONF_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=2,
),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
),
cv.Optional(CONF_SPEED): sensor.sensor_schema(
unit_of_measurement=UNIT_REVOLUTIONS_PER_MINUTE,
accuracy_decimals=2,
),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2,
),
}
)
.extend(ble_client.BLE_CLIENT_SCHEMA)
.extend(cv.polling_component_schema("15s"))
)
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 flow_config := config.get(CONF_FLOW):
sens = await sensor.new_sensor(flow_config)
cg.add(var.set_flow_sensor(sens))
if head_config := config.get(CONF_HEAD):
sens = await sensor.new_sensor(head_config)
cg.add(var.set_head_sensor(sens))
if power_config := config.get(CONF_POWER):
sens = await sensor.new_sensor(power_config)
cg.add(var.set_power_sensor(sens))
if current_config := config.get(CONF_CURRENT):
sens = await sensor.new_sensor(current_config)
cg.add(var.set_current_sensor(sens))
if speed_config := config.get(CONF_SPEED):
sens = await sensor.new_sensor(speed_config)
cg.add(var.set_speed_sensor(sens))
if voltage_config := config.get(CONF_VOLTAGE):
sens = await sensor.new_sensor(voltage_config)
cg.add(var.set_voltage_sensor(sens))

View file

@ -47,10 +47,10 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config: if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature_sensor(sens)) cg.add(var.set_temperature_sensor(sens))
if CONF_HUMIDITY in config: if humidity_config := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(config[CONF_HUMIDITY]) sens = await sensor.new_sensor(humidity_config)
cg.add(var.set_humidity_sensor(sens)) cg.add(var.set_humidity_sensor(sens))

View file

@ -44,10 +44,10 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await ble_client.register_ble_node(var, config) await ble_client.register_ble_node(var, config)
if CONF_BATTERY_LEVEL in config: if battery_level_config := config.get(CONF_BATTERY_LEVEL):
sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL]) sens = await sensor.new_sensor(battery_level_config)
cg.add(var.set_battery(sens)) cg.add(var.set_battery(sens))
if CONF_ILLUMINANCE in config: if illuminance_config := config.get(CONF_ILLUMINANCE):
sens = await sensor.new_sensor(config[CONF_ILLUMINANCE]) sens = await sensor.new_sensor(illuminance_config)
cg.add(var.set_illuminance(sens)) cg.add(var.set_illuminance(sens))

View file

@ -1,7 +1,7 @@
import logging import logging
from esphome import core from esphome import automation, core
from esphome.components import display, font from esphome.components import font
import esphome.components.image as espImage import esphome.components.image as espImage
from esphome.components.image import CONF_USE_TRANSPARENCY from esphome.components.image import CONF_USE_TRANSPARENCY
import esphome.config_validation as cv import esphome.config_validation as cv
@ -18,14 +18,30 @@ from esphome.core import CORE, HexInt
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
AUTO_LOAD = ["image"]
CODEOWNERS = ["@syndlex"]
DEPENDENCIES = ["display"] DEPENDENCIES = ["display"]
MULTI_CONF = True MULTI_CONF = True
CONF_LOOP = "loop" CONF_LOOP = "loop"
CONF_START_FRAME = "start_frame" CONF_START_FRAME = "start_frame"
CONF_END_FRAME = "end_frame" CONF_END_FRAME = "end_frame"
CONF_FRAME = "frame"
Animation_ = display.display_ns.class_("Animation", espImage.Image_) animation_ns = cg.esphome_ns.namespace("animation")
Animation_ = animation_ns.class_("Animation", espImage.Image_)
# Actions
NextFrameAction = animation_ns.class_(
"AnimationNextFrameAction", automation.Action, cg.Parented.template(Animation_)
)
PrevFrameAction = animation_ns.class_(
"AnimationPrevFrameAction", automation.Action, cg.Parented.template(Animation_)
)
SetFrameAction = animation_ns.class_(
"AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_)
)
def validate_cross_dependencies(config): def validate_cross_dependencies(config):
@ -74,7 +90,35 @@ ANIMATION_SCHEMA = cv.Schema(
CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA) CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA)
CODEOWNERS = ["@syndlex"] NEXT_FRAME_SCHEMA = automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(Animation_),
}
)
PREV_FRAME_SCHEMA = automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(Animation_),
}
)
SET_FRAME_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(Animation_),
cv.Required(CONF_FRAME): cv.uint16_t,
}
)
@automation.register_action("animation.next_frame", NextFrameAction, NEXT_FRAME_SCHEMA)
@automation.register_action("animation.prev_frame", PrevFrameAction, PREV_FRAME_SCHEMA)
@automation.register_action("animation.set_frame", SetFrameAction, SET_FRAME_SCHEMA)
async def animation_action_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
if (frame := config.get(CONF_FRAME)) is not None:
template_ = await cg.templatable(frame, args, cg.uint16)
cg.add(var.set_frame(template_))
return var
async def to_code(config): async def to_code(config):
@ -245,8 +289,8 @@ async def to_code(config):
espImage.IMAGE_TYPE[config[CONF_TYPE]], espImage.IMAGE_TYPE[config[CONF_TYPE]],
) )
cg.add(var.set_transparency(transparent)) cg.add(var.set_transparency(transparent))
if CONF_LOOP in config: if loop_config := config.get(CONF_LOOP):
start = config[CONF_LOOP][CONF_START_FRAME] start = loop_config[CONF_START_FRAME]
end = config[CONF_LOOP].get(CONF_END_FRAME, frames) end = loop_config.get(CONF_END_FRAME, frames)
count = config[CONF_LOOP].get(CONF_REPEAT, -1) count = loop_config.get(CONF_REPEAT, -1)
cg.add(var.set_loop(start, end, count)) cg.add(var.set_loop(start, end, count))

View file

@ -3,9 +3,10 @@
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
namespace esphome { namespace esphome {
namespace display { namespace animation {
Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type) Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count,
image::ImageType type)
: Image(data_start, width, height, type), : Image(data_start, width, height, type),
animation_data_start_(data_start), animation_data_start_(data_start),
current_frame_(0), current_frame_(0),
@ -65,5 +66,5 @@ void Animation::update_data_start_() {
this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_; this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_;
} }
} // namespace display } // namespace animation
} // namespace esphome } // namespace esphome

View file

@ -0,0 +1,67 @@
#pragma once
#include "esphome/components/image/image.h"
#include "esphome/core/automation.h"
namespace esphome {
namespace animation {
class Animation : public image::Image {
public:
Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, image::ImageType type);
uint32_t get_animation_frame_count() const;
int get_current_frame() const;
void next_frame();
void prev_frame();
/** Selects a specific frame within the animation.
*
* @param frame If possitive, advance to the frame. If negative, recede to that frame from the end frame.
*/
void set_frame(int frame);
void set_loop(uint32_t start_frame, uint32_t end_frame, int count);
protected:
void update_data_start_();
const uint8_t *animation_data_start_;
int current_frame_;
uint32_t animation_frame_count_;
uint32_t loop_start_frame_;
uint32_t loop_end_frame_;
int loop_count_;
int loop_current_iteration_;
};
template<typename... Ts> class AnimationNextFrameAction : public Action<Ts...> {
public:
AnimationNextFrameAction(Animation *parent) : parent_(parent) {}
void play(Ts... x) override { this->parent_->next_frame(); }
protected:
Animation *parent_;
};
template<typename... Ts> class AnimationPrevFrameAction : public Action<Ts...> {
public:
AnimationPrevFrameAction(Animation *parent) : parent_(parent) {}
void play(Ts... x) override { this->parent_->prev_frame(); }
protected:
Animation *parent_;
};
template<typename... Ts> class AnimationSetFrameAction : public Action<Ts...> {
public:
AnimationSetFrameAction(Animation *parent) : parent_(parent) {}
TEMPLATABLE_VALUE(uint16_t, frame)
void play(Ts... x) override { this->parent_->set_frame(this->frame_.value(x...)); }
protected:
Animation *parent_;
};
} // namespace animation
} // namespace esphome

View file

@ -116,9 +116,8 @@ async def to_code(config):
cg.add(var.register_user_service(trigger)) cg.add(var.register_user_service(trigger))
await automation.build_automation(trigger, func_args, conf) await automation.build_automation(trigger, func_args, conf)
if CONF_ENCRYPTION in config: if encryption_config := config.get(CONF_ENCRYPTION):
conf = config[CONF_ENCRYPTION] decoded = base64.b64decode(encryption_config[CONF_KEY])
decoded = base64.b64decode(conf[CONF_KEY])
cg.add(var.set_noise_psk(list(decoded))) cg.add(var.set_noise_psk(list(decoded)))
cg.add_define("USE_API_NOISE") cg.add_define("USE_API_NOISE")
cg.add_library("esphome/noise-c", "0.1.4") cg.add_library("esphome/noise-c", "0.1.4")

View file

@ -1420,6 +1420,7 @@ message VoiceAssistantRequest {
bool start = 1; bool start = 1;
string conversation_id = 2; string conversation_id = 2;
bool use_vad = 3;
} }
message VoiceAssistantResponse { message VoiceAssistantResponse {

View file

@ -907,12 +907,13 @@ BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
bool APIConnection::request_voice_assistant(bool start, const std::string &conversation_id) { bool APIConnection::request_voice_assistant(bool start, const std::string &conversation_id, bool use_vad) {
if (!this->voice_assistant_subscription_) if (!this->voice_assistant_subscription_)
return false; return false;
VoiceAssistantRequest msg; VoiceAssistantRequest msg;
msg.start = start; msg.start = start;
msg.conversation_id = conversation_id; msg.conversation_id = conversation_id;
msg.use_vad = use_vad;
return this->send_voice_assistant_request(msg); return this->send_voice_assistant_request(msg);
} }
void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) { void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) {
@ -1050,6 +1051,10 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
resp.manufacturer = "Espressif"; resp.manufacturer = "Espressif";
#elif defined(USE_RP2040) #elif defined(USE_RP2040)
resp.manufacturer = "Raspberry Pi"; resp.manufacturer = "Raspberry Pi";
#elif defined(USE_BK72XX)
resp.manufacturer = "Beken";
#elif defined(USE_RTL87XX)
resp.manufacturer = "Realtek";
#elif defined(USE_HOST) #elif defined(USE_HOST)
resp.manufacturer = "Host"; resp.manufacturer = "Host";
#endif #endif

View file

@ -124,7 +124,7 @@ class APIConnection : public APIServerConnection {
void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override { void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override {
this->voice_assistant_subscription_ = msg.subscribe; this->voice_assistant_subscription_ = msg.subscribe;
} }
bool request_voice_assistant(bool start, const std::string &conversation_id); bool request_voice_assistant(bool start, const std::string &conversation_id, bool use_vad);
void on_voice_assistant_response(const VoiceAssistantResponse &msg) override; void on_voice_assistant_response(const VoiceAssistantResponse &msg) override;
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override; void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override;
#endif #endif

View file

@ -3,6 +3,8 @@
#include "api_pb2.h" #include "api_pb2.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include <cinttypes>
namespace esphome { namespace esphome {
namespace api { namespace api {
@ -522,12 +524,12 @@ void HelloRequest::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" api_version_major: "); out.append(" api_version_major: ");
sprintf(buffer, "%u", this->api_version_major); sprintf(buffer, "%" PRIu32, this->api_version_major);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append(" api_version_minor: "); out.append(" api_version_minor: ");
sprintf(buffer, "%u", this->api_version_minor); sprintf(buffer, "%" PRIu32, this->api_version_minor);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
@ -572,12 +574,12 @@ void HelloResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("HelloResponse {\n"); out.append("HelloResponse {\n");
out.append(" api_version_major: "); out.append(" api_version_major: ");
sprintf(buffer, "%u", this->api_version_major); sprintf(buffer, "%" PRIu32, this->api_version_major);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append(" api_version_minor: "); out.append(" api_version_minor: ");
sprintf(buffer, "%u", this->api_version_minor); sprintf(buffer, "%" PRIu32, this->api_version_minor);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -783,17 +785,17 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" webserver_port: "); out.append(" webserver_port: ");
sprintf(buffer, "%u", this->webserver_port); sprintf(buffer, "%" PRIu32, this->webserver_port);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append(" legacy_bluetooth_proxy_version: "); out.append(" legacy_bluetooth_proxy_version: ");
sprintf(buffer, "%u", this->legacy_bluetooth_proxy_version); sprintf(buffer, "%" PRIu32, this->legacy_bluetooth_proxy_version);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append(" bluetooth_proxy_feature_flags: "); out.append(" bluetooth_proxy_feature_flags: ");
sprintf(buffer, "%u", this->bluetooth_proxy_feature_flags); sprintf(buffer, "%" PRIu32, this->bluetooth_proxy_feature_flags);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -806,7 +808,7 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" voice_assistant_version: "); out.append(" voice_assistant_version: ");
sprintf(buffer, "%u", this->voice_assistant_version); sprintf(buffer, "%" PRIu32, this->voice_assistant_version);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
@ -898,7 +900,7 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -966,7 +968,7 @@ void BinarySensorStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("BinarySensorStateResponse {\n"); out.append("BinarySensorStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -1069,7 +1071,7 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -1159,7 +1161,7 @@ void CoverStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("CoverStateResponse {\n"); out.append("CoverStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -1242,7 +1244,7 @@ void CoverCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("CoverCommandRequest {\n"); out.append("CoverCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -1362,7 +1364,7 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -1387,7 +1389,7 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" supported_speed_count: "); out.append(" supported_speed_count: ");
sprintf(buffer, "%d", this->supported_speed_count); sprintf(buffer, "%" PRId32, this->supported_speed_count);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -1454,7 +1456,7 @@ void FanStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("FanStateResponse {\n"); out.append("FanStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -1475,7 +1477,7 @@ void FanStateResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" speed_level: "); out.append(" speed_level: ");
sprintf(buffer, "%d", this->speed_level); sprintf(buffer, "%" PRId32, this->speed_level);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
@ -1555,7 +1557,7 @@ void FanCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("FanCommandRequest {\n"); out.append("FanCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -1596,7 +1598,7 @@ void FanCommandRequest::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" speed_level: "); out.append(" speed_level: ");
sprintf(buffer, "%d", this->speed_level); sprintf(buffer, "%" PRId32, this->speed_level);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
@ -1710,7 +1712,7 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -1864,7 +1866,7 @@ void LightStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("LightStateResponse {\n"); out.append("LightStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -2087,7 +2089,7 @@ void LightCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("LightCommandRequest {\n"); out.append("LightCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -2185,7 +2187,7 @@ void LightCommandRequest::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" transition_length: "); out.append(" transition_length: ");
sprintf(buffer, "%u", this->transition_length); sprintf(buffer, "%" PRIu32, this->transition_length);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -2194,7 +2196,7 @@ void LightCommandRequest::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" flash_length: "); out.append(" flash_length: ");
sprintf(buffer, "%u", this->flash_length); sprintf(buffer, "%" PRIu32, this->flash_length);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -2302,7 +2304,7 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -2323,7 +2325,7 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" accuracy_decimals: "); out.append(" accuracy_decimals: ");
sprintf(buffer, "%d", this->accuracy_decimals); sprintf(buffer, "%" PRId32, this->accuracy_decimals);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -2387,7 +2389,7 @@ void SensorStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("SensorStateResponse {\n"); out.append("SensorStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -2476,7 +2478,7 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -2539,7 +2541,7 @@ void SwitchStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("SwitchStateResponse {\n"); out.append("SwitchStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -2578,7 +2580,7 @@ void SwitchCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("SwitchCommandRequest {\n"); out.append("SwitchCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -2652,7 +2654,7 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -2718,7 +2720,7 @@ void TextSensorStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("TextSensorStateResponse {\n"); out.append("TextSensorStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -3025,7 +3027,7 @@ void GetTimeResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("GetTimeResponse {\n"); out.append("GetTimeResponse {\n");
out.append(" epoch_seconds: "); out.append(" epoch_seconds: ");
sprintf(buffer, "%u", this->epoch_seconds); sprintf(buffer, "%" PRIu32, this->epoch_seconds);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
@ -3109,7 +3111,7 @@ void ListEntitiesServicesResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -3203,7 +3205,7 @@ void ExecuteServiceArgument::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" legacy_int: "); out.append(" legacy_int: ");
sprintf(buffer, "%d", this->legacy_int); sprintf(buffer, "%" PRId32, this->legacy_int);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -3217,7 +3219,7 @@ void ExecuteServiceArgument::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" int_: "); out.append(" int_: ");
sprintf(buffer, "%d", this->int_); sprintf(buffer, "%" PRId32, this->int_);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -3229,7 +3231,7 @@ void ExecuteServiceArgument::dump_to(std::string &out) const {
for (const auto &it : this->int_array) { for (const auto &it : this->int_array) {
out.append(" int_array: "); out.append(" int_array: ");
sprintf(buffer, "%d", it); sprintf(buffer, "%" PRId32, it);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
} }
@ -3280,7 +3282,7 @@ void ExecuteServiceRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ExecuteServiceRequest {\n"); out.append("ExecuteServiceRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -3356,7 +3358,7 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -3422,7 +3424,7 @@ void CameraImageResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("CameraImageResponse {\n"); out.append("CameraImageResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -3614,7 +3616,7 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -3802,7 +3804,7 @@ void ClimateStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ClimateStateResponse {\n"); out.append("ClimateStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -3990,7 +3992,7 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ClimateCommandRequest {\n"); out.append("ClimateCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -4173,7 +4175,7 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -4260,7 +4262,7 @@ void NumberStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("NumberStateResponse {\n"); out.append("NumberStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -4298,7 +4300,7 @@ void NumberCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("NumberCommandRequest {\n"); out.append("NumberCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -4380,7 +4382,7 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -4452,7 +4454,7 @@ void SelectStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("SelectStateResponse {\n"); out.append("SelectStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -4495,7 +4497,7 @@ void SelectCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("SelectCommandRequest {\n"); out.append("SelectCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -4589,7 +4591,7 @@ void ListEntitiesLockResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -4660,7 +4662,7 @@ void LockStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("LockStateResponse {\n"); out.append("LockStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -4715,7 +4717,7 @@ void LockCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("LockCommandRequest {\n"); out.append("LockCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -4802,7 +4804,7 @@ void ListEntitiesButtonResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -4848,7 +4850,7 @@ void ButtonCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ButtonCommandRequest {\n"); out.append("ButtonCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
@ -4923,7 +4925,7 @@ void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -4992,7 +4994,7 @@ void MediaPlayerStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("MediaPlayerStateResponse {\n"); out.append("MediaPlayerStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -5071,7 +5073,7 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("MediaPlayerCommandRequest {\n"); out.append("MediaPlayerCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -5120,7 +5122,7 @@ void SubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("SubscribeBluetoothLEAdvertisementsRequest {\n"); out.append("SubscribeBluetoothLEAdvertisementsRequest {\n");
out.append(" flags: "); out.append(" flags: ");
sprintf(buffer, "%u", this->flags); sprintf(buffer, "%" PRIu32, this->flags);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
@ -5167,7 +5169,7 @@ void BluetoothServiceData::dump_to(std::string &out) const {
for (const auto &it : this->legacy_data) { for (const auto &it : this->legacy_data) {
out.append(" legacy_data: "); out.append(" legacy_data: ");
sprintf(buffer, "%u", it); sprintf(buffer, "%" PRIu32, it);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
} }
@ -5247,7 +5249,7 @@ void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" rssi: "); out.append(" rssi: ");
sprintf(buffer, "%d", this->rssi); sprintf(buffer, "%" PRId32, this->rssi);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -5270,7 +5272,7 @@ void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const {
} }
out.append(" address_type: "); out.append(" address_type: ");
sprintf(buffer, "%u", this->address_type); sprintf(buffer, "%" PRIu32, this->address_type);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
@ -5320,12 +5322,12 @@ void BluetoothLERawAdvertisement::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" rssi: "); out.append(" rssi: ");
sprintf(buffer, "%d", this->rssi); sprintf(buffer, "%" PRId32, this->rssi);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append(" address_type: "); out.append(" address_type: ");
sprintf(buffer, "%u", this->address_type); sprintf(buffer, "%" PRIu32, this->address_type);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -5408,7 +5410,7 @@ void BluetoothDeviceRequest::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" address_type: "); out.append(" address_type: ");
sprintf(buffer, "%u", this->address_type); sprintf(buffer, "%" PRIu32, this->address_type);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
@ -5456,12 +5458,12 @@ void BluetoothDeviceConnectionResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" mtu: "); out.append(" mtu: ");
sprintf(buffer, "%u", this->mtu); sprintf(buffer, "%" PRIu32, this->mtu);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append(" error: "); out.append(" error: ");
sprintf(buffer, "%d", this->error); sprintf(buffer, "%" PRId32, this->error);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
@ -5521,7 +5523,7 @@ void BluetoothGATTDescriptor::dump_to(std::string &out) const {
} }
out.append(" handle: "); out.append(" handle: ");
sprintf(buffer, "%u", this->handle); sprintf(buffer, "%" PRIu32, this->handle);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
@ -5577,12 +5579,12 @@ void BluetoothGATTCharacteristic::dump_to(std::string &out) const {
} }
out.append(" handle: "); out.append(" handle: ");
sprintf(buffer, "%u", this->handle); sprintf(buffer, "%" PRIu32, this->handle);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append(" properties: "); out.append(" properties: ");
sprintf(buffer, "%u", this->properties); sprintf(buffer, "%" PRIu32, this->properties);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -5639,7 +5641,7 @@ void BluetoothGATTService::dump_to(std::string &out) const {
} }
out.append(" handle: "); out.append(" handle: ");
sprintf(buffer, "%u", this->handle); sprintf(buffer, "%" PRIu32, this->handle);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -5746,7 +5748,7 @@ void BluetoothGATTReadRequest::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" handle: "); out.append(" handle: ");
sprintf(buffer, "%u", this->handle); sprintf(buffer, "%" PRIu32, this->handle);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
@ -5791,7 +5793,7 @@ void BluetoothGATTReadResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" handle: "); out.append(" handle: ");
sprintf(buffer, "%u", this->handle); sprintf(buffer, "%" PRIu32, this->handle);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -5845,7 +5847,7 @@ void BluetoothGATTWriteRequest::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" handle: "); out.append(" handle: ");
sprintf(buffer, "%u", this->handle); sprintf(buffer, "%" PRIu32, this->handle);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -5887,7 +5889,7 @@ void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" handle: "); out.append(" handle: ");
sprintf(buffer, "%u", this->handle); sprintf(buffer, "%" PRIu32, this->handle);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
@ -5932,7 +5934,7 @@ void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" handle: "); out.append(" handle: ");
sprintf(buffer, "%u", this->handle); sprintf(buffer, "%" PRIu32, this->handle);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -5975,7 +5977,7 @@ void BluetoothGATTNotifyRequest::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" handle: "); out.append(" handle: ");
sprintf(buffer, "%u", this->handle); sprintf(buffer, "%" PRIu32, this->handle);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -6024,7 +6026,7 @@ void BluetoothGATTNotifyDataResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" handle: "); out.append(" handle: ");
sprintf(buffer, "%u", this->handle); sprintf(buffer, "%" PRIu32, this->handle);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -6063,12 +6065,12 @@ void BluetoothConnectionsFreeResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("BluetoothConnectionsFreeResponse {\n"); out.append("BluetoothConnectionsFreeResponse {\n");
out.append(" free: "); out.append(" free: ");
sprintf(buffer, "%u", this->free); sprintf(buffer, "%" PRIu32, this->free);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append(" limit: "); out.append(" limit: ");
sprintf(buffer, "%u", this->limit); sprintf(buffer, "%" PRIu32, this->limit);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
@ -6107,12 +6109,12 @@ void BluetoothGATTErrorResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" handle: "); out.append(" handle: ");
sprintf(buffer, "%u", this->handle); sprintf(buffer, "%" PRIu32, this->handle);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append(" error: "); out.append(" error: ");
sprintf(buffer, "%d", this->error); sprintf(buffer, "%" PRId32, this->error);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
@ -6146,7 +6148,7 @@ void BluetoothGATTWriteResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" handle: "); out.append(" handle: ");
sprintf(buffer, "%u", this->handle); sprintf(buffer, "%" PRIu32, this->handle);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
@ -6180,7 +6182,7 @@ void BluetoothGATTNotifyResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" handle: "); out.append(" handle: ");
sprintf(buffer, "%u", this->handle); sprintf(buffer, "%" PRIu32, this->handle);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
@ -6223,7 +6225,7 @@ void BluetoothDevicePairingResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" error: "); out.append(" error: ");
sprintf(buffer, "%d", this->error); sprintf(buffer, "%" PRId32, this->error);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
@ -6266,7 +6268,7 @@ void BluetoothDeviceUnpairingResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" error: "); out.append(" error: ");
sprintf(buffer, "%d", this->error); sprintf(buffer, "%" PRId32, this->error);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
@ -6315,7 +6317,7 @@ void BluetoothDeviceClearCacheResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" error: "); out.append(" error: ");
sprintf(buffer, "%d", this->error); sprintf(buffer, "%" PRId32, this->error);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
@ -6348,6 +6350,10 @@ bool VoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
this->start = value.as_bool(); this->start = value.as_bool();
return true; return true;
} }
case 3: {
this->use_vad = value.as_bool();
return true;
}
default: default:
return false; return false;
} }
@ -6365,6 +6371,7 @@ bool VoiceAssistantRequest::decode_length(uint32_t field_id, ProtoLengthDelimite
void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(1, this->start); buffer.encode_bool(1, this->start);
buffer.encode_string(2, this->conversation_id); buffer.encode_string(2, this->conversation_id);
buffer.encode_bool(3, this->use_vad);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void VoiceAssistantRequest::dump_to(std::string &out) const { void VoiceAssistantRequest::dump_to(std::string &out) const {
@ -6377,6 +6384,10 @@ void VoiceAssistantRequest::dump_to(std::string &out) const {
out.append(" conversation_id: "); out.append(" conversation_id: ");
out.append("'").append(this->conversation_id).append("'"); out.append("'").append(this->conversation_id).append("'");
out.append("\n"); out.append("\n");
out.append(" use_vad: ");
out.append(YESNO(this->use_vad));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -6403,7 +6414,7 @@ void VoiceAssistantResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("VoiceAssistantResponse {\n"); out.append("VoiceAssistantResponse {\n");
out.append(" port: "); out.append(" port: ");
sprintf(buffer, "%u", this->port); sprintf(buffer, "%" PRIu32, this->port);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -6566,7 +6577,7 @@ void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -6591,7 +6602,7 @@ void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append(" supported_features: "); out.append(" supported_features: ");
sprintf(buffer, "%u", this->supported_features); sprintf(buffer, "%" PRIu32, this->supported_features);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -6634,7 +6645,7 @@ void AlarmControlPanelStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("AlarmControlPanelStateResponse {\n"); out.append("AlarmControlPanelStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -6684,7 +6695,7 @@ void AlarmControlPanelCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("AlarmControlPanelCommandRequest {\n"); out.append("AlarmControlPanelCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");

View file

@ -1655,6 +1655,7 @@ class VoiceAssistantRequest : public ProtoMessage {
public: public:
bool start{false}; bool start{false};
std::string conversation_id{}; std::string conversation_id{};
bool use_vad{false};
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

@ -323,16 +323,16 @@ void APIServer::on_shutdown() {
} }
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
bool APIServer::start_voice_assistant(const std::string &conversation_id) { bool APIServer::start_voice_assistant(const std::string &conversation_id, bool use_vad) {
for (auto &c : this->clients_) { for (auto &c : this->clients_) {
if (c->request_voice_assistant(true, conversation_id)) if (c->request_voice_assistant(true, conversation_id, use_vad))
return true; return true;
} }
return false; return false;
} }
void APIServer::stop_voice_assistant() { void APIServer::stop_voice_assistant() {
for (auto &c : this->clients_) { for (auto &c : this->clients_) {
if (c->request_voice_assistant(false, "")) if (c->request_voice_assistant(false, "", false))
return; return;
} }
} }

View file

@ -81,7 +81,7 @@ class APIServer : public Component, public Controller {
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
bool start_voice_assistant(const std::string &conversation_id); bool start_voice_assistant(const std::string &conversation_id, bool use_vad);
void stop_voice_assistant(); void stop_voice_assistant();
#endif #endif

View file

@ -47,7 +47,7 @@ async def async_run_logs(config, address):
except APIConnectionError: except APIConnectionError:
cli.disconnect() cli.disconnect()
async def on_disconnect(): async def on_disconnect(expected_disconnect: bool) -> None:
_LOGGER.warning("Disconnected from API") _LOGGER.warning("Disconnected from API")
zc = zeroconf.Zeroconf() zc = zeroconf.Zeroconf()

View file

@ -31,12 +31,10 @@ CONFIG_SCHEMA = cv.Schema(
async def to_code(config): async def to_code(config):
hub = await cg.get_variable(config[CONF_AS3935_ID]) hub = await cg.get_variable(config[CONF_AS3935_ID])
if CONF_DISTANCE in config: if distance_config := config.get(CONF_DISTANCE):
conf = config[CONF_DISTANCE] sens = await sensor.new_sensor(distance_config)
distance_sensor = await sensor.new_sensor(conf) cg.add(hub.set_distance_sensor(sens))
cg.add(hub.set_distance_sensor(distance_sensor))
if CONF_LIGHTNING_ENERGY in config: if lightning_energy_config := config.get(CONF_LIGHTNING_ENERGY):
conf = config[CONF_LIGHTNING_ENERGY] sens = await sensor.new_sensor(lightning_energy_config)
lightning_energy_sensor = await sensor.new_sensor(conf) cg.add(hub.set_energy_sensor(sens))
cg.add(hub.set_energy_sensor(lightning_energy_sensor))

View file

@ -107,6 +107,6 @@ async def to_code(config):
cg.add(var.set_astep(config[CONF_ASTEP])) cg.add(var.set_astep(config[CONF_ASTEP]))
for conf_id, set_sensor_func in SENSORS.items(): for conf_id, set_sensor_func in SENSORS.items():
if conf_id in config: if sens_config := config.get(conf_id):
sens = await sensor.new_sensor(config[conf_id]) sens = await sensor.new_sensor(sens_config)
cg.add(getattr(var, set_sensor_func)(sens)) cg.add(getattr(var, set_sensor_func)(sens))

View file

@ -8,15 +8,15 @@ CODEOWNERS = ["@OttoWinter"]
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
cv.Schema({}), cv.Schema({}),
cv.only_with_arduino, cv.only_with_arduino,
cv.only_on(["esp32", "esp8266"]), cv.only_on(["esp32", "esp8266", "bk72xx", "rtl87xx"]),
) )
@coroutine_with_priority(200.0) @coroutine_with_priority(200.0)
async def to_code(config): async def to_code(config):
if CORE.is_esp32: if CORE.is_esp32 or CORE.is_libretiny:
# https://github.com/esphome/AsyncTCP/blob/master/library.json # https://github.com/esphome/AsyncTCP/blob/master/library.json
cg.add_library("esphome/AsyncTCP-esphome", "1.2.2") cg.add_library("esphome/AsyncTCP-esphome", "2.0.1")
elif CORE.is_esp8266: elif CORE.is_esp8266:
# https://github.com/esphome/ESPAsyncTCP # https://github.com/esphome/ESPAsyncTCP
cg.add_library("esphome/ESPAsyncTCP-esphome", "1.2.3") cg.add_library("esphome/ESPAsyncTCP-esphome", "2.0.0")

View file

@ -83,18 +83,18 @@ async def to_code(config):
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
if CONF_TEMPERATURE in config: if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature(sens)) cg.add(var.set_temperature(sens))
if CONF_HUMIDITY in config: if humidity_config := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(config[CONF_HUMIDITY]) sens = await sensor.new_sensor(humidity_config)
cg.add(var.set_humidity(sens)) cg.add(var.set_humidity(sens))
if CONF_BATTERY_LEVEL in config: if battery_level_config := config.get(CONF_BATTERY_LEVEL):
sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL]) sens = await sensor.new_sensor(battery_level_config)
cg.add(var.set_battery_level(sens)) cg.add(var.set_battery_level(sens))
if CONF_BATTERY_VOLTAGE in config: if battery_voltage_config := config.get(CONF_BATTERY_VOLTAGE):
sens = await sensor.new_sensor(config[CONF_BATTERY_VOLTAGE]) sens = await sensor.new_sensor(battery_voltage_config)
cg.add(var.set_battery_voltage(sens)) cg.add(var.set_battery_voltage(sens))
if CONF_SIGNAL_STRENGTH in config: if signal_strength_config := config.get(CONF_SIGNAL_STRENGTH):
sens = await sensor.new_sensor(config[CONF_SIGNAL_STRENGTH]) sens = await sensor.new_sensor(signal_strength_config)
cg.add(var.set_signal_strength(sens)) cg.add(var.set_signal_strength(sens))

View file

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

View file

@ -0,0 +1,235 @@
#include "atm90e26.h"
#include "atm90e26_reg.h"
#include "esphome/core/log.h"
namespace esphome {
namespace atm90e26 {
static const char *const TAG = "atm90e26";
void ATM90E26Component::update() {
if (this->read16_(ATM90E26_REGISTER_FUNCEN) != 0x0030) {
this->status_set_warning();
return;
}
if (this->voltage_sensor_ != nullptr) {
this->voltage_sensor_->publish_state(this->get_line_voltage_());
}
if (this->current_sensor_ != nullptr) {
this->current_sensor_->publish_state(this->get_line_current_());
}
if (this->power_sensor_ != nullptr) {
this->power_sensor_->publish_state(this->get_active_power_());
}
if (this->reactive_power_sensor_ != nullptr) {
this->reactive_power_sensor_->publish_state(this->get_reactive_power_());
}
if (this->power_factor_sensor_ != nullptr) {
this->power_factor_sensor_->publish_state(this->get_power_factor_());
}
if (this->forward_active_energy_sensor_ != nullptr) {
this->forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_());
}
if (this->reverse_active_energy_sensor_ != nullptr) {
this->reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_());
}
if (this->freq_sensor_ != nullptr) {
this->freq_sensor_->publish_state(this->get_frequency_());
}
this->status_clear_warning();
}
void ATM90E26Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up ATM90E26 Component...");
this->spi_setup();
uint16_t mmode = 0x422; // default values for everything but L/N line current gains
mmode |= (gain_pga_ & 0x7) << 13;
mmode |= (n_line_gain_ & 0x3) << 11;
this->write16_(ATM90E26_REGISTER_SOFTRESET, 0x789A); // Perform soft reset
this->write16_(ATM90E26_REGISTER_FUNCEN,
0x0030); // Voltage sag irq=1, report on warnout pin=1, energy dir change irq=0
uint16_t read = this->read16_(ATM90E26_REGISTER_LASTDATA);
if (read != 0x0030) {
ESP_LOGW(TAG, "Could not initialize ATM90E26 IC, check SPI settings: %d", read);
this->mark_failed();
return;
}
// TODO: 100 * <nominal voltage, e.g. 230> * sqrt(2) * <fraction of nominal, e.g. 0.9> / (4 * gain_voltage/32768)
this->write16_(ATM90E26_REGISTER_SAGTH, 0x17DD); // Voltage sag threshhold 0x1F2F
// Set metering calibration values
this->write16_(ATM90E26_REGISTER_CALSTART, 0x5678); // CAL Metering calibration startup command
// Configure
this->write16_(ATM90E26_REGISTER_MMODE, mmode); // Metering Mode Configuration (see above)
this->write16_(ATM90E26_REGISTER_PLCONSTH, (pl_const_ >> 16)); // PL Constant MSB
this->write16_(ATM90E26_REGISTER_PLCONSTL, pl_const_ & 0xFFFF); // PL Constant LSB
// Calibrate this to be 1 pulse per Wh
this->write16_(ATM90E26_REGISTER_LGAIN, gain_metering_); // L Line Calibration Gain (active power metering)
this->write16_(ATM90E26_REGISTER_LPHI, 0x0000); // L Line Calibration Angle
this->write16_(ATM90E26_REGISTER_NGAIN, 0x0000); // N Line Calibration Gain
this->write16_(ATM90E26_REGISTER_NPHI, 0x0000); // N Line Calibration Angle
this->write16_(ATM90E26_REGISTER_PSTARTTH, 0x08BD); // Active Startup Power Threshold (default) = 2237
this->write16_(ATM90E26_REGISTER_PNOLTH, 0x0000); // Active No-Load Power Threshold
this->write16_(ATM90E26_REGISTER_QSTARTTH, 0x0AEC); // Reactive Startup Power Threshold (default) = 2796
this->write16_(ATM90E26_REGISTER_QNOLTH, 0x0000); // Reactive No-Load Power Threshold
// Compute Checksum for the registers we set above
// low byte = sum of all bytes
uint16_t cs =
((mmode >> 8) + (mmode & 0xFF) + (pl_const_ >> 24) + ((pl_const_ >> 16) & 0xFF) + ((pl_const_ >> 8) & 0xFF) +
(pl_const_ & 0xFF) + (gain_metering_ >> 8) + (gain_metering_ & 0xFF) + 0x08 + 0xBD + 0x0A + 0xEC) &
0xFF;
// high byte = XOR of all bytes
cs |= ((mmode >> 8) ^ (mmode & 0xFF) ^ (pl_const_ >> 24) ^ ((pl_const_ >> 16) & 0xFF) ^ ((pl_const_ >> 8) & 0xFF) ^
(pl_const_ & 0xFF) ^ (gain_metering_ >> 8) ^ (gain_metering_ & 0xFF) ^ 0x08 ^ 0xBD ^ 0x0A ^ 0xEC)
<< 8;
this->write16_(ATM90E26_REGISTER_CS1, cs);
ESP_LOGVV(TAG, "Set CS1 to: 0x%04X", cs);
// Set measurement calibration values
this->write16_(ATM90E26_REGISTER_ADJSTART, 0x5678); // Measurement calibration startup command, registers 31-3A
this->write16_(ATM90E26_REGISTER_UGAIN, gain_voltage_); // Voltage RMS gain
this->write16_(ATM90E26_REGISTER_IGAINL, gain_ct_); // L line current RMS gain
this->write16_(ATM90E26_REGISTER_IGAINN, 0x7530); // N Line Current RMS Gain
this->write16_(ATM90E26_REGISTER_UOFFSET, 0x0000); // Voltage Offset
this->write16_(ATM90E26_REGISTER_IOFFSETL, 0x0000); // L Line Current Offset
this->write16_(ATM90E26_REGISTER_IOFFSETN, 0x0000); // N Line Current Offse
this->write16_(ATM90E26_REGISTER_POFFSETL, 0x0000); // L Line Active Power Offset
this->write16_(ATM90E26_REGISTER_QOFFSETL, 0x0000); // L Line Reactive Power Offset
this->write16_(ATM90E26_REGISTER_POFFSETN, 0x0000); // N Line Active Power Offset
this->write16_(ATM90E26_REGISTER_QOFFSETN, 0x0000); // N Line Reactive Power Offset
// Compute Checksum for the registers we set above
cs = ((gain_voltage_ >> 8) + (gain_voltage_ & 0xFF) + (gain_ct_ >> 8) + (gain_ct_ & 0xFF) + 0x75 + 0x30) & 0xFF;
cs |= ((gain_voltage_ >> 8) ^ (gain_voltage_ & 0xFF) ^ (gain_ct_ >> 8) ^ (gain_ct_ & 0xFF) ^ 0x75 ^ 0x30) << 8;
this->write16_(ATM90E26_REGISTER_CS2, cs);
ESP_LOGVV(TAG, "Set CS2 to: 0x%04X", cs);
this->write16_(ATM90E26_REGISTER_CALSTART,
0x8765); // Checks correctness of 21-2B registers and starts normal metering if ok
this->write16_(ATM90E26_REGISTER_ADJSTART,
0x8765); // Checks correctness of 31-3A registers and starts normal measurement if ok
uint16_t sys_status = this->read16_(ATM90E26_REGISTER_SYSSTATUS);
if (sys_status & 0xC000) { // Checksum 1 Error
ESP_LOGW(TAG, "Could not initialize ATM90E26 IC: CS1 was incorrect, expected: 0x%04X",
this->read16_(ATM90E26_REGISTER_CS1));
this->mark_failed();
}
if (sys_status & 0x3000) { // Checksum 2 Error
ESP_LOGW(TAG, "Could not initialize ATM90E26 IC: CS2 was incorrect, expected: 0x%04X",
this->read16_(ATM90E26_REGISTER_CS2));
this->mark_failed();
}
}
void ATM90E26Component::dump_config() {
ESP_LOGCONFIG("", "ATM90E26:");
LOG_PIN(" CS Pin: ", this->cs_);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with ATM90E26 failed!");
}
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Voltage A", this->voltage_sensor_);
LOG_SENSOR(" ", "Current A", this->current_sensor_);
LOG_SENSOR(" ", "Power A", this->power_sensor_);
LOG_SENSOR(" ", "Reactive Power A", this->reactive_power_sensor_);
LOG_SENSOR(" ", "PF A", this->power_factor_sensor_);
LOG_SENSOR(" ", "Active Forward Energy A", this->forward_active_energy_sensor_);
LOG_SENSOR(" ", "Active Reverse Energy A", this->reverse_active_energy_sensor_);
LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
}
float ATM90E26Component::get_setup_priority() const { return setup_priority::DATA; }
uint16_t ATM90E26Component::read16_(uint8_t a_register) {
uint8_t data[2];
uint16_t output;
this->enable();
delayMicroseconds(4);
this->write_byte(a_register | 0x80);
delayMicroseconds(4);
this->read_array(data, 2);
this->disable();
output = (uint16_t(data[0] & 0xFF) << 8) | (data[1] & 0xFF);
ESP_LOGVV(TAG, "read16_ 0x%04X output 0x%04X", a_register, output);
return output;
}
void ATM90E26Component::write16_(uint8_t a_register, uint16_t val) {
ESP_LOGVV(TAG, "write16_ 0x%04X val 0x%04X", a_register, val);
this->enable();
delayMicroseconds(4);
this->write_byte(a_register & 0x7F);
delayMicroseconds(4);
this->write_byte((val >> 8) & 0xFF);
this->write_byte(val & 0xFF);
this->disable();
}
float ATM90E26Component::get_line_current_() {
uint16_t current = this->read16_(ATM90E26_REGISTER_IRMS);
return current / 1000.0f;
}
float ATM90E26Component::get_line_voltage_() {
uint16_t voltage = this->read16_(ATM90E26_REGISTER_URMS);
return voltage / 100.0f;
}
float ATM90E26Component::get_active_power_() {
int16_t val = this->read16_(ATM90E26_REGISTER_PMEAN); // two's complement
return (float) val;
}
float ATM90E26Component::get_reactive_power_() {
int16_t val = this->read16_(ATM90E26_REGISTER_QMEAN); // two's complement
return (float) val;
}
float ATM90E26Component::get_power_factor_() {
uint16_t val = this->read16_(ATM90E26_REGISTER_POWERF); // signed
if (val & 0x8000) {
return -(val & 0x7FF) / 1000.0f;
} else {
return val / 1000.0f;
}
}
float ATM90E26Component::get_forward_active_energy_() {
uint16_t val = this->read16_(ATM90E26_REGISTER_APENERGY);
if ((UINT32_MAX - this->cumulative_forward_active_energy_) > val) {
this->cumulative_forward_active_energy_ += val;
} else {
this->cumulative_forward_active_energy_ = val;
}
// The register holds thenths of pulses, we want to output Wh
return (this->cumulative_forward_active_energy_ * 100.0f / meter_constant_);
}
float ATM90E26Component::get_reverse_active_energy_() {
uint16_t val = this->read16_(ATM90E26_REGISTER_ANENERGY);
if (UINT32_MAX - this->cumulative_reverse_active_energy_ > val) {
this->cumulative_reverse_active_energy_ += val;
} else {
this->cumulative_reverse_active_energy_ = val;
}
return (this->cumulative_reverse_active_energy_ * 100.0f / meter_constant_);
}
float ATM90E26Component::get_frequency_() {
uint16_t freq = this->read16_(ATM90E26_REGISTER_FREQ);
return freq / 100.0f;
}
} // namespace atm90e26
} // namespace esphome

View file

@ -0,0 +1,72 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/spi/spi.h"
namespace esphome {
namespace atm90e26 {
class ATM90E26Component : public PollingComponent,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_200KHZ> {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void set_voltage_sensor(sensor::Sensor *obj) { this->voltage_sensor_ = obj; }
void set_current_sensor(sensor::Sensor *obj) { this->current_sensor_ = obj; }
void set_power_sensor(sensor::Sensor *obj) { this->power_sensor_ = obj; }
void set_reactive_power_sensor(sensor::Sensor *obj) { this->reactive_power_sensor_ = obj; }
void set_forward_active_energy_sensor(sensor::Sensor *obj) { this->forward_active_energy_sensor_ = obj; }
void set_reverse_active_energy_sensor(sensor::Sensor *obj) { this->reverse_active_energy_sensor_ = obj; }
void set_power_factor_sensor(sensor::Sensor *obj) { this->power_factor_sensor_ = obj; }
void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; }
void set_line_freq(int freq) { line_freq_ = freq; }
void set_meter_constant(float val) { meter_constant_ = val; }
void set_pl_const(uint32_t pl_const) { pl_const_ = pl_const; }
void set_gain_metering(uint16_t gain) { this->gain_metering_ = gain; }
void set_gain_voltage(uint16_t gain) { this->gain_voltage_ = gain; }
void set_gain_ct(uint16_t gain) { this->gain_ct_ = gain; }
void set_gain_pga(uint16_t gain) { gain_pga_ = gain; }
void set_n_line_gain(uint16_t gain) { n_line_gain_ = gain; }
protected:
uint16_t read16_(uint8_t a_register);
int read32_(uint8_t addr_h, uint8_t addr_l);
void write16_(uint8_t a_register, uint16_t val);
float get_line_voltage_();
float get_line_current_();
float get_active_power_();
float get_reactive_power_();
float get_power_factor_();
float get_forward_active_energy_();
float get_reverse_active_energy_();
float get_frequency_();
float get_chip_temperature_();
sensor::Sensor *freq_sensor_{nullptr};
sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *power_sensor_{nullptr};
sensor::Sensor *reactive_power_sensor_{nullptr};
sensor::Sensor *power_factor_sensor_{nullptr};
sensor::Sensor *forward_active_energy_sensor_{nullptr};
sensor::Sensor *reverse_active_energy_sensor_{nullptr};
uint32_t cumulative_forward_active_energy_{0};
uint32_t cumulative_reverse_active_energy_{0};
uint16_t gain_metering_{7481};
uint16_t gain_voltage_{26400};
uint16_t gain_ct_{31251};
uint16_t gain_pga_{0x4};
uint16_t n_line_gain_{0x2};
int line_freq_{60};
float meter_constant_{3200.0f};
uint32_t pl_const_{1429876};
};
} // namespace atm90e26
} // namespace esphome

View file

@ -0,0 +1,70 @@
#pragma once
namespace esphome {
namespace atm90e26 {
/* Status and Special Register */
static const uint8_t ATM90E26_REGISTER_SOFTRESET = 0x00; // Software Reset
static const uint8_t ATM90E26_REGISTER_SYSSTATUS = 0x01; // System Status
static const uint8_t ATM90E26_REGISTER_FUNCEN = 0x02; // Function Enable
static const uint8_t ATM90E26_REGISTER_SAGTH = 0x03; // Voltage Sag Threshold
static const uint8_t ATM90E26_REGISTER_SMALLPMOD = 0x04; // Small-Power Mode
static const uint8_t ATM90E26_REGISTER_LASTDATA = 0x06; // Last Read/Write SPI/UART Value
/* Metering Calibration and Configuration Register */
static const uint8_t ATM90E26_REGISTER_LSB = 0x08; // RMS/Power 16-bit LSB
static const uint8_t ATM90E26_REGISTER_CALSTART = 0x20; // Calibration Start Command
static const uint8_t ATM90E26_REGISTER_PLCONSTH = 0x21; // High Word of PL_Constant
static const uint8_t ATM90E26_REGISTER_PLCONSTL = 0x22; // Low Word of PL_Constant
static const uint8_t ATM90E26_REGISTER_LGAIN = 0x23; // L Line Calibration Gain
static const uint8_t ATM90E26_REGISTER_LPHI = 0x24; // L Line Calibration Angle
static const uint8_t ATM90E26_REGISTER_NGAIN = 0x25; // N Line Calibration Gain
static const uint8_t ATM90E26_REGISTER_NPHI = 0x26; // N Line Calibration Angle
static const uint8_t ATM90E26_REGISTER_PSTARTTH = 0x27; // Active Startup Power Threshold
static const uint8_t ATM90E26_REGISTER_PNOLTH = 0x28; // Active No-Load Power Threshold
static const uint8_t ATM90E26_REGISTER_QSTARTTH = 0x29; // Reactive Startup Power Threshold
static const uint8_t ATM90E26_REGISTER_QNOLTH = 0x2A; // Reactive No-Load Power Threshold
static const uint8_t ATM90E26_REGISTER_MMODE = 0x2B; // Metering Mode Configuration
static const uint8_t ATM90E26_REGISTER_CS1 = 0x2C; // Checksum 1
/* Measurement Calibration Register */
static const uint8_t ATM90E26_REGISTER_ADJSTART = 0x30; // Measurement Calibration Start Command
static const uint8_t ATM90E26_REGISTER_UGAIN = 0x31; // Voltage RMS Gain
static const uint8_t ATM90E26_REGISTER_IGAINL = 0x32; // L Line Current RMS Gain
static const uint8_t ATM90E26_REGISTER_IGAINN = 0x33; // N Line Current RMS Gain
static const uint8_t ATM90E26_REGISTER_UOFFSET = 0x34; // Voltage Offset
static const uint8_t ATM90E26_REGISTER_IOFFSETL = 0x35; // L Line Current Offset
static const uint8_t ATM90E26_REGISTER_IOFFSETN = 0x36; // N Line Current Offse
static const uint8_t ATM90E26_REGISTER_POFFSETL = 0x37; // L Line Active Power Offset
static const uint8_t ATM90E26_REGISTER_QOFFSETL = 0x38; // L Line Reactive Power Offset
static const uint8_t ATM90E26_REGISTER_POFFSETN = 0x39; // N Line Active Power Offset
static const uint8_t ATM90E26_REGISTER_QOFFSETN = 0x3A; // N Line Reactive Power Offset
static const uint8_t ATM90E26_REGISTER_CS2 = 0x3B; // Checksum 2
/* Energy Register */
static const uint8_t ATM90E26_REGISTER_APENERGY = 0x40; // Forward Active Energy
static const uint8_t ATM90E26_REGISTER_ANENERGY = 0x41; // Reverse Active Energy
static const uint8_t ATM90E26_REGISTER_ATENERGY = 0x42; // Absolute Active Energy
static const uint8_t ATM90E26_REGISTER_RPENERGY = 0x43; // Forward (Inductive) Reactive Energy
static const uint8_t ATM90E26_REGISTER_RNENERG = 0x44; // Reverse (Capacitive) Reactive Energy
static const uint8_t ATM90E26_REGISTER_RTENERGY = 0x45; // Absolute Reactive Energy
static const uint8_t ATM90E26_REGISTER_ENSTATUS = 0x46; // Metering Status
/* Measurement Register */
static const uint8_t ATM90E26_REGISTER_IRMS = 0x48; // L Line Current RMS
static const uint8_t ATM90E26_REGISTER_URMS = 0x49; // Voltage RMS
static const uint8_t ATM90E26_REGISTER_PMEAN = 0x4A; // L Line Mean Active Power
static const uint8_t ATM90E26_REGISTER_QMEAN = 0x4B; // L Line Mean Reactive Power
static const uint8_t ATM90E26_REGISTER_FREQ = 0x4C; // Voltage Frequency
static const uint8_t ATM90E26_REGISTER_POWERF = 0x4D; // L Line Power Factor
static const uint8_t ATM90E26_REGISTER_PANGLE = 0x4E; // Phase Angle between Voltage and L Line Current
static const uint8_t ATM90E26_REGISTER_SMEAN = 0x4F; // L Line Mean Apparent Power
static const uint8_t ATM90E26_REGISTER_IRMS2 = 0x68; // N Line Current rms
static const uint8_t ATM90E26_REGISTER_PMEAN2 = 0x6A; // N Line Mean Active Power
static const uint8_t ATM90E26_REGISTER_QMEAN2 = 0x6B; // N Line Mean Reactive Power
static const uint8_t ATM90E26_REGISTER_POWERF2 = 0x6D; // N Line Power Factor
static const uint8_t ATM90E26_REGISTER_PANGLE2 = 0x6E; // Phase Angle between Voltage and N Line Current
static const uint8_t ATM90E26_REGISTER_SMEAN2 = 0x6F; // N Line Mean Apparent Power
} // namespace atm90e26
} // namespace esphome

View file

@ -0,0 +1,157 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, spi
from esphome.const import (
CONF_ID,
CONF_REACTIVE_POWER,
CONF_VOLTAGE,
CONF_CURRENT,
CONF_POWER,
CONF_POWER_FACTOR,
CONF_FREQUENCY,
CONF_FORWARD_ACTIVE_ENERGY,
CONF_REVERSE_ACTIVE_ENERGY,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_VOLTAGE,
ICON_LIGHTBULB,
ICON_CURRENT_AC,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_HERTZ,
UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
UNIT_VOLT_AMPS_REACTIVE,
UNIT_WATT_HOURS,
)
CONF_LINE_FREQUENCY = "line_frequency"
CONF_METER_CONSTANT = "meter_constant"
CONF_PL_CONST = "pl_const"
CONF_GAIN_PGA = "gain_pga"
CONF_GAIN_METERING = "gain_metering"
CONF_GAIN_VOLTAGE = "gain_voltage"
CONF_GAIN_CT = "gain_ct"
LINE_FREQS = {
"50HZ": 50,
"60HZ": 60,
}
PGA_GAINS = {
"1X": 0x4,
"4X": 0x0,
"8X": 0x1,
"16X": 0x2,
"24X": 0x3,
}
atm90e26_ns = cg.esphome_ns.namespace("atm90e26")
ATM90E26Component = atm90e26_ns.class_(
"ATM90E26Component", cg.PollingComponent, spi.SPIDevice
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(ATM90E26Component),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
icon=ICON_LIGHTBULB,
accuracy_decimals=2,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER_FACTOR,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
unit_of_measurement=UNIT_HERTZ,
icon=ICON_CURRENT_AC,
accuracy_decimals=1,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
cv.Required(CONF_METER_CONSTANT): cv.positive_float,
cv.Optional(CONF_PL_CONST, default=1429876): cv.uint32_t,
cv.Optional(CONF_GAIN_METERING, default=7481): cv.uint16_t,
cv.Optional(CONF_GAIN_VOLTAGE, default=26400): cv.int_range(
min=0, max=32767
),
cv.Optional(CONF_GAIN_CT, default=31251): cv.uint16_t,
cv.Optional(CONF_GAIN_PGA, default="1X"): cv.enum(PGA_GAINS, upper=True),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(spi.spi_device_schema())
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await spi.register_spi_device(var, config)
if voltage_config := config.get(CONF_VOLTAGE):
sens = await sensor.new_sensor(voltage_config)
cg.add(var.set_voltage_sensor(sens))
if current_config := config.get(CONF_CURRENT):
sens = await sensor.new_sensor(current_config)
cg.add(var.set_current_sensor(sens))
if power_config := config.get(CONF_POWER):
sens = await sensor.new_sensor(power_config)
cg.add(var.set_power_sensor(sens))
if reactive_power_config := config.get(CONF_REACTIVE_POWER):
sens = await sensor.new_sensor(reactive_power_config)
cg.add(var.set_reactive_power_sensor(sens))
if power_factor_config := config.get(CONF_POWER_FACTOR):
sens = await sensor.new_sensor(power_factor_config)
cg.add(var.set_power_factor_sensor(sens))
if forward_active_energy_config := config.get(CONF_FORWARD_ACTIVE_ENERGY):
sens = await sensor.new_sensor(forward_active_energy_config)
cg.add(var.set_forward_active_energy_sensor(sens))
if reverse_active_energy_config := config.get(CONF_REVERSE_ACTIVE_ENERGY):
sens = await sensor.new_sensor(reverse_active_energy_config)
cg.add(var.set_reverse_active_energy_sensor(sens))
if frequency_config := config.get(CONF_FREQUENCY):
sens = await sensor.new_sensor(frequency_config)
cg.add(var.set_freq_sensor(sens))
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
cg.add(var.set_meter_constant(config[CONF_METER_CONSTANT]))
cg.add(var.set_pl_const(config[CONF_PL_CONST]))
cg.add(var.set_gain_metering(config[CONF_GAIN_METERING]))
cg.add(var.set_gain_voltage(config[CONF_GAIN_VOLTAGE]))
cg.add(var.set_gain_ct(config[CONF_GAIN_CT]))
cg.add(var.set_gain_pga(config[CONF_GAIN_PGA]))

View file

@ -6,6 +6,9 @@ from esphome.const import (
CONF_REACTIVE_POWER, CONF_REACTIVE_POWER,
CONF_VOLTAGE, CONF_VOLTAGE,
CONF_CURRENT, CONF_CURRENT,
CONF_PHASE_A,
CONF_PHASE_B,
CONF_PHASE_C,
CONF_POWER, CONF_POWER,
CONF_POWER_FACTOR, CONF_POWER_FACTOR,
CONF_FREQUENCY, CONF_FREQUENCY,
@ -31,10 +34,6 @@ from esphome.const import (
UNIT_WATT_HOURS, UNIT_WATT_HOURS,
) )
CONF_PHASE_A = "phase_a"
CONF_PHASE_B = "phase_b"
CONF_PHASE_C = "phase_c"
CONF_LINE_FREQUENCY = "line_frequency" CONF_LINE_FREQUENCY = "line_frequency"
CONF_CHIP_TEMPERATURE = "chip_temperature" CONF_CHIP_TEMPERATURE = "chip_temperature"
CONF_GAIN_PGA = "gain_pga" CONF_GAIN_PGA = "gain_pga"
@ -151,33 +150,35 @@ async def to_code(config):
conf = config[phase] conf = config[phase]
cg.add(var.set_volt_gain(i, conf[CONF_GAIN_VOLTAGE])) cg.add(var.set_volt_gain(i, conf[CONF_GAIN_VOLTAGE]))
cg.add(var.set_ct_gain(i, conf[CONF_GAIN_CT])) cg.add(var.set_ct_gain(i, conf[CONF_GAIN_CT]))
if CONF_VOLTAGE in conf: if voltage_config := conf.get(CONF_VOLTAGE):
sens = await sensor.new_sensor(conf[CONF_VOLTAGE]) sens = await sensor.new_sensor(voltage_config)
cg.add(var.set_voltage_sensor(i, sens)) cg.add(var.set_voltage_sensor(i, sens))
if CONF_CURRENT in conf: if current_config := conf.get(CONF_CURRENT):
sens = await sensor.new_sensor(conf[CONF_CURRENT]) sens = await sensor.new_sensor(current_config)
cg.add(var.set_current_sensor(i, sens)) cg.add(var.set_current_sensor(i, sens))
if CONF_POWER in conf: if power_config := conf.get(CONF_POWER):
sens = await sensor.new_sensor(conf[CONF_POWER]) sens = await sensor.new_sensor(power_config)
cg.add(var.set_power_sensor(i, sens)) cg.add(var.set_power_sensor(i, sens))
if CONF_REACTIVE_POWER in conf: if reactive_power_config := conf.get(CONF_REACTIVE_POWER):
sens = await sensor.new_sensor(conf[CONF_REACTIVE_POWER]) sens = await sensor.new_sensor(reactive_power_config)
cg.add(var.set_reactive_power_sensor(i, sens)) cg.add(var.set_reactive_power_sensor(i, sens))
if CONF_POWER_FACTOR in conf: if power_factor_config := conf.get(CONF_POWER_FACTOR):
sens = await sensor.new_sensor(conf[CONF_POWER_FACTOR]) sens = await sensor.new_sensor(power_factor_config)
cg.add(var.set_power_factor_sensor(i, sens)) cg.add(var.set_power_factor_sensor(i, sens))
if CONF_FORWARD_ACTIVE_ENERGY in conf: if forward_active_energy_config := conf.get(CONF_FORWARD_ACTIVE_ENERGY):
sens = await sensor.new_sensor(conf[CONF_FORWARD_ACTIVE_ENERGY]) sens = await sensor.new_sensor(forward_active_energy_config)
cg.add(var.set_forward_active_energy_sensor(i, sens)) cg.add(var.set_forward_active_energy_sensor(i, sens))
if CONF_REVERSE_ACTIVE_ENERGY in conf: if reverse_active_energy_config := conf.get(CONF_REVERSE_ACTIVE_ENERGY):
sens = await sensor.new_sensor(conf[CONF_REVERSE_ACTIVE_ENERGY]) sens = await sensor.new_sensor(reverse_active_energy_config)
cg.add(var.set_reverse_active_energy_sensor(i, sens)) cg.add(var.set_reverse_active_energy_sensor(i, sens))
if CONF_FREQUENCY in config:
sens = await sensor.new_sensor(config[CONF_FREQUENCY]) if frequency_config := config.get(CONF_FREQUENCY):
sens = await sensor.new_sensor(frequency_config)
cg.add(var.set_freq_sensor(sens)) cg.add(var.set_freq_sensor(sens))
if CONF_CHIP_TEMPERATURE in config: if chip_temperature_config := config.get(CONF_CHIP_TEMPERATURE):
sens = await sensor.new_sensor(config[CONF_CHIP_TEMPERATURE]) sens = await sensor.new_sensor(chip_temperature_config)
cg.add(var.set_chip_temperature_sensor(sens)) cg.add(var.set_chip_temperature_sensor(sens))
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY])) cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES])) cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES]))
cg.add(var.set_pga_gain(config[CONF_GAIN_PGA])) cg.add(var.set_pga_gain(config[CONF_GAIN_PGA]))

View file

@ -87,6 +87,6 @@ async def to_code(config):
(CONF_MOISTURE, var.set_soil_moisture), (CONF_MOISTURE, var.set_soil_moisture),
(CONF_ILLUMINANCE, var.set_illuminance), (CONF_ILLUMINANCE, var.set_illuminance),
]: ]:
if config_key in config: if sensor_config := config.get(config_key):
sens = await sensor.new_sensor(config[config_key]) sens = await sensor.new_sensor(sensor_config)
cg.add(setter(sens)) cg.add(setter(sens))

View file

@ -57,19 +57,18 @@ async def to_code(config):
var.get_idle_trigger(), [], config[CONF_IDLE_ACTION] var.get_idle_trigger(), [], config[CONF_IDLE_ACTION]
) )
if CONF_COOL_ACTION in config: if cool_action_config := config.get(CONF_COOL_ACTION):
await automation.build_automation( await automation.build_automation(
var.get_cool_trigger(), [], config[CONF_COOL_ACTION] var.get_cool_trigger(), [], cool_action_config
) )
cg.add(var.set_supports_cool(True)) cg.add(var.set_supports_cool(True))
if CONF_HEAT_ACTION in config: if heat_action_config := config.get(CONF_HEAT_ACTION):
await automation.build_automation( await automation.build_automation(
var.get_heat_trigger(), [], config[CONF_HEAT_ACTION] var.get_heat_trigger(), [], heat_action_config
) )
cg.add(var.set_supports_heat(True)) cg.add(var.set_supports_heat(True))
if CONF_AWAY_CONFIG in config: if away := config.get(CONF_AWAY_CONFIG):
away = config[CONF_AWAY_CONFIG]
away_config = BangBangClimateTargetTempConfig( away_config = BangBangClimateTargetTempConfig(
away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW], away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW],
away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH], away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH],

View file

@ -45,8 +45,8 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config) await cg.register_component(var, config)
await ble_client.register_ble_node(var, config) await ble_client.register_ble_node(var, config)
if CONF_TIME_ID in config: if time_id := config.get(CONF_TIME_ID):
time_ = await cg.get_variable(config[CONF_TIME_ID]) time_ = await cg.get_variable(time_id)
cg.add(var.set_time_id(time_)) cg.add(var.set_time_id(time_))
if CONF_RECEIVE_TIMEOUT in config: if (receive_timeout := config.get(CONF_RECEIVE_TIMEOUT)) is not None:
cg.add(var.set_status_timeout(config[CONF_RECEIVE_TIMEOUT])) cg.add(var.set_status_timeout(receive_timeout))

View file

@ -3,6 +3,7 @@
#include "bedjet_hub.h" #include "bedjet_hub.h"
#include "bedjet_child.h" #include "bedjet_child.h"
#include "bedjet_const.h" #include "bedjet_const.h"
#include <cinttypes>
namespace esphome { namespace esphome {
namespace bedjet { namespace bedjet {
@ -373,7 +374,7 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
if (this->last_notify_ == 0 || delta > MIN_NOTIFY_THROTTLE || this->force_refresh_) { if (this->last_notify_ == 0 || delta > MIN_NOTIFY_THROTTLE || this->force_refresh_) {
// Set reentrant flag to prevent processing multiple packets. // Set reentrant flag to prevent processing multiple packets.
this->processing_ = true; this->processing_ = true;
ESP_LOGVV(TAG, "[%s] Decoding packet: last=%d, delta=%d, force=%s", this->get_name().c_str(), ESP_LOGVV(TAG, "[%s] Decoding packet: last=%" PRId32 ", delta=%" PRId32 ", force=%s", this->get_name().c_str(),
this->last_notify_, delta, this->force_refresh_ ? "y" : "n"); this->last_notify_, delta, this->force_refresh_ ? "y" : "n");
bool needs_extra = this->codec_->decode_notify(param->notify.value, param->notify.value_len); bool needs_extra = this->codec_->decode_notify(param->notify.value, param->notify.value_len);
@ -523,11 +524,11 @@ void BedJetHub::dispatch_status_() {
ESP_LOGI(TAG, "[%s] Still waiting for first GATT notify event.", this->get_name().c_str()); ESP_LOGI(TAG, "[%s] Still waiting for first GATT notify event.", this->get_name().c_str());
} else if (diff > NOTIFY_WARN_THRESHOLD) { } else if (diff > NOTIFY_WARN_THRESHOLD) {
ESP_LOGW(TAG, "[%s] Last GATT notify was %d seconds ago.", this->get_name().c_str(), diff / 1000); ESP_LOGW(TAG, "[%s] Last GATT notify was %" PRId32 " seconds ago.", this->get_name().c_str(), diff / 1000);
} }
if (this->timeout_ > 0 && diff > this->timeout_ && this->parent()->enabled) { if (this->timeout_ > 0 && diff > this->timeout_ && this->parent()->enabled) {
ESP_LOGW(TAG, "[%s] Timed out after %d sec. Retrying...", this->get_name().c_str(), this->timeout_); ESP_LOGW(TAG, "[%s] Timed out after %" PRId32 " sec. Retrying...", this->get_name().c_str(), this->timeout_);
// set_enabled(false) will only close the connection if state != IDLE. // set_enabled(false) will only close the connection if state != IDLE.
this->parent()->set_state(espbt::ClientState::CONNECTING); this->parent()->set_state(espbt::ClientState::CONNECTING);
this->parent()->set_enabled(false); this->parent()->set_enabled(false);

View file

@ -29,10 +29,10 @@ async def to_code(config):
output_ = await cg.get_variable(config[CONF_OUTPUT]) output_ = await cg.get_variable(config[CONF_OUTPUT])
cg.add(var.set_output(output_)) cg.add(var.set_output(output_))
if CONF_OSCILLATION_OUTPUT in config: if oscillation_output_id := config.get(CONF_OSCILLATION_OUTPUT):
oscillation_output = await cg.get_variable(config[CONF_OSCILLATION_OUTPUT]) oscillation_output = await cg.get_variable(oscillation_output_id)
cg.add(var.set_oscillating(oscillation_output)) cg.add(var.set_oscillating(oscillation_output))
if CONF_DIRECTION_OUTPUT in config: if direction_output_id := config.get(CONF_DIRECTION_OUTPUT):
direction_output = await cg.get_variable(config[CONF_DIRECTION_OUTPUT]) direction_output = await cg.get_variable(direction_output_id)
cg.add(var.set_direction(direction_output)) cg.add(var.set_direction(direction_output))

View file

@ -95,6 +95,14 @@ DEVICE_CLASSES = [
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
CONF_TIME_OFF = "time_off"
CONF_TIME_ON = "time_on"
DEFAULT_DELAY = "1s"
DEFAULT_TIME_OFF = "100ms"
DEFAULT_TIME_ON = "900ms"
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.EntityBase) BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.EntityBase)
BinarySensorInitiallyOff = binary_sensor_ns.class_( BinarySensorInitiallyOff = binary_sensor_ns.class_(
@ -138,47 +146,75 @@ FILTER_REGISTRY = Registry()
validate_filters = cv.validate_registry("filter", FILTER_REGISTRY) validate_filters = cv.validate_registry("filter", FILTER_REGISTRY)
@FILTER_REGISTRY.register("invert", InvertFilter, {}) def register_filter(name, filter_type, schema):
return FILTER_REGISTRY.register(name, filter_type, schema)
@register_filter("invert", InvertFilter, {})
async def invert_filter_to_code(config, filter_id): async def invert_filter_to_code(config, filter_id):
return cg.new_Pvariable(filter_id) return cg.new_Pvariable(filter_id)
@FILTER_REGISTRY.register( @register_filter(
"delayed_on_off", DelayedOnOffFilter, cv.positive_time_period_milliseconds "delayed_on_off",
DelayedOnOffFilter,
cv.Any(
cv.templatable(cv.positive_time_period_milliseconds),
cv.Schema(
{
cv.Required(CONF_TIME_ON): cv.templatable(
cv.positive_time_period_milliseconds
),
cv.Required(CONF_TIME_OFF): cv.templatable(
cv.positive_time_period_milliseconds
),
}
),
msg="'delayed_on_off' filter requires either a delay time to be used for both "
"turn-on and turn-off delays, or two parameters 'time_on' and 'time_off' if "
"different delay times are required.",
),
) )
async def delayed_on_off_filter_to_code(config, filter_id): async def delayed_on_off_filter_to_code(config, filter_id):
var = cg.new_Pvariable(filter_id, config) var = cg.new_Pvariable(filter_id)
await cg.register_component(var, {}) await cg.register_component(var, {})
if isinstance(config, dict):
template_ = await cg.templatable(config[CONF_TIME_ON], [], cg.uint32)
cg.add(var.set_on_delay(template_))
template_ = await cg.templatable(config[CONF_TIME_OFF], [], cg.uint32)
cg.add(var.set_off_delay(template_))
else:
template_ = await cg.templatable(config, [], cg.uint32)
cg.add(var.set_on_delay(template_))
cg.add(var.set_off_delay(template_))
return var return var
@FILTER_REGISTRY.register( @register_filter(
"delayed_on", DelayedOnFilter, cv.positive_time_period_milliseconds "delayed_on", DelayedOnFilter, cv.templatable(cv.positive_time_period_milliseconds)
) )
async def delayed_on_filter_to_code(config, filter_id): async def delayed_on_filter_to_code(config, filter_id):
var = cg.new_Pvariable(filter_id, config) var = cg.new_Pvariable(filter_id)
await cg.register_component(var, {}) await cg.register_component(var, {})
template_ = await cg.templatable(config, [], cg.uint32)
cg.add(var.set_delay(template_))
return var return var
@FILTER_REGISTRY.register( @register_filter(
"delayed_off", DelayedOffFilter, cv.positive_time_period_milliseconds "delayed_off",
DelayedOffFilter,
cv.templatable(cv.positive_time_period_milliseconds),
) )
async def delayed_off_filter_to_code(config, filter_id): async def delayed_off_filter_to_code(config, filter_id):
var = cg.new_Pvariable(filter_id, config) var = cg.new_Pvariable(filter_id)
await cg.register_component(var, {}) await cg.register_component(var, {})
template_ = await cg.templatable(config, [], cg.uint32)
cg.add(var.set_delay(template_))
return var return var
CONF_TIME_OFF = "time_off" @register_filter(
CONF_TIME_ON = "time_on"
DEFAULT_DELAY = "1s"
DEFAULT_TIME_OFF = "100ms"
DEFAULT_TIME_ON = "900ms"
@FILTER_REGISTRY.register(
"autorepeat", "autorepeat",
AutorepeatFilter, AutorepeatFilter,
cv.All( cv.All(
@ -215,7 +251,7 @@ async def autorepeat_filter_to_code(config, filter_id):
return var return var
@FILTER_REGISTRY.register("lambda", LambdaFilter, cv.returning_lambda) @register_filter("lambda", LambdaFilter, cv.returning_lambda)
async def lambda_filter_to_code(config, filter_id): async def lambda_filter_to_code(config, filter_id):
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config, [(bool, "x")], return_type=cg.optional.template(bool) config, [(bool, "x")], return_type=cg.optional.template(bool)
@ -323,6 +359,18 @@ def validate_multi_click_timing(value):
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
def validate_click_timing(value):
for v in value:
min_length = v.get(CONF_MIN_LENGTH)
max_length = v.get(CONF_MAX_LENGTH)
if max_length < min_length:
raise cv.Invalid(
f"Max length ({max_length}) must be larger than min length ({min_length})."
)
return value
BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_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),
@ -342,7 +390,8 @@ BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).ex
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
} }
), ),
cv.Optional(CONF_ON_CLICK): automation.validate_automation( cv.Optional(CONF_ON_CLICK): cv.All(
automation.validate_automation(
{ {
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
cv.Optional( cv.Optional(
@ -353,7 +402,10 @@ BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).ex
): cv.positive_time_period_milliseconds, ): cv.positive_time_period_milliseconds,
} }
), ),
cv.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation( validate_click_timing,
),
cv.Optional(CONF_ON_DOUBLE_CLICK): cv.All(
automation.validate_automation(
{ {
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger),
cv.Optional( cv.Optional(
@ -364,6 +416,8 @@ BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).ex
): cv.positive_time_period_milliseconds, ): cv.positive_time_period_milliseconds,
} }
), ),
validate_click_timing,
),
cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation( cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation(
{ {
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger),
@ -413,14 +467,14 @@ def binary_sensor_schema(
async def setup_binary_sensor_core_(var, config): async def setup_binary_sensor_core_(var, config):
await setup_entity(var, config) await setup_entity(var, config)
if CONF_DEVICE_CLASS in config: if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) cg.add(var.set_device_class(device_class))
if CONF_PUBLISH_INITIAL_STATE in config: if publish_initial_state := config.get(CONF_PUBLISH_INITIAL_STATE):
cg.add(var.set_publish_initial_state(config[CONF_PUBLISH_INITIAL_STATE])) cg.add(var.set_publish_initial_state(publish_initial_state))
if CONF_INVERTED in config: if inverted := config.get(CONF_INVERTED):
cg.add(var.set_inverted(config[CONF_INVERTED])) cg.add(var.set_inverted(inverted))
if CONF_FILTERS in config: if filters_config := config.get(CONF_FILTERS):
filters = await cg.build_registry_list(FILTER_REGISTRY, config[CONF_FILTERS]) filters = await cg.build_registry_list(FILTER_REGISTRY, filters_config)
cg.add(var.add_filters(filters)) cg.add(var.add_filters(filters))
for conf in config.get(CONF_ON_PRESS, []): for conf in config.get(CONF_ON_PRESS, []):
@ -464,8 +518,8 @@ async def setup_binary_sensor_core_(var, config):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(bool, "x")], conf) await automation.build_automation(trigger, [(bool, "x")], conf)
if CONF_MQTT_ID in config: if mqtt_id := config.get(CONF_MQTT_ID):
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)

View file

@ -26,22 +26,20 @@ void Filter::input(bool value, bool is_initial) {
} }
} }
DelayedOnOffFilter::DelayedOnOffFilter(uint32_t delay) : delay_(delay) {}
optional<bool> DelayedOnOffFilter::new_value(bool value, bool is_initial) { optional<bool> DelayedOnOffFilter::new_value(bool value, bool is_initial) {
if (value) { if (value) {
this->set_timeout("ON_OFF", this->delay_, [this, is_initial]() { this->output(true, is_initial); }); this->set_timeout("ON_OFF", this->on_delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
} else { } else {
this->set_timeout("ON_OFF", this->delay_, [this, is_initial]() { this->output(false, is_initial); }); this->set_timeout("ON_OFF", this->off_delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
} }
return {}; return {};
} }
float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; } float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
DelayedOnFilter::DelayedOnFilter(uint32_t delay) : delay_(delay) {}
optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) { optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
if (value) { if (value) {
this->set_timeout("ON", this->delay_, [this, is_initial]() { this->output(true, is_initial); }); this->set_timeout("ON", this->delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
return {}; return {};
} else { } else {
this->cancel_timeout("ON"); this->cancel_timeout("ON");
@ -51,10 +49,9 @@ optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
float DelayedOnFilter::get_setup_priority() const { return setup_priority::HARDWARE; } float DelayedOnFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
DelayedOffFilter::DelayedOffFilter(uint32_t delay) : delay_(delay) {}
optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) { optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) {
if (!value) { if (!value) {
this->set_timeout("OFF", this->delay_, [this, is_initial]() { this->output(false, is_initial); }); this->set_timeout("OFF", this->delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
return {}; return {};
} else { } else {
this->cancel_timeout("OFF"); this->cancel_timeout("OFF");
@ -114,15 +111,6 @@ LambdaFilter::LambdaFilter(std::function<optional<bool>(bool)> f) : f_(std::move
optional<bool> LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); } optional<bool> LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); }
optional<bool> UniqueFilter::new_value(bool value, bool is_initial) {
if (this->last_value_.has_value() && *this->last_value_ == value) {
return {};
} else {
this->last_value_ = value;
return value;
}
}
} // namespace binary_sensor } // namespace binary_sensor
} // namespace esphome } // namespace esphome

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
@ -29,38 +30,40 @@ class Filter {
class DelayedOnOffFilter : public Filter, public Component { class DelayedOnOffFilter : public Filter, public Component {
public: public:
explicit DelayedOnOffFilter(uint32_t delay);
optional<bool> new_value(bool value, bool is_initial) override; optional<bool> new_value(bool value, bool is_initial) override;
float get_setup_priority() const override; float get_setup_priority() const override;
template<typename T> void set_on_delay(T delay) { this->on_delay_ = delay; }
template<typename T> void set_off_delay(T delay) { this->off_delay_ = delay; }
protected: protected:
uint32_t delay_; TemplatableValue<uint32_t> on_delay_{};
TemplatableValue<uint32_t> off_delay_{};
}; };
class DelayedOnFilter : public Filter, public Component { class DelayedOnFilter : public Filter, public Component {
public: public:
explicit DelayedOnFilter(uint32_t delay);
optional<bool> new_value(bool value, bool is_initial) override; optional<bool> new_value(bool value, bool is_initial) override;
float get_setup_priority() const override; float get_setup_priority() const override;
template<typename T> void set_delay(T delay) { this->delay_ = delay; }
protected: protected:
uint32_t delay_; TemplatableValue<uint32_t> delay_{};
}; };
class DelayedOffFilter : public Filter, public Component { class DelayedOffFilter : public Filter, public Component {
public: public:
explicit DelayedOffFilter(uint32_t delay);
optional<bool> new_value(bool value, bool is_initial) override; optional<bool> new_value(bool value, bool is_initial) override;
float get_setup_priority() const override; float get_setup_priority() const override;
template<typename T> void set_delay(T delay) { this->delay_ = delay; }
protected: protected:
uint32_t delay_; TemplatableValue<uint32_t> delay_{};
}; };
class InvertFilter : public Filter { class InvertFilter : public Filter {
@ -105,14 +108,6 @@ class LambdaFilter : public Filter {
std::function<optional<bool>(bool)> f_; std::function<optional<bool>(bool)> f_;
}; };
class UniqueFilter : public Filter {
public:
optional<bool> new_value(bool value, bool is_initial) override;
protected:
optional<bool> last_value_{};
};
} // namespace binary_sensor } // namespace binary_sensor
} // namespace esphome } // namespace esphome

View file

@ -0,0 +1,51 @@
# This file was auto-generated by libretiny/generate_components.py
# Do not modify its contents.
# For custom pin validators, put validate_pin() or validate_usage()
# in gpio.py file in this directory.
# For changing schema/pin schema, put COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA
# in schema.py file in this directory.
from esphome import pins
from esphome.components import libretiny
from esphome.components.libretiny.const import (
COMPONENT_BK72XX,
KEY_COMPONENT_DATA,
KEY_LIBRETINY,
LibreTinyComponent,
)
from esphome.core import CORE
from .boards import BK72XX_BOARDS, BK72XX_BOARD_PINS
CODEOWNERS = ["@kuba2k2"]
AUTO_LOAD = ["libretiny"]
COMPONENT_DATA = LibreTinyComponent(
name=COMPONENT_BK72XX,
boards=BK72XX_BOARDS,
board_pins=BK72XX_BOARD_PINS,
pin_validation=None,
usage_validation=None,
)
def _set_core_data(config):
CORE.data[KEY_LIBRETINY] = {}
CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA] = COMPONENT_DATA
return config
CONFIG_SCHEMA = libretiny.BASE_SCHEMA
PIN_SCHEMA = libretiny.gpio.BASE_PIN_SCHEMA
CONFIG_SCHEMA.prepend_extra(_set_core_data)
async def to_code(config):
return await libretiny.component_to_code(config)
@pins.PIN_SCHEMA_REGISTRY.register("bk72xx", PIN_SCHEMA)
async def pin_to_code(config):
return await libretiny.gpio.component_pin_to_code(config)

File diff suppressed because it is too large Load diff

View file

@ -93,35 +93,27 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await uart.register_uart_device(var, config) await uart.register_uart_device(var, config)
if CONF_VOLTAGE in config: if voltage_config := config.get(CONF_VOLTAGE):
conf = config[CONF_VOLTAGE] sens = await sensor.new_sensor(voltage_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_voltage_sensor(sens)) cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT_1 in config: if current_1_config := config.get(CONF_CURRENT_1):
conf = config[CONF_CURRENT_1] sens = await sensor.new_sensor(current_1_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_current_sensor_1(sens)) cg.add(var.set_current_sensor_1(sens))
if CONF_CURRENT_2 in config: if current_2_config := config.get(CONF_CURRENT_2):
conf = config[CONF_CURRENT_2] sens = await sensor.new_sensor(current_2_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_current_sensor_2(sens)) cg.add(var.set_current_sensor_2(sens))
if CONF_ACTIVE_POWER_1 in config: if active_power_1_config := config.get(CONF_ACTIVE_POWER_1):
conf = config[CONF_ACTIVE_POWER_1] sens = await sensor.new_sensor(active_power_1_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_power_sensor_1(sens)) cg.add(var.set_power_sensor_1(sens))
if CONF_ACTIVE_POWER_2 in config: if active_power_2_config := config.get(CONF_ACTIVE_POWER_2):
conf = config[CONF_ACTIVE_POWER_2] sens = await sensor.new_sensor(active_power_2_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_power_sensor_2(sens)) cg.add(var.set_power_sensor_2(sens))
if CONF_ENERGY_1 in config: if energy_1_config := config.get(CONF_ENERGY_1):
conf = config[CONF_ENERGY_1] sens = await sensor.new_sensor(energy_1_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_energy_sensor_1(sens)) cg.add(var.set_energy_sensor_1(sens))
if CONF_ENERGY_2 in config: if energy_2_config := config.get(CONF_ENERGY_2):
conf = config[CONF_ENERGY_2] sens = await sensor.new_sensor(energy_2_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_energy_sensor_2(sens)) cg.add(var.set_energy_sensor_2(sens))
if CONF_ENERGY_TOTAL in config: if energy_total_config := config.get(CONF_ENERGY_TOTAL):
conf = config[CONF_ENERGY_TOTAL] sens = await sensor.new_sensor(energy_total_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_energy_sensor_sum(sens)) cg.add(var.set_energy_sensor_sum(sens))

View file

@ -79,27 +79,21 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await uart.register_uart_device(var, config) await uart.register_uart_device(var, config)
if CONF_VOLTAGE in config: if voltage_config := config.get(CONF_VOLTAGE):
conf = config[CONF_VOLTAGE] sens = await sensor.new_sensor(voltage_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_voltage_sensor(sens)) cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT in config: if current_config := config.get(CONF_CURRENT):
conf = config[CONF_CURRENT] sens = await sensor.new_sensor(current_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_current_sensor(sens)) cg.add(var.set_current_sensor(sens))
if CONF_POWER in config: if power_config := config.get(CONF_POWER):
conf = config[CONF_POWER] sens = await sensor.new_sensor(power_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_power_sensor(sens)) cg.add(var.set_power_sensor(sens))
if CONF_ENERGY in config: if energy_config := config.get(CONF_ENERGY):
conf = config[CONF_ENERGY] sens = await sensor.new_sensor(energy_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_energy_sensor(sens)) cg.add(var.set_energy_sensor(sens))
if CONF_INTERNAL_TEMPERATURE in config: if internal_temperature_config := config.get(CONF_INTERNAL_TEMPERATURE):
conf = config[CONF_INTERNAL_TEMPERATURE] sens = await sensor.new_sensor(internal_temperature_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_internal_temperature_sensor(sens)) cg.add(var.set_internal_temperature_sensor(sens))
if CONF_EXTERNAL_TEMPERATURE in config: if external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE):
conf = config[CONF_EXTERNAL_TEMPERATURE] sens = await sensor.new_sensor(external_temperature_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_external_temperature_sensor(sens)) cg.add(var.set_external_temperature_sensor(sens))

View file

@ -71,23 +71,18 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await uart.register_uart_device(var, config) await uart.register_uart_device(var, config)
if CONF_VOLTAGE in config: if voltage_config := config.get(CONF_VOLTAGE):
conf = config[CONF_VOLTAGE] sens = await sensor.new_sensor(voltage_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_voltage_sensor(sens)) cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT in config: if current_config := config.get(CONF_CURRENT):
conf = config[CONF_CURRENT] sens = await sensor.new_sensor(current_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_current_sensor(sens)) cg.add(var.set_current_sensor(sens))
if CONF_POWER in config: if power_config := config.get(CONF_POWER):
conf = config[CONF_POWER] sens = await sensor.new_sensor(power_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_power_sensor(sens)) cg.add(var.set_power_sensor(sens))
if CONF_ENERGY in config: if energy_config := config.get(CONF_ENERGY):
conf = config[CONF_ENERGY] sens = await sensor.new_sensor(energy_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_energy_sensor(sens)) cg.add(var.set_energy_sensor(sens))
if CONF_FREQUENCY in config: if frequency_config := config.get(CONF_FREQUENCY):
conf = config[CONF_FREQUENCY] sens = await sensor.new_sensor(frequency_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_frequency_sensor(sens)) cg.add(var.set_frequency_sensor(sens))

View file

@ -129,32 +129,18 @@ async def characteristic_sensor_to_code(config):
) )
cg.add(var.set_char_uuid128(uuid128)) cg.add(var.set_char_uuid128(uuid128))
if CONF_DESCRIPTOR_UUID in config: if descriptor_uuid := config.get(CONF_DESCRIPTOR_UUID):
if len(config[CONF_DESCRIPTOR_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): if len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add( cg.add(var.set_descr_uuid16(esp32_ble_tracker.as_hex(descriptor_uuid)))
var.set_descr_uuid16( elif len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid32_format):
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID]) cg.add(var.set_descr_uuid32(esp32_ble_tracker.as_hex(descriptor_uuid)))
) elif len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid128_format):
) uuid128 = esp32_ble_tracker.as_reversed_hex_array(descriptor_uuid)
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
esp32_ble_tracker.bt_uuid32_format
):
cg.add(
var.set_descr_uuid32(
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID])
)
)
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format
):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
config[CONF_DESCRIPTOR_UUID]
)
cg.add(var.set_descr_uuid128(uuid128)) cg.add(var.set_descr_uuid128(uuid128))
if CONF_LAMBDA in config: if lambda_config := config.get(CONF_LAMBDA):
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(adv_data_t_const_ref, "x")], return_type=cg.float_ lambda_config, [(adv_data_t_const_ref, "x")], return_type=cg.float_
) )
cg.add(var.set_data_to_value(lambda_)) cg.add(var.set_data_to_value(lambda_))

View file

@ -88,27 +88,13 @@ async def to_code(config):
) )
cg.add(var.set_char_uuid128(uuid128)) cg.add(var.set_char_uuid128(uuid128))
if CONF_DESCRIPTOR_UUID in config: if descriptor_uuid := config:
if len(config[CONF_DESCRIPTOR_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): if len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add( cg.add(var.set_descr_uuid16(esp32_ble_tracker.as_hex(descriptor_uuid)))
var.set_descr_uuid16( elif len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid32_format):
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID]) cg.add(var.set_descr_uuid32(esp32_ble_tracker.as_hex(descriptor_uuid)))
) elif len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid128_format):
) uuid128 = esp32_ble_tracker.as_reversed_hex_array(descriptor_uuid)
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
esp32_ble_tracker.bt_uuid32_format
):
cg.add(
var.set_descr_uuid32(
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID])
)
)
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format
):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
config[CONF_DESCRIPTOR_UUID]
)
cg.add(var.set_descr_uuid128(uuid128)) cg.add(var.set_descr_uuid128(uuid128))
await cg.register_component(var, config) await cg.register_component(var, config)

View file

@ -39,7 +39,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t, cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t,
cv.Optional(CONF_IBEACON_UUID): cv.uuid, cv.Optional(CONF_IBEACON_UUID): cv.uuid,
cv.Optional(CONF_MIN_RSSI): cv.All( cv.Optional(CONF_MIN_RSSI): cv.All(
cv.decibel, cv.int_range(min=-90, max=-30) cv.decibel, cv.int_range(min=-100, max=-30)
), ),
} }
) )
@ -55,35 +55,27 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await esp32_ble_tracker.register_ble_device(var, config) await esp32_ble_tracker.register_ble_device(var, config)
if CONF_MIN_RSSI in config: if min_rssi := config.get(CONF_MIN_RSSI):
cg.add(var.set_minimum_rssi(config[CONF_MIN_RSSI])) cg.add(var.set_minimum_rssi(min_rssi))
if CONF_MAC_ADDRESS in config: if mac_address := config.get(CONF_MAC_ADDRESS):
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) cg.add(var.set_address(mac_address.as_hex))
if CONF_SERVICE_UUID in config: if service_uuid := config.get(CONF_SERVICE_UUID):
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): if len(service_uuid) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add( cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(service_uuid)))
var.set_service_uuid16( elif len(service_uuid) == len(esp32_ble_tracker.bt_uuid32_format):
esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]) cg.add(var.set_service_uuid32(esp32_ble_tracker.as_hex(service_uuid)))
) elif len(service_uuid) == len(esp32_ble_tracker.bt_uuid128_format):
) uuid128 = esp32_ble_tracker.as_reversed_hex_array(service_uuid)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
cg.add(
var.set_service_uuid32(
esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])
)
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
cg.add(var.set_service_uuid128(uuid128)) cg.add(var.set_service_uuid128(uuid128))
if CONF_IBEACON_UUID in config: if ibeacon_uuid := config.get(CONF_IBEACON_UUID):
ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(config[CONF_IBEACON_UUID])) ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(ibeacon_uuid))
cg.add(var.set_ibeacon_uuid(ibeacon_uuid)) cg.add(var.set_ibeacon_uuid(ibeacon_uuid))
if CONF_IBEACON_MAJOR in config: if (ibeacon_major := config.get(CONF_IBEACON_MAJOR)) is not None:
cg.add(var.set_ibeacon_major(config[CONF_IBEACON_MAJOR])) cg.add(var.set_ibeacon_major(ibeacon_major))
if CONF_IBEACON_MINOR in config: if (ibeacon_minor := config.get(CONF_IBEACON_MINOR)) is not None:
cg.add(var.set_ibeacon_minor(config[CONF_IBEACON_MINOR])) cg.add(var.set_ibeacon_minor(ibeacon_minor))

View file

@ -51,7 +51,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
this->found_ = false; this->found_ = false;
} }
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
if (this->check_minimum_rssi_ && this->minimum_rssi_ <= device.get_rssi()) { if (this->check_minimum_rssi_ && this->minimum_rssi_ > device.get_rssi()) {
return false; return false;
} }
switch (this->match_by_) { switch (this->match_by_) {

View file

@ -57,32 +57,24 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await esp32_ble_tracker.register_ble_device(var, config) await esp32_ble_tracker.register_ble_device(var, config)
if CONF_MAC_ADDRESS in config: if mac_address := config.get(CONF_MAC_ADDRESS):
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) cg.add(var.set_address(mac_address.as_hex))
if CONF_SERVICE_UUID in config: if service_uuid := config.get(CONF_SERVICE_UUID):
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): if len(service_uuid) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add( cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(service_uuid)))
var.set_service_uuid16( elif len(service_uuid) == len(esp32_ble_tracker.bt_uuid32_format):
esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]) cg.add(var.set_service_uuid32(esp32_ble_tracker.as_hex(service_uuid)))
) elif len(service_uuid) == len(esp32_ble_tracker.bt_uuid128_format):
) uuid128 = esp32_ble_tracker.as_reversed_hex_array(service_uuid)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
cg.add(
var.set_service_uuid32(
esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])
)
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
cg.add(var.set_service_uuid128(uuid128)) cg.add(var.set_service_uuid128(uuid128))
if CONF_IBEACON_UUID in config: if ibeacon_uuid := config.get(CONF_IBEACON_UUID):
ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(config[CONF_IBEACON_UUID])) ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(ibeacon_uuid))
cg.add(var.set_ibeacon_uuid(ibeacon_uuid)) cg.add(var.set_ibeacon_uuid(ibeacon_uuid))
if CONF_IBEACON_MAJOR in config: if (ibeacon_major := config.get(CONF_IBEACON_MAJOR)) is not None:
cg.add(var.set_ibeacon_major(config[CONF_IBEACON_MAJOR])) cg.add(var.set_ibeacon_major(ibeacon_major))
if CONF_IBEACON_MINOR in config: if (ibeacon_minor := config.get(CONF_IBEACON_MINOR)) is not None:
cg.add(var.set_ibeacon_minor(config[CONF_IBEACON_MINOR])) cg.add(var.set_ibeacon_minor(ibeacon_minor))

View file

@ -275,6 +275,10 @@ esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enabl
return ESP_OK; return ESP_OK;
} }
esp32_ble_tracker::AdvertisementParserType BluetoothConnection::get_advertisement_parser_type() {
return this->proxy_->get_advertisement_parser_type();
}
} // namespace bluetooth_proxy } // namespace bluetooth_proxy
} // namespace esphome } // namespace esphome

View file

@ -14,6 +14,7 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase {
bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, bool 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;
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
esp_err_t read_characteristic(uint16_t handle); esp_err_t read_characteristic(uint16_t handle);
esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response); esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response);

View file

@ -198,6 +198,12 @@ void BluetoothProxy::loop() {
} }
} }
esp32_ble_tracker::AdvertisementParserType BluetoothProxy::get_advertisement_parser_type() {
if (this->raw_advertisements_)
return esp32_ble_tracker::AdvertisementParserType::RAW_ADVERTISEMENTS;
return esp32_ble_tracker::AdvertisementParserType::PARSED_ADVERTISEMENTS;
}
BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) { BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) {
for (auto *connection : this->connections_) { for (auto *connection : this->connections_) {
if (connection->get_address() == address) if (connection->get_address() == address)
@ -435,6 +441,7 @@ void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection
} }
this->api_connection_ = api_connection; this->api_connection_ = api_connection;
this->raw_advertisements_ = flags & BluetoothProxySubscriptionFlag::SUBSCRIPTION_RAW_ADVERTISEMENTS; this->raw_advertisements_ = flags & BluetoothProxySubscriptionFlag::SUBSCRIPTION_RAW_ADVERTISEMENTS;
this->parent_->recalculate_advertisement_parser_types();
} }
void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connection) { void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connection) {
@ -444,6 +451,7 @@ void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connecti
} }
this->api_connection_ = nullptr; this->api_connection_ = nullptr;
this->raw_advertisements_ = false; this->raw_advertisements_ = false;
this->parent_->recalculate_advertisement_parser_types();
} }
void BluetoothProxy::send_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) { void BluetoothProxy::send_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) {

View file

@ -51,6 +51,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) override; bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) override;
void dump_config() override; void dump_config() override;
void loop() override; void loop() override;
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
void register_connection(BluetoothConnection *connection) { void register_connection(BluetoothConnection *connection) {
this->connections_.push_back(connection); this->connections_.push_back(connection);

View file

@ -98,22 +98,19 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config: if temperature_config := config.get(CONF_TEMPERATURE):
conf = config[CONF_TEMPERATURE] sens = await sensor.new_sensor(temperature_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_temperature_sensor(sens)) cg.add(var.set_temperature_sensor(sens))
cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING])) cg.add(var.set_temperature_oversampling(temperature_config[CONF_OVERSAMPLING]))
if CONF_PRESSURE in config: if pressure_config := config.get(CONF_PRESSURE):
conf = config[CONF_PRESSURE] sens = await sensor.new_sensor(pressure_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_pressure_sensor(sens)) cg.add(var.set_pressure_sensor(sens))
cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING])) cg.add(var.set_pressure_oversampling(pressure_config[CONF_OVERSAMPLING]))
if CONF_HUMIDITY in config: if humidity_config := config.get(CONF_HUMIDITY):
conf = config[CONF_HUMIDITY] sens = await sensor.new_sensor(humidity_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_humidity_sensor(sens)) cg.add(var.set_humidity_sensor(sens))
cg.add(var.set_humidity_oversampling(conf[CONF_OVERSAMPLING])) cg.add(var.set_humidity_oversampling(humidity_config[CONF_OVERSAMPLING]))
cg.add(var.set_iir_filter(config[CONF_IIR_FILTER])) cg.add(var.set_iir_filter(config[CONF_IIR_FILTER]))

View file

@ -130,27 +130,23 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config: if temperature_config := config.get(CONF_TEMPERATURE):
conf = config[CONF_TEMPERATURE] sens = await sensor.new_sensor(temperature_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_temperature_sensor(sens)) cg.add(var.set_temperature_sensor(sens))
cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING])) cg.add(var.set_temperature_oversampling(temperature_config[CONF_OVERSAMPLING]))
if CONF_PRESSURE in config: if pressure_config := config.get(CONF_PRESSURE):
conf = config[CONF_PRESSURE] sens = await sensor.new_sensor(pressure_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_pressure_sensor(sens)) cg.add(var.set_pressure_sensor(sens))
cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING])) cg.add(var.set_pressure_oversampling(pressure_config[CONF_OVERSAMPLING]))
if CONF_HUMIDITY in config: if humidity_config := config.get(CONF_HUMIDITY):
conf = config[CONF_HUMIDITY] sens = await sensor.new_sensor(humidity_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_humidity_sensor(sens)) cg.add(var.set_humidity_sensor(sens))
cg.add(var.set_humidity_oversampling(conf[CONF_OVERSAMPLING])) cg.add(var.set_humidity_oversampling(humidity_config[CONF_OVERSAMPLING]))
if CONF_GAS_RESISTANCE in config: if gas_resistance_config := config.get(CONF_GAS_RESISTANCE):
conf = config[CONF_GAS_RESISTANCE] sens = await sensor.new_sensor(gas_resistance_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_gas_resistance_sensor(sens)) cg.add(var.set_gas_resistance_sensor(sens))
cg.add(var.set_iir_filter(IIR_FILTER_OPTIONS[config[CONF_IIR_FILTER]])) cg.add(var.set_iir_filter(IIR_FILTER_OPTIONS[config[CONF_IIR_FILTER]]))

View file

@ -6,8 +6,10 @@ from esphome.const import (
CONF_HUMIDITY, CONF_HUMIDITY,
CONF_PRESSURE, CONF_PRESSURE,
CONF_TEMPERATURE, CONF_TEMPERATURE,
DEVICE_CLASS_CARBON_DIOXIDE,
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PRESSURE, DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS, UNIT_CELSIUS,
@ -17,8 +19,6 @@ from esphome.const import (
UNIT_PERCENT, UNIT_PERCENT,
ICON_GAS_CYLINDER, ICON_GAS_CYLINDER,
ICON_GAUGE, ICON_GAUGE,
ICON_THERMOMETER,
ICON_WATER_PERCENT,
) )
from . import ( from . import (
BME680BSECComponent, BME680BSECComponent,
@ -35,7 +35,6 @@ CONF_CO2_EQUIVALENT = "co2_equivalent"
CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent" CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent"
UNIT_IAQ = "IAQ" UNIT_IAQ = "IAQ"
ICON_ACCURACY = "mdi:checkbox-marked-circle-outline" ICON_ACCURACY = "mdi:checkbox-marked-circle-outline"
ICON_TEST_TUBE = "mdi:test-tube"
TYPES = [ TYPES = [
CONF_TEMPERATURE, CONF_TEMPERATURE,
@ -53,7 +52,6 @@ CONFIG_SCHEMA = cv.Schema(
cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent), cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS, unit_of_measurement=UNIT_CELSIUS,
icon=ICON_THERMOMETER,
accuracy_decimals=1, accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
@ -62,16 +60,14 @@ CONFIG_SCHEMA = cv.Schema(
), ),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema( cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement=UNIT_HECTOPASCAL, unit_of_measurement=UNIT_HECTOPASCAL,
icon=ICON_GAUGE,
accuracy_decimals=1, accuracy_decimals=1,
device_class=DEVICE_CLASS_PRESSURE, device_class=DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
).extend( ).extend(
{cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)} {cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)}
), ),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT, unit_of_measurement=UNIT_PERCENT,
icon=ICON_WATER_PERCENT,
accuracy_decimals=1, accuracy_decimals=1,
device_class=DEVICE_CLASS_HUMIDITY, device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
@ -97,14 +93,14 @@ CONFIG_SCHEMA = cv.Schema(
), ),
cv.Optional(CONF_CO2_EQUIVALENT): sensor.sensor_schema( cv.Optional(CONF_CO2_EQUIVALENT): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION, unit_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_TEST_TUBE,
accuracy_decimals=1, accuracy_decimals=1,
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
cv.Optional(CONF_BREATH_VOC_EQUIVALENT): sensor.sensor_schema( cv.Optional(CONF_BREATH_VOC_EQUIVALENT): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION, unit_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_TEST_TUBE,
accuracy_decimals=1, accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
} }
@ -112,12 +108,13 @@ CONFIG_SCHEMA = cv.Schema(
async def setup_conf(config, key, hub): async def setup_conf(config, key, hub):
if key in config: if sensor_config := config.get(key):
conf = config[key] sens = await sensor.new_sensor(sensor_config)
sens = await sensor.new_sensor(conf)
cg.add(getattr(hub, f"set_{key}_sensor")(sens)) cg.add(getattr(hub, f"set_{key}_sensor")(sens))
if CONF_SAMPLE_RATE in conf: if CONF_SAMPLE_RATE in sensor_config:
cg.add(getattr(hub, f"set_{key}_sample_rate")(conf[CONF_SAMPLE_RATE])) cg.add(
getattr(hub, f"set_{key}_sample_rate")(sensor_config[CONF_SAMPLE_RATE])
)
async def to_code(config): async def to_code(config):

View file

@ -21,9 +21,8 @@ CONFIG_SCHEMA = cv.Schema(
async def setup_conf(config, key, hub): async def setup_conf(config, key, hub):
if key in config: if sensor_config := config.get(key):
conf = config[key] sens = await text_sensor.new_text_sensor(sensor_config)
sens = await text_sensor.new_text_sensor(conf)
cg.add(getattr(hub, f"set_{key}_text_sensor")(sens)) cg.add(getattr(hub, f"set_{key}_text_sensor")(sens))

View file

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

View file

@ -0,0 +1,270 @@
#include "bmi160.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
namespace esphome {
namespace bmi160 {
static const char *const TAG = "bmi160";
const uint8_t BMI160_REGISTER_CHIPID = 0x00;
const uint8_t BMI160_REGISTER_CMD = 0x7E;
enum class Cmd : uint8_t {
START_FOC = 0x03,
ACCL_SET_PMU_MODE = 0b00010000, // last 2 bits are mode
GYRO_SET_PMU_MODE = 0b00010100, // last 2 bits are mode
MAG_SET_PMU_MODE = 0b00011000, // last 2 bits are mode
PROG_NVM = 0xA0,
FIFO_FLUSH = 0xB0,
INT_RESET = 0xB1,
SOFT_RESET = 0xB6,
STEP_CNT_CLR = 0xB2,
};
enum class GyroPmuMode : uint8_t {
SUSPEND = 0b00,
NORMAL = 0b01,
LOW_POWER = 0b10,
};
enum class AcclPmuMode : uint8_t {
SUSPEND = 0b00,
NORMAL = 0b01,
FAST_STARTUP = 0b11,
};
enum class MagPmuMode : uint8_t {
SUSPEND = 0b00,
NORMAL = 0b01,
LOW_POWER = 0b10,
};
const uint8_t BMI160_REGISTER_ACCEL_CONFIG = 0x40;
enum class AcclFilterMode : uint8_t {
POWER_SAVING = 0b00000000,
PERF = 0b10000000,
};
enum class AcclBandwidth : uint8_t {
OSR4_AVG1 = 0b00000000,
OSR2_AVG2 = 0b00010000,
NORMAL_AVG4 = 0b00100000,
RES_AVG8 = 0b00110000,
RES_AVG16 = 0b01000000,
RES_AVG32 = 0b01010000,
RES_AVG64 = 0b01100000,
RES_AVG128 = 0b01110000,
};
enum class AccelOutputDataRate : uint8_t {
HZ_25_32 = 0b0001, // 25/32 Hz
HZ_25_16 = 0b0010, // 25/16 Hz
HZ_25_8 = 0b0011, // 25/8 Hz
HZ_25_4 = 0b0100, // 25/4 Hz
HZ_25_2 = 0b0101, // 25/2 Hz
HZ_25 = 0b0110, // 25 Hz
HZ_50 = 0b0111, // 50 Hz
HZ_100 = 0b1000, // 100 Hz
HZ_200 = 0b1001, // 200 Hz
HZ_400 = 0b1010, // 400 Hz
HZ_800 = 0b1011, // 800 Hz
HZ_1600 = 0b1100, // 1600 Hz
};
const uint8_t BMI160_REGISTER_ACCEL_RANGE = 0x41;
enum class AccelRange : uint8_t {
RANGE_2G = 0b0011,
RANGE_4G = 0b0101,
RANGE_8G = 0b1000,
RANGE_16G = 0b1100,
};
const uint8_t BMI160_REGISTER_GYRO_CONFIG = 0x42;
enum class GyroBandwidth : uint8_t {
OSR4 = 0x00,
OSR2 = 0x10,
NORMAL = 0x20,
};
enum class GyroOuputDataRate : uint8_t {
HZ_25 = 0x06,
HZ_50 = 0x07,
HZ_100 = 0x08,
HZ_200 = 0x09,
HZ_400 = 0x0A,
HZ_800 = 0x0B,
HZ_1600 = 0x0C,
HZ_3200 = 0x0D,
};
const uint8_t BMI160_REGISTER_GYRO_RANGE = 0x43;
enum class GyroRange : uint8_t {
RANGE_2000_DPS = 0x0, // ±2000 °/s
RANGE_1000_DPS = 0x1,
RANGE_500_DPS = 0x2,
RANGE_250_DPS = 0x3,
RANGE_125_DPS = 0x4,
};
const uint8_t BMI160_REGISTER_DATA_GYRO_X_LSB = 0x0C;
const uint8_t BMI160_REGISTER_DATA_GYRO_X_MSB = 0x0D;
const uint8_t BMI160_REGISTER_DATA_GYRO_Y_LSB = 0x0E;
const uint8_t BMI160_REGISTER_DATA_GYRO_Y_MSB = 0x0F;
const uint8_t BMI160_REGISTER_DATA_GYRO_Z_LSB = 0x10;
const uint8_t BMI160_REGISTER_DATA_GYRO_Z_MSB = 0x11;
const uint8_t BMI160_REGISTER_DATA_ACCEL_X_LSB = 0x12;
const uint8_t BMI160_REGISTER_DATA_ACCEL_X_MSB = 0x13;
const uint8_t BMI160_REGISTER_DATA_ACCEL_Y_LSB = 0x14;
const uint8_t BMI160_REGISTER_DATA_ACCEL_Y_MSB = 0x15;
const uint8_t BMI160_REGISTER_DATA_ACCEL_Z_LSB = 0x16;
const uint8_t BMI160_REGISTER_DATA_ACCEL_Z_MSB = 0x17;
const uint8_t BMI160_REGISTER_DATA_TEMP_LSB = 0x20;
const uint8_t BMI160_REGISTER_DATA_TEMP_MSB = 0x21;
const float GRAVITY_EARTH = 9.80665f;
void BMI160Component::internal_setup_(int stage) {
switch (stage) {
case 0:
ESP_LOGCONFIG(TAG, "Setting up BMI160...");
uint8_t chipid;
if (!this->read_byte(BMI160_REGISTER_CHIPID, &chipid) || (chipid != 0b11010001)) {
this->mark_failed();
return;
}
ESP_LOGV(TAG, " Bringing accelerometer out of sleep...");
if (!this->write_byte(BMI160_REGISTER_CMD, (uint8_t) Cmd::ACCL_SET_PMU_MODE | (uint8_t) AcclPmuMode::NORMAL)) {
this->mark_failed();
return;
}
ESP_LOGV(TAG, " Waiting for accelerometer to wake up...");
// need to wait (max delay in datasheet) because we can't send commands while another is in progress
// min 5ms, 10ms
this->set_timeout(10, [this]() { this->internal_setup_(1); });
break;
case 1:
ESP_LOGV(TAG, " Bringing gyroscope out of sleep...");
if (!this->write_byte(BMI160_REGISTER_CMD, (uint8_t) Cmd::GYRO_SET_PMU_MODE | (uint8_t) GyroPmuMode::NORMAL)) {
this->mark_failed();
return;
}
ESP_LOGV(TAG, " Waiting for gyroscope to wake up...");
// wait between 51 & 81ms, doing 100 to be safe
this->set_timeout(10, [this]() { this->internal_setup_(2); });
break;
case 2:
ESP_LOGV(TAG, " Setting up Gyro Config...");
uint8_t gyro_config = (uint8_t) GyroBandwidth::OSR4 | (uint8_t) GyroOuputDataRate::HZ_25;
ESP_LOGV(TAG, " Output gyro_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(gyro_config));
if (!this->write_byte(BMI160_REGISTER_GYRO_CONFIG, gyro_config)) {
this->mark_failed();
return;
}
ESP_LOGV(TAG, " Setting up Gyro Range...");
uint8_t gyro_range = (uint8_t) GyroRange::RANGE_2000_DPS;
ESP_LOGV(TAG, " Output gyro_range: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(gyro_range));
if (!this->write_byte(BMI160_REGISTER_GYRO_RANGE, gyro_range)) {
this->mark_failed();
return;
}
ESP_LOGV(TAG, " Setting up Accel Config...");
uint8_t accel_config =
(uint8_t) AcclFilterMode::PERF | (uint8_t) AcclBandwidth::RES_AVG16 | (uint8_t) AccelOutputDataRate::HZ_25;
ESP_LOGV(TAG, " Output accel_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(accel_config));
if (!this->write_byte(BMI160_REGISTER_ACCEL_CONFIG, accel_config)) {
this->mark_failed();
return;
}
ESP_LOGV(TAG, " Setting up Accel Range...");
uint8_t accel_range = (uint8_t) AccelRange::RANGE_16G;
ESP_LOGV(TAG, " Output accel_range: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(accel_range));
if (!this->write_byte(BMI160_REGISTER_ACCEL_RANGE, accel_range)) {
this->mark_failed();
return;
}
this->setup_complete_ = true;
}
}
void BMI160Component::setup() { this->internal_setup_(0); }
void BMI160Component::dump_config() {
ESP_LOGCONFIG(TAG, "BMI160:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with BMI160 failed!");
}
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Acceleration X", this->accel_x_sensor_);
LOG_SENSOR(" ", "Acceleration Y", this->accel_y_sensor_);
LOG_SENSOR(" ", "Acceleration Z", this->accel_z_sensor_);
LOG_SENSOR(" ", "Gyro X", this->gyro_x_sensor_);
LOG_SENSOR(" ", "Gyro Y", this->gyro_y_sensor_);
LOG_SENSOR(" ", "Gyro Z", this->gyro_z_sensor_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
}
i2c::ErrorCode BMI160Component::read_le_int16_(uint8_t reg, int16_t *value, uint8_t len) {
uint8_t raw_data[len * 2];
// read using read_register because we have little-endian data, and read_bytes_16 will swap it
i2c::ErrorCode err = this->read_register(reg, raw_data, len * 2, true);
if (err != i2c::ERROR_OK) {
return err;
}
for (int i = 0; i < len; i++) {
value[i] = (int16_t) ((uint16_t) raw_data[i * 2] | ((uint16_t) raw_data[i * 2 + 1] << 8));
}
return err;
}
void BMI160Component::update() {
if (!this->setup_complete_) {
return;
}
ESP_LOGV(TAG, " Updating BMI160...");
int16_t data[6];
if (this->read_le_int16_(BMI160_REGISTER_DATA_GYRO_X_LSB, data, 6) != i2c::ERROR_OK) {
this->status_set_warning();
return;
}
float gyro_x = (float) data[0] / (float) INT16_MAX * 2000.f;
float gyro_y = (float) data[1] / (float) INT16_MAX * 2000.f;
float gyro_z = (float) data[2] / (float) INT16_MAX * 2000.f;
float accel_x = (float) data[3] / (float) INT16_MAX * 16 * GRAVITY_EARTH;
float accel_y = (float) data[4] / (float) INT16_MAX * 16 * GRAVITY_EARTH;
float accel_z = (float) data[5] / (float) INT16_MAX * 16 * GRAVITY_EARTH;
int16_t raw_temperature;
if (this->read_le_int16_(BMI160_REGISTER_DATA_TEMP_LSB, &raw_temperature, 1) != i2c::ERROR_OK) {
this->status_set_warning();
return;
}
float temperature = (float) raw_temperature / (float) INT16_MAX * 64.5f + 23.f;
ESP_LOGD(TAG,
"Got accel={x=%.3f m/s², y=%.3f m/s², z=%.3f m/s²}, "
"gyro={x=%.3f °/s, y=%.3f °/s, z=%.3f °/s}, temp=%.3f°C",
accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z, temperature);
if (this->accel_x_sensor_ != nullptr)
this->accel_x_sensor_->publish_state(accel_x);
if (this->accel_y_sensor_ != nullptr)
this->accel_y_sensor_->publish_state(accel_y);
if (this->accel_z_sensor_ != nullptr)
this->accel_z_sensor_->publish_state(accel_z);
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature);
if (this->gyro_x_sensor_ != nullptr)
this->gyro_x_sensor_->publish_state(gyro_x);
if (this->gyro_y_sensor_ != nullptr)
this->gyro_y_sensor_->publish_state(gyro_y);
if (this->gyro_z_sensor_ != nullptr)
this->gyro_z_sensor_->publish_state(gyro_z);
this->status_clear_warning();
}
float BMI160Component::get_setup_priority() const { return setup_priority::DATA; }
} // namespace bmi160
} // namespace esphome

View file

@ -0,0 +1,44 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace bmi160 {
class BMI160Component : public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
void update() override;
float get_setup_priority() const override;
void set_accel_x_sensor(sensor::Sensor *accel_x_sensor) { accel_x_sensor_ = accel_x_sensor; }
void set_accel_y_sensor(sensor::Sensor *accel_y_sensor) { accel_y_sensor_ = accel_y_sensor; }
void set_accel_z_sensor(sensor::Sensor *accel_z_sensor) { accel_z_sensor_ = accel_z_sensor; }
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
void set_gyro_x_sensor(sensor::Sensor *gyro_x_sensor) { gyro_x_sensor_ = gyro_x_sensor; }
void set_gyro_y_sensor(sensor::Sensor *gyro_y_sensor) { gyro_y_sensor_ = gyro_y_sensor; }
void set_gyro_z_sensor(sensor::Sensor *gyro_z_sensor) { gyro_z_sensor_ = gyro_z_sensor; }
protected:
sensor::Sensor *accel_x_sensor_{nullptr};
sensor::Sensor *accel_y_sensor_{nullptr};
sensor::Sensor *accel_z_sensor_{nullptr};
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *gyro_x_sensor_{nullptr};
sensor::Sensor *gyro_y_sensor_{nullptr};
sensor::Sensor *gyro_z_sensor_{nullptr};
void internal_setup_(int stage);
bool setup_complete_{false};
/** reads `len` 16-bit little-endian integers from the given i2c register */
i2c::ErrorCode read_le_int16_(uint8_t reg, int16_t *value, uint8_t len);
};
} // namespace bmi160
} // namespace esphome

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