Compare commits

..

No commits in common. "dev" and "2025.4.0" have entirely different histories.

1980 changed files with 28038 additions and 84968 deletions

View File

@ -1 +0,0 @@
07f621354fe1350ba51953c80273cd44a04aa44f15cc30bd7b8fe2a641427b7a

View File

@ -1,4 +1,2 @@
[run]
omit =
esphome/components/*
tests/integration/*
omit = esphome/components/*

View File

@ -1,37 +0,0 @@
ARG BUILD_BASE_VERSION=2025.04.0
FROM ghcr.io/esphome/docker-base:debian-${BUILD_BASE_VERSION} AS base
RUN git config --system --add safe.directory "*"
RUN apt update \
&& apt install -y \
protobuf-compiler
RUN pip install uv
RUN useradd esphome -m
USER esphome
ENV VIRTUAL_ENV=/home/esphome/.local/esphome-venv
RUN uv venv $VIRTUAL_ENV
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
# Override this set to true in the docker-base image
ENV UV_SYSTEM_PYTHON=false
WORKDIR /tmp
COPY requirements.txt ./
RUN uv pip install -r requirements.txt
COPY requirements_dev.txt requirements_test.txt ./
RUN uv pip install -r requirements_dev.txt -r requirements_test.txt
RUN \
platformio settings set enable_telemetry No \
&& platformio settings set check_platformio_interval 1000000
COPY script/platformio_install_deps.py platformio.ini ./
RUN ./platformio_install_deps.py platformio.ini --libraries --platforms --tools
WORKDIR /workspaces

View File

@ -1,17 +1,18 @@
{
"name": "ESPHome Dev",
"context": "..",
"dockerFile": "Dockerfile",
"image": "ghcr.io/esphome/esphome-lint:dev",
"postCreateCommand": [
"script/devcontainer-post-create"
],
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {}
"containerEnv": {
"DEVCONTAINER": "1",
"PIP_BREAK_SYSTEM_PACKAGES": "1",
"PIP_ROOT_USER_ACTION": "ignore"
},
"runArgs": [
"--privileged",
"-e",
"GIT_EDITOR=code --wait"
"ESPHOME_DASHBOARD_USE_PING=1"
// uncomment and edit the path in order to pass though local USB serial to the conatiner
// , "--device=/dev/ttyACM0"
],

View File

@ -114,5 +114,4 @@ config/
examples/
Dockerfile
.git/
tests/
.*
tests/build/

View File

@ -1,11 +1,15 @@
name: Build Image
inputs:
platform:
description: "Platform to build for"
required: true
example: "linux/amd64"
target:
description: "Target to build"
required: true
example: "docker"
build_type:
description: "Build type"
baseimg:
description: "Base image type"
required: true
example: "docker"
suffix:
@ -15,11 +19,6 @@ inputs:
description: "Version to build"
required: true
example: "2023.12.0"
base_os:
description: "Base OS to use"
required: false
default: "debian"
example: "debian"
runs:
using: "composite"
steps:
@ -47,52 +46,52 @@ runs:
- name: Build and push to ghcr by digest
id: build-ghcr
uses: docker/build-push-action@v6.18.0
uses: docker/build-push-action@v6.15.0
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
with:
context: .
file: ./docker/Dockerfile
platforms: ${{ inputs.platform }}
target: ${{ inputs.target }}
cache-from: type=gha
cache-to: ${{ steps.cache-to.outputs.value }}
build-args: |
BUILD_TYPE=${{ inputs.build_type }}
BASEIMGTYPE=${{ inputs.baseimg }}
BUILD_VERSION=${{ inputs.version }}
BUILD_OS=${{ inputs.base_os }}
outputs: |
type=image,name=ghcr.io/${{ steps.tags.outputs.image_name }},push-by-digest=true,name-canonical=true,push=true
- name: Export ghcr digests
shell: bash
run: |
mkdir -p /tmp/digests/${{ inputs.build_type }}/ghcr
mkdir -p /tmp/digests/${{ inputs.target }}/ghcr
digest="${{ steps.build-ghcr.outputs.digest }}"
touch "/tmp/digests/${{ inputs.build_type }}/ghcr/${digest#sha256:}"
touch "/tmp/digests/${{ inputs.target }}/ghcr/${digest#sha256:}"
- name: Build and push to dockerhub by digest
id: build-dockerhub
uses: docker/build-push-action@v6.18.0
uses: docker/build-push-action@v6.15.0
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
with:
context: .
file: ./docker/Dockerfile
platforms: ${{ inputs.platform }}
target: ${{ inputs.target }}
cache-from: type=gha
cache-to: ${{ steps.cache-to.outputs.value }}
build-args: |
BUILD_TYPE=${{ inputs.build_type }}
BASEIMGTYPE=${{ inputs.baseimg }}
BUILD_VERSION=${{ inputs.version }}
BUILD_OS=${{ inputs.base_os }}
outputs: |
type=image,name=docker.io/${{ steps.tags.outputs.image_name }},push-by-digest=true,name-canonical=true,push=true
- name: Export dockerhub digests
shell: bash
run: |
mkdir -p /tmp/digests/${{ inputs.build_type }}/dockerhub
mkdir -p /tmp/digests/${{ inputs.target }}/dockerhub
digest="${{ steps.build-dockerhub.outputs.digest }}"
touch "/tmp/digests/${{ inputs.build_type }}/dockerhub/${digest#sha256:}"
touch "/tmp/digests/${{ inputs.target }}/dockerhub/${digest#sha256:}"

View File

@ -17,7 +17,7 @@ runs:
steps:
- name: Set up Python ${{ inputs.python-version }}
id: python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v5.5.0
with:
python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment
@ -34,14 +34,14 @@ runs:
python -m venv venv
source venv/bin/activate
python --version
pip install -r requirements.txt -r requirements_test.txt
pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
pip install -e .
- name: Create Python virtual environment
if: steps.cache-venv.outputs.cache-hit != 'true' && runner.os == 'Windows'
shell: bash
run: |
python -m venv venv
source ./venv/Scripts/activate
./venv/Scripts/activate
python --version
pip install -r requirements.txt -r requirements_test.txt
pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
pip install -e .

View File

@ -17,6 +17,7 @@ updates:
docker-actions:
applies-to: version-updates
patterns:
- "docker/setup-qemu-action"
- "docker/login-action"
- "docker/setup-buildx-action"
- package-ecosystem: github-actions

View File

@ -21,9 +21,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v5.5.0
with:
python-version: "3.11"
@ -57,17 +57,6 @@ jobs:
event: 'REQUEST_CHANGES',
body: 'You have altered the generated proto files but they do not match what is expected.\nPlease run "script/api_protobuf/api_protobuf.py" and commit the changes.'
})
- if: failure()
name: Show changes
run: git diff
- if: failure()
name: Archive artifacts
uses: actions/upload-artifact@v4.6.2
with:
name: generated-proto-files
path: |
esphome/components/api/api_pb2.*
esphome/components/api/api_pb2_service.*
- if: success()
name: Dismiss review
uses: actions/github-script@v7.0.1

View File

@ -1,75 +0,0 @@
name: Clang-tidy Hash CI
on:
pull_request:
paths:
- ".clang-tidy"
- "platformio.ini"
- "requirements_dev.txt"
- ".clang-tidy.hash"
- "script/clang_tidy_hash.py"
- ".github/workflows/ci-clang-tidy-hash.yml"
permissions:
contents: read
pull-requests: write
jobs:
verify-hash:
name: Verify clang-tidy hash
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4.2.2
- name: Set up Python
uses: actions/setup-python@v5.6.0
with:
python-version: "3.11"
- name: Verify hash
run: |
python script/clang_tidy_hash.py --verify
- if: failure()
name: Show hash details
run: |
python script/clang_tidy_hash.py
echo "## Job Failed" | tee -a $GITHUB_STEP_SUMMARY
echo "You have modified clang-tidy configuration but have not updated the hash." | tee -a $GITHUB_STEP_SUMMARY
echo "Please run 'script/clang_tidy_hash.py --update' and commit the changes." | tee -a $GITHUB_STEP_SUMMARY
- if: failure()
name: Request changes
uses: actions/github-script@v7.0.1
with:
script: |
await github.rest.pulls.createReview({
pull_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
event: 'REQUEST_CHANGES',
body: 'You have modified clang-tidy configuration but have not updated the hash.\nPlease run `script/clang_tidy_hash.py --update` and commit the changes.'
})
- if: success()
name: Dismiss review
uses: actions/github-script@v7.0.1
with:
script: |
let reviews = await github.rest.pulls.listReviews({
pull_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo
});
for (let review of reviews.data) {
if (review.user.login === 'github-actions[bot]' && review.state === 'CHANGES_REQUESTED') {
await github.rest.pulls.dismissReview({
pull_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
review_id: review.id,
message: 'Clang-tidy hash now matches configuration.'
});
}
}

View File

@ -37,19 +37,16 @@ jobs:
strategy:
fail-fast: false
matrix:
os: ["ubuntu-24.04", "ubuntu-24.04-arm"]
build_type:
- "ha-addon"
- "docker"
# - "lint"
os: ["ubuntu-latest", "ubuntu-24.04-arm"]
build_type: ["ha-addon", "docker", "lint"]
steps:
- uses: actions/checkout@v4.2.2
- uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v5.5.0
with:
python-version: "3.10"
python-version: "3.9"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.11.1
uses: docker/setup-buildx-action@v3.10.0
- name: Set TAG
run: |

View File

@ -20,8 +20,8 @@ permissions:
contents: read
env:
DEFAULT_PYTHON: "3.10"
PYUPGRADE_TARGET: "--py310-plus"
DEFAULT_PYTHON: "3.9"
PYUPGRADE_TARGET: "--py39-plus"
concurrency:
# yamllint disable-line rule:line-length
@ -36,13 +36,13 @@ jobs:
cache-key: ${{ steps.cache-key.outputs.key }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Generate cache-key
id: cache-key
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v5.5.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
@ -58,19 +58,59 @@ jobs:
python -m venv venv
. venv/bin/activate
python --version
pip install -r requirements.txt -r requirements_test.txt pre-commit
pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
pip install -e .
ruff:
name: Check ruff
runs-on: ubuntu-24.04
needs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Run Ruff
run: |
. venv/bin/activate
ruff format esphome tests
- name: Suggested changes
run: script/ci-suggest-changes
if: always()
flake8:
name: Check flake8
runs-on: ubuntu-24.04
needs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Run flake8
run: |
. venv/bin/activate
flake8 esphome
- name: Suggested changes
run: script/ci-suggest-changes
if: always()
pylint:
name: Check pylint
runs-on: ubuntu-24.04
needs:
- common
- determine-jobs
if: needs.determine-jobs.outputs.python-linters == 'true'
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -84,6 +124,27 @@ jobs:
run: script/ci-suggest-changes
if: always()
pyupgrade:
name: Check pyupgrade
runs-on: ubuntu-24.04
needs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Run pyupgrade
run: |
. venv/bin/activate
pyupgrade ${{ env.PYUPGRADE_TARGET }} `find esphome -name "*.py" -type f`
- name: Suggested changes
run: script/ci-suggest-changes
if: always()
ci-custom:
name: Run script/ci-custom
runs-on: ubuntu-24.04
@ -91,7 +152,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -104,7 +165,6 @@ jobs:
. venv/bin/activate
script/ci-custom.py
script/build_codeowners.py --check
script/build_language_schema.py --check
pytest:
name: Run pytest
@ -112,10 +172,10 @@ jobs:
fail-fast: false
matrix:
python-version:
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- "3.13"
os:
- ubuntu-latest
- macOS-latest
@ -124,26 +184,25 @@ jobs:
# Minimize CI resource usage
# by only running the Python version
# version used for docker images on Windows and macOS
- python-version: "3.13"
os: windows-latest
- python-version: "3.12"
os: windows-latest
- python-version: "3.10"
os: windows-latest
- python-version: "3.13"
os: macOS-latest
- python-version: "3.9"
os: windows-latest
- python-version: "3.12"
os: macOS-latest
- python-version: "3.10"
os: macOS-latest
- python-version: "3.9"
os: macOS-latest
runs-on: ${{ matrix.os }}
needs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Restore Python
id: restore-python
uses: ./.github/actions/restore-python
with:
python-version: ${{ matrix.python-version }}
@ -153,108 +212,56 @@ jobs:
- name: Run pytest
if: matrix.os == 'windows-latest'
run: |
. ./venv/Scripts/activate.ps1
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
./venv/Scripts/activate
pytest -vv --cov-report=xml --tb=native tests
- name: Run pytest
if: matrix.os == 'ubuntu-latest' || matrix.os == 'macOS-latest'
run: |
. venv/bin/activate
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
pytest -vv --cov-report=xml --tb=native tests
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5.4.3
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Save Python virtual environment cache
if: github.ref == 'refs/heads/dev'
uses: actions/cache/save@v4.2.3
with:
path: venv
key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
determine-jobs:
name: Determine which jobs to run
clang-format:
name: Check clang-format
runs-on: ubuntu-24.04
needs:
- common
outputs:
integration-tests: ${{ steps.determine.outputs.integration-tests }}
clang-tidy: ${{ steps.determine.outputs.clang-tidy }}
python-linters: ${{ steps.determine.outputs.python-linters }}
changed-components: ${{ steps.determine.outputs.changed-components }}
component-test-count: ${{ steps.determine.outputs.component-test-count }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
with:
# Fetch enough history to find the merge base
fetch-depth: 2
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Determine which tests to run
id: determine
env:
GH_TOKEN: ${{ github.token }}
- name: Install clang-format
run: |
. venv/bin/activate
output=$(python script/determine-jobs.py)
echo "Test determination output:"
echo "$output" | jq
# Extract individual fields
echo "integration-tests=$(echo "$output" | jq -r '.integration_tests')" >> $GITHUB_OUTPUT
echo "clang-tidy=$(echo "$output" | jq -r '.clang_tidy')" >> $GITHUB_OUTPUT
echo "python-linters=$(echo "$output" | jq -r '.python_linters')" >> $GITHUB_OUTPUT
echo "changed-components=$(echo "$output" | jq -c '.changed_components')" >> $GITHUB_OUTPUT
echo "component-test-count=$(echo "$output" | jq -r '.component_test_count')" >> $GITHUB_OUTPUT
integration-tests:
name: Run integration tests
runs-on: ubuntu-latest
needs:
- common
- determine-jobs
if: needs.determine-jobs.outputs.integration-tests == 'true'
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Set up Python 3.13
id: python
uses: actions/setup-python@v5.6.0
with:
python-version: "3.13"
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v4.2.3
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
- name: Create Python virtual environment
if: steps.cache-venv.outputs.cache-hit != 'true'
run: |
python -m venv venv
. venv/bin/activate
python --version
pip install -r requirements.txt -r requirements_test.txt
pip install -e .
- name: Register matcher
run: echo "::add-matcher::.github/workflows/matchers/pytest.json"
- name: Run integration tests
pip install clang-format -c requirements_dev.txt
- name: Run clang-format
run: |
. venv/bin/activate
pytest -vv --no-cov --tb=native -n auto tests/integration/
script/clang-format -i
git diff-index --quiet HEAD --
- name: Suggested changes
run: script/ci-suggest-changes
if: always()
clang-tidy:
name: ${{ matrix.name }}
runs-on: ubuntu-24.04
needs:
- common
- determine-jobs
if: needs.determine-jobs.outputs.clang-tidy == 'true'
env:
GH_TOKEN: ${{ github.token }}
- ruff
- ci-custom
- clang-format
- flake8
- pylint
- pytest
- pyupgrade
strategy:
fail-fast: false
max-parallel: 2
@ -284,19 +291,10 @@ jobs:
name: Run script/clang-tidy for ESP32 IDF
options: --environment esp32-idf-tidy --grep USE_ESP_IDF
pio_cache_key: tidyesp32-idf
- id: clang-tidy
name: Run script/clang-tidy for ZEPHYR
options: --environment nrf52-tidy --grep USE_ZEPHYR
pio_cache_key: tidy-zephyr
ignore_errors: false
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
with:
# Need history for HEAD~1 to work for checking changed files
fetch-depth: 2
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -308,14 +306,14 @@ jobs:
uses: actions/cache@v4.2.3
with:
path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
key: platformio-${{ matrix.pio_cache_key }}
- name: Cache platformio
if: github.ref != 'refs/heads/dev'
uses: actions/cache/restore@v4.2.3
with:
path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
key: platformio-${{ matrix.pio_cache_key }}
- name: Register problem matchers
run: |
@ -329,49 +327,72 @@ jobs:
mkdir -p .temp
pio run --list-targets -e esp32-idf-tidy
- name: Check if full clang-tidy scan needed
id: check_full_scan
run: |
. venv/bin/activate
if python script/clang_tidy_hash.py --check; then
echo "full_scan=true" >> $GITHUB_OUTPUT
echo "reason=hash_changed" >> $GITHUB_OUTPUT
else
echo "full_scan=false" >> $GITHUB_OUTPUT
echo "reason=normal" >> $GITHUB_OUTPUT
fi
- name: Run clang-tidy
run: |
. venv/bin/activate
if [ "${{ steps.check_full_scan.outputs.full_scan }}" = "true" ]; then
echo "Running FULL clang-tidy scan (hash changed)"
script/clang-tidy --all-headers --fix ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
else
echo "Running clang-tidy on changed files only"
script/clang-tidy --all-headers --fix --changed ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
fi
script/clang-tidy --all-headers --fix ${{ matrix.options }}
env:
# Also cache libdeps, store them in a ~/.platformio subfolder
PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
- name: Suggested changes
run: script/ci-suggest-changes ${{ matrix.ignore_errors && '|| true' || '' }}
run: script/ci-suggest-changes
# yamllint disable-line rule:line-length
if: always()
list-components:
runs-on: ubuntu-24.04
needs:
- common
if: github.event_name == 'pull_request'
outputs:
components: ${{ steps.list-components.outputs.components }}
count: ${{ steps.list-components.outputs.count }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
with:
# Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works.
fetch-depth: 500
- name: Get target branch
id: target-branch
run: |
echo "branch=${{ github.event.pull_request.base.ref }}" >> $GITHUB_OUTPUT
- name: Fetch ${{ steps.target-branch.outputs.branch }} branch
run: |
git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +refs/heads/${{ steps.target-branch.outputs.branch }}:refs/remotes/origin/${{ steps.target-branch.outputs.branch }}
git merge-base refs/remotes/origin/${{ steps.target-branch.outputs.branch }} HEAD
- name: Restore Python
uses: ./.github/actions/restore-python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Find changed components
id: list-components
run: |
. venv/bin/activate
components=$(script/list-components.py --changed --branch ${{ steps.target-branch.outputs.branch }})
output_components=$(echo "$components" | jq -R -s -c 'split("\n")[:-1] | map(select(length > 0))')
count=$(echo "$output_components" | jq length)
echo "components=$output_components" >> $GITHUB_OUTPUT
echo "count=$count" >> $GITHUB_OUTPUT
echo "$count Components:"
echo "$output_components" | jq
test-build-components:
name: Component test ${{ matrix.file }}
runs-on: ubuntu-24.04
needs:
- common
- determine-jobs
if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) > 0 && fromJSON(needs.determine-jobs.outputs.component-test-count) < 100
- list-components
if: github.event_name == 'pull_request' && fromJSON(needs.list-components.outputs.count) > 0 && fromJSON(needs.list-components.outputs.count) < 100
strategy:
fail-fast: false
max-parallel: 2
matrix:
file: ${{ fromJson(needs.determine-jobs.outputs.changed-components) }}
file: ${{ fromJson(needs.list-components.outputs.components) }}
steps:
- name: Install dependencies
run: |
@ -379,7 +400,7 @@ jobs:
sudo apt-get install libsdl2-dev
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -399,17 +420,17 @@ jobs:
runs-on: ubuntu-24.04
needs:
- common
- determine-jobs
if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) >= 100
- list-components
if: github.event_name == 'pull_request' && fromJSON(needs.list-components.outputs.count) >= 100
outputs:
matrix: ${{ steps.split.outputs.components }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Split components into 20 groups
id: split
run: |
components=$(echo '${{ needs.determine-jobs.outputs.changed-components }}' | jq -c '.[]' | shuf | jq -s -c '[_nwise(20) | join(" ")]')
components=$(echo '${{ needs.list-components.outputs.components }}' | jq -c '.[]' | shuf | jq -s -c '[_nwise(20) | join(" ")]')
echo "components=$components" >> $GITHUB_OUTPUT
test-build-components-split:
@ -417,9 +438,9 @@ jobs:
runs-on: ubuntu-24.04
needs:
- common
- determine-jobs
- list-components
- test-build-components-splitter
if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) >= 100
if: github.event_name == 'pull_request' && fromJSON(needs.list-components.outputs.count) >= 100
strategy:
fail-fast: false
max-parallel: 4
@ -435,7 +456,7 @@ jobs:
sudo apt-get install libsdl2-dev
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -456,41 +477,23 @@ jobs:
./script/test_build_components -e compile -c $component
done
pre-commit-ci-lite:
name: pre-commit.ci lite
runs-on: ubuntu-latest
needs:
- common
if: github.event_name == 'pull_request' && github.base_ref != 'beta' && github.base_ref != 'release'
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Restore Python
uses: ./.github/actions/restore-python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- uses: pre-commit/action@v3.0.1
env:
SKIP: pylint,clang-tidy-hash
- uses: pre-commit-ci/lite-action@v1.1.0
if: always()
ci-status:
name: CI Status
runs-on: ubuntu-24.04
needs:
- common
- ruff
- ci-custom
- clang-format
- flake8
- pylint
- pytest
- integration-tests
- pyupgrade
- clang-tidy
- determine-jobs
- list-components
- test-build-components
- test-build-components-splitter
- test-build-components-split
- pre-commit-ci-lite
if: always()
steps:
- name: Success

View File

@ -1,11 +1,28 @@
---
name: Lock closed issues and PRs
name: Lock
on:
schedule:
- cron: "30 0 * * *" # Run daily at 00:30 UTC
- cron: "30 0 * * *"
workflow_dispatch:
permissions:
issues: write
pull-requests: write
concurrency:
group: lock
jobs:
lock:
uses: esphome/workflows/.github/workflows/lock.yml@main
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v5.0.1
with:
pr-inactive-days: "1"
pr-lock-reason: ""
exclude-any-pr-labels: keep-open
issue-inactive-days: "7"
issue-lock-reason: ""
exclude-any-issue-labels: keep-open

View File

@ -18,9 +18,8 @@ jobs:
outputs:
tag: ${{ steps.tag.outputs.tag }}
branch_build: ${{ steps.tag.outputs.branch_build }}
deploy_env: ${{ steps.tag.outputs.deploy_env }}
steps:
- uses: actions/checkout@v4.2.2
- uses: actions/checkout@v4.1.7
- name: Get tag
id: tag
# yamllint disable rule:line-length
@ -28,11 +27,6 @@ jobs:
if [[ "${{ github.event_name }}" = "release" ]]; then
TAG="${{ github.event.release.tag_name}}"
BRANCH_BUILD="false"
if [[ "${{ github.event.release.prerelease }}" = "true" ]]; then
ENVIRONMENT="beta"
else
ENVIRONMENT="production"
fi
else
TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p")
today="$(date --utc '+%Y%m%d')"
@ -41,15 +35,12 @@ jobs:
if [[ "$BRANCH" != "dev" ]]; then
TAG="${TAG}-${BRANCH}"
BRANCH_BUILD="true"
ENVIRONMENT=""
else
BRANCH_BUILD="false"
ENVIRONMENT="dev"
fi
fi
echo "tag=${TAG}" >> $GITHUB_OUTPUT
echo "branch_build=${BRANCH_BUILD}" >> $GITHUB_OUTPUT
echo "deploy_env=${ENVIRONMENT}" >> $GITHUB_OUTPUT
# yamllint enable rule:line-length
deploy-pypi:
@ -60,46 +51,48 @@ jobs:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4.2.2
- uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v5.5.0
with:
python-version: "3.x"
- name: Set up python environment
env:
ESPHOME_NO_VENV: 1
run: script/setup
- name: Build
run: |-
pip3 install build
python3 -m build
- name: Publish
uses: pypa/gh-action-pypi-publish@v1.12.4
with:
skip-existing: true
deploy-docker:
name: Build ESPHome ${{ matrix.platform.arch }}
name: Build ESPHome ${{ matrix.platform }}
if: github.repository == 'esphome/esphome'
permissions:
contents: read
packages: write
runs-on: ${{ matrix.platform.os }}
runs-on: ubuntu-latest
needs: [init]
strategy:
fail-fast: false
matrix:
platform:
- arch: amd64
os: "ubuntu-24.04"
- arch: arm64
os: "ubuntu-24.04-arm"
- linux/amd64
- linux/arm64
steps:
- uses: actions/checkout@v4.2.2
- uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v5.5.0
with:
python-version: "3.10"
python-version: "3.9"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.11.1
uses: docker/setup-buildx-action@v3.10.0
- name: Set up QEMU
if: matrix.platform != 'linux/amd64'
uses: docker/setup-qemu-action@v3.6.0
- name: Log in to docker hub
uses: docker/login-action@v3.4.0
@ -116,36 +109,45 @@ jobs:
- name: Build docker
uses: ./.github/actions/build-image
with:
target: final
build_type: docker
platform: ${{ matrix.platform }}
target: docker
baseimg: docker
suffix: ""
version: ${{ needs.init.outputs.tag }}
- name: Build ha-addon
uses: ./.github/actions/build-image
with:
target: final
build_type: ha-addon
platform: ${{ matrix.platform }}
target: hassio
baseimg: hassio
suffix: "hassio"
version: ${{ needs.init.outputs.tag }}
# - name: Build lint
# uses: ./.github/actions/build-image
# with:
# target: lint
# build_type: lint
# suffix: lint
# version: ${{ needs.init.outputs.tag }}
- name: Build lint
uses: ./.github/actions/build-image
with:
platform: ${{ matrix.platform }}
target: lint
baseimg: docker
suffix: lint
version: ${{ needs.init.outputs.tag }}
- name: Sanitize platform name
id: sanitize
run: |
echo "${{ matrix.platform }}" | sed 's|/|-|g' > /tmp/platform
echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT
- name: Upload digests
uses: actions/upload-artifact@v4.6.2
with:
name: digests-${{ matrix.platform.arch }}
name: digests-${{ steps.sanitize.outputs.name }}
path: /tmp/digests
retention-days: 1
deploy-manifest:
name: Publish ESPHome ${{ matrix.image.build_type }} to ${{ matrix.registry }}
name: Publish ESPHome ${{ matrix.image.title }} to ${{ matrix.registry }}
runs-on: ubuntu-latest
needs:
- init
@ -158,27 +160,30 @@ jobs:
fail-fast: false
matrix:
image:
- build_type: "docker"
suffix: ""
- build_type: "ha-addon"
- title: "ha-addon"
target: "hassio"
suffix: "hassio"
# - build_type: "lint"
# suffix: "lint"
- title: "docker"
target: "docker"
suffix: ""
- title: "lint"
target: "lint"
suffix: "lint"
registry:
- ghcr
- dockerhub
steps:
- uses: actions/checkout@v4.2.2
- uses: actions/checkout@v4.1.7
- name: Download digests
uses: actions/download-artifact@v4.3.0
uses: actions/download-artifact@v4.2.1
with:
pattern: digests-*
path: /tmp/digests
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.11.1
uses: docker/setup-buildx-action@v3.10.0
- name: Log in to docker hub
if: matrix.registry == 'dockerhub'
@ -207,7 +212,7 @@ jobs:
done
- name: Create manifest list and push
working-directory: /tmp/digests/${{ matrix.image.build_type }}/${{ matrix.registry }}
working-directory: /tmp/digests/${{ matrix.image.target }}/${{ matrix.registry }}
run: |
docker buildx imagetools create $(jq -Rcnr 'inputs | . / "," | map("-t " + .) | join(" ")' <<< "${{ steps.tags.outputs.tags}}") \
$(printf '${{ steps.tags.outputs.image }}@sha256:%s ' *)
@ -238,24 +243,3 @@ jobs:
content: description
}
})
deploy-esphome-schema:
if: github.repository == 'esphome/esphome' && needs.init.outputs.branch_build == 'false'
runs-on: ubuntu-latest
needs: [init]
environment: ${{ needs.init.outputs.deploy_env }}
steps:
- name: Trigger Workflow
uses: actions/github-script@v7.0.1
with:
github-token: ${{ secrets.DEPLOY_ESPHOME_SCHEMA_REPO_TOKEN }}
script: |
github.rest.actions.createWorkflowDispatch({
owner: "esphome",
repo: "esphome-schema",
workflow_id: "generate-schemas.yml",
ref: "main",
inputs: {
version: "${{ needs.init.outputs.tag }}",
}
})

View File

@ -13,18 +13,18 @@ jobs:
if: github.repository == 'esphome/esphome'
steps:
- name: Checkout
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
- name: Checkout Home Assistant
uses: actions/checkout@v4.2.2
uses: actions/checkout@v4.1.7
with:
repository: home-assistant/core
path: lib/home-assistant
- name: Setup Python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v5.5.0
with:
python-version: 3.13
python-version: 3.12
- name: Install Home Assistant
run: |

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

@ -0,0 +1,25 @@
---
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.1.7
- name: Run yamllint
uses: frenck/action-yamllint@v1.5.0
with:
strict: true

1
.gitignore vendored
View File

@ -143,4 +143,3 @@ sdkconfig.*
/components
/managed_components
api-docs/

View File

@ -1,17 +1,10 @@
---
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
ci:
autoupdate_commit_msg: 'pre-commit: autoupdate'
autoupdate_schedule: off # Disabled until ruff versions are synced between deps and pre-commit
# Skip hooks that have issues in pre-commit CI environment
skip: [pylint, clang-tidy-hash]
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.12.3
rev: v0.11.0
hooks:
# Run the linter.
- id: ruff
@ -19,7 +12,7 @@ repos:
# Run the formatter.
- id: ruff-format
- repo: https://github.com/PyCQA/flake8
rev: 7.3.0
rev: 7.2.0
hooks:
- id: flake8
additional_dependencies:
@ -27,25 +20,22 @@ repos:
- pydocstyle==5.1.1
files: ^(esphome|tests)/.+\.py$
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v3.4.0
hooks:
- id: no-commit-to-branch
args:
- --branch=dev
- --branch=release
- --branch=beta
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/asottile/pyupgrade
rev: v3.20.0
rev: v3.15.2
hooks:
- id: pyupgrade
args: [--py310-plus]
args: [--py39-plus]
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.37.1
rev: v1.35.1
hooks:
- id: yamllint
exclude: ^(\.clang-format|\.clang-tidy)$
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v13.0.1
hooks:
@ -58,10 +48,3 @@ repos:
entry: python3 script/run-in-env.py pylint
language: system
types: [python]
- id: clang-tidy-hash
name: Update clang-tidy hash
entry: python script/clang_tidy_hash.py --update-if-changed
language: python
files: ^(\.clang-tidy|platformio\.ini|requirements_dev\.txt)$
pass_filenames: false
additional_dependencies: []

View File

@ -28,7 +28,7 @@ esphome/components/aic3204/* @kbx81
esphome/components/airthings_ble/* @jeromelaban
esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau
esphome/components/airthings_wave_mini/* @ncareau
esphome/components/airthings_wave_plus/* @jeromelaban @precurse
esphome/components/airthings_wave_plus/* @jeromelaban
esphome/components/alarm_control_panel/* @grahambrown11 @hwstar
esphome/components/alpha3/* @jan-hofmeier
esphome/components/am2315c/* @swoboda1337
@ -87,7 +87,6 @@ esphome/components/bp1658cj/* @Cossid
esphome/components/bp5758d/* @Cossid
esphome/components/button/* @esphome/core
esphome/components/bytebuffer/* @clydebarrow
esphome/components/camera/* @DT-art1 @bdraco
esphome/components/canbus/* @danielschramm @mvturnho
esphome/components/cap1188/* @mreditor97
esphome/components/captive_portal/* @OttoWinter
@ -97,10 +96,8 @@ esphome/components/ch422g/* @clydebarrow @jesterret
esphome/components/chsc6x/* @kkosik20
esphome/components/climate/* @esphome/core
esphome/components/climate_ir/* @glmnet
esphome/components/cm1106/* @andrewjswan
esphome/components/color_temperature/* @jesserockz
esphome/components/combination/* @Cat-Ion @kahrendt
esphome/components/const/* @esphome/core
esphome/components/coolix/* @glmnet
esphome/components/copy/* @OttoWinter
esphome/components/cover/* @esphome/core
@ -125,7 +122,6 @@ esphome/components/dht/* @OttoWinter
esphome/components/display_menu_base/* @numo68
esphome/components/dps310/* @kbx81
esphome/components/ds1307/* @badbadc0ffee
esphome/components/ds2484/* @mrk-its
esphome/components/dsmr/* @glmnet @zuidwijk
esphome/components/duty_time/* @dudanov
esphome/components/ee895/* @Stock-M
@ -141,19 +137,16 @@ esphome/components/es7210/* @kahrendt
esphome/components/es7243e/* @kbx81
esphome/components/es8156/* @kbx81
esphome/components/es8311/* @kahrendt @kroimon
esphome/components/es8388/* @P4uLT
esphome/components/esp32/* @esphome/core
esphome/components/esp32_ble/* @Rapsssito @jesserockz
esphome/components/esp32_ble_client/* @jesserockz
esphome/components/esp32_ble_server/* @Rapsssito @clydebarrow @jesserockz
esphome/components/esp32_camera_web_server/* @ayufan
esphome/components/esp32_can/* @Sympatron
esphome/components/esp32_hosted/* @swoboda1337
esphome/components/esp32_improv/* @jesserockz
esphome/components/esp32_rmt/* @jesserockz
esphome/components/esp32_rmt_led_strip/* @jesserockz
esphome/components/esp8266/* @esphome/core
esphome/components/esp_ldo/* @clydebarrow
esphome/components/ethernet_info/* @gtjadsonsantos
esphome/components/event/* @nohat
esphome/components/event_emitter/* @Rapsssito
@ -170,13 +163,12 @@ esphome/components/ft5x06/* @clydebarrow
esphome/components/ft63x6/* @gpambrozio
esphome/components/gcja5/* @gcormier
esphome/components/gdk101/* @Szewcson
esphome/components/gl_r01_i2c/* @pkejval
esphome/components/globals/* @esphome/core
esphome/components/gp2y1010au0f/* @zry98
esphome/components/gp8403/* @jesserockz
esphome/components/gpio/* @esphome/core
esphome/components/gpio/one_wire/* @ssieb
esphome/components/gps/* @coogle @ximex
esphome/components/gps/* @coogle
esphome/components/graph/* @synco
esphome/components/graphical_display_menu/* @MrMDavidson
esphome/components/gree/* @orestismers
@ -240,7 +232,6 @@ esphome/components/kamstrup_kmp/* @cfeenstra1024
esphome/components/key_collector/* @ssieb
esphome/components/key_provider/* @ssieb
esphome/components/kuntze/* @ssieb
esphome/components/lc709203f/* @ilikecake
esphome/components/lcd_menu/* @numo68
esphome/components/ld2410/* @regevbr @sebcaps
esphome/components/ld2420/* @descipher
@ -251,17 +242,14 @@ esphome/components/libretiny_pwm/* @kuba2k2
esphome/components/light/* @esphome/core
esphome/components/lightwaverf/* @max246
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
esphome/components/ln882x/* @lamauny
esphome/components/lock/* @esphome/core
esphome/components/logger/* @esphome/core
esphome/components/logger/select/* @clydebarrow
esphome/components/lps22/* @nagisa
esphome/components/ltr390/* @latonita @sjtrny
esphome/components/ltr501/* @latonita
esphome/components/ltr_als_ps/* @latonita
esphome/components/lvgl/* @clydebarrow
esphome/components/m5stack_8angle/* @rnauber
esphome/components/mapping/* @clydebarrow
esphome/components/matrix_keypad/* @ssieb
esphome/components/max17043/* @blacknell
esphome/components/max31865/* @DAVe3283
@ -288,11 +276,10 @@ esphome/components/mdns/* @esphome/core
esphome/components/media_player/* @jesserockz
esphome/components/micro_wake_word/* @jesserockz @kahrendt
esphome/components/micronova/* @jorre05
esphome/components/microphone/* @jesserockz @kahrendt
esphome/components/microphone/* @jesserockz
esphome/components/mics_4514/* @jesserockz
esphome/components/midea/* @dudanov
esphome/components/midea_ir/* @dudanov
esphome/components/mipi_spi/* @clydebarrow
esphome/components/mitsubishi/* @RubyBailey
esphome/components/mixer/speaker/* @kahrendt
esphome/components/mlx90393/* @functionpointer
@ -328,22 +315,16 @@ esphome/components/number/* @esphome/core
esphome/components/one_wire/* @ssieb
esphome/components/online_image/* @clydebarrow @guillempages
esphome/components/opentherm/* @olegtarasov
esphome/components/openthread/* @mrene
esphome/components/opt3001/* @ccutrer
esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core
esphome/components/packet_transport/* @clydebarrow
esphome/components/pca6416a/* @Mat931
esphome/components/pca9554/* @clydebarrow @hwstar
esphome/components/pcf85063/* @brogon
esphome/components/pcf8563/* @KoenBreeman
esphome/components/pi4ioe5v6408/* @jesserockz
esphome/components/pid/* @OttoWinter
esphome/components/pipsolar/* @andreashergert1984
esphome/components/pm1006/* @habbie
esphome/components/pm2005/* @andrewjswan
esphome/components/pmsa003i/* @sjtrny
esphome/components/pmsx003/* @ximex
esphome/components/pmwcs3/* @SeByDocKy
esphome/components/pn532/* @OttoWinter @jesserockz
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
@ -412,7 +393,6 @@ esphome/components/smt100/* @piechade
esphome/components/sn74hc165/* @jesserockz
esphome/components/socket/* @esphome/core
esphome/components/sonoff_d1/* @anatoly-savchenkov
esphome/components/sound_level/* @kahrendt
esphome/components/speaker/* @jesserockz @kahrendt
esphome/components/speaker/media_player/* @kahrendt @synesthesiam
esphome/components/spi/* @clydebarrow @esphome/core
@ -444,9 +424,6 @@ esphome/components/sun/* @OttoWinter
esphome/components/sun_gtil2/* @Mat931
esphome/components/switch/* @esphome/core
esphome/components/switch/binary_sensor/* @ssieb
esphome/components/sx126x/* @swoboda1337
esphome/components/sx127x/* @swoboda1337
esphome/components/syslog/* @clydebarrow
esphome/components/t6615/* @tylermenezes
esphome/components/tc74/* @sethgirvan
esphome/components/tca9548a/* @andreashergert1984
@ -486,25 +463,21 @@ esphome/components/tuya/switch/* @jesserockz
esphome/components/tuya/text_sensor/* @dentra
esphome/components/uart/* @esphome/core
esphome/components/uart/button/* @ssieb
esphome/components/uart/packet_transport/* @clydebarrow
esphome/components/udp/* @clydebarrow
esphome/components/ufire_ec/* @pvizeli
esphome/components/ufire_ise/* @pvizeli
esphome/components/ultrasonic/* @OttoWinter
esphome/components/update/* @jesserockz
esphome/components/uponor_smatrix/* @kroimon
esphome/components/usb_host/* @clydebarrow
esphome/components/usb_uart/* @clydebarrow
esphome/components/valve/* @esphome/core
esphome/components/vbus/* @ssieb
esphome/components/veml3235/* @kbx81
esphome/components/veml7700/* @latonita
esphome/components/version/* @esphome/core
esphome/components/voice_assistant/* @jesserockz @kahrendt
esphome/components/voice_assistant/* @jesserockz
esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
esphome/components/watchdog/* @oarcher
esphome/components/waveshare_epaper/* @clydebarrow
esphome/components/web_server/ota/* @esphome/core
esphome/components/web_server_base/* @OttoWinter
esphome/components/web_server_idf/* @dentra
esphome/components/weikai/* @DrCoolZic
@ -531,7 +504,6 @@ esphome/components/xiaomi_lywsd03mmc/* @ahpohl
esphome/components/xiaomi_mhoc303/* @drug123
esphome/components/xiaomi_mhoc401/* @vevsvevs
esphome/components/xiaomi_rtcgq02lm/* @jesserockz
esphome/components/xiaomi_xmwsdj04mmc/* @medusalix
esphome/components/xl9535/* @mreditor97
esphome/components/xpt2046/touchscreen/* @nielsnl68 @numo68
esphome/components/xxtea/* @clydebarrow

2877
Doxyfile

File diff suppressed because it is too large Load Diff

View File

@ -1,56 +1,131 @@
ARG BUILD_VERSION=dev
ARG BUILD_OS=alpine
ARG BUILD_BASE_VERSION=2025.04.0
ARG BUILD_TYPE=docker
# Build these with the build.py script
# Example:
# python3 docker/build.py --tag dev --arch amd64 --build-type docker build
FROM ghcr.io/esphome/docker-base:${BUILD_OS}-${BUILD_BASE_VERSION} AS base-source-docker
FROM ghcr.io/esphome/docker-base:${BUILD_OS}-ha-addon-${BUILD_BASE_VERSION} AS base-source-ha-addon
# One of "docker", "hassio"
ARG BASEIMGTYPE=docker
ARG BUILD_TYPE
FROM base-source-${BUILD_TYPE} AS base
RUN git config --system --add safe.directory "*"
# https://github.com/hassio-addons/addon-debian-base/releases
FROM ghcr.io/hassio-addons/debian-base:7.2.0 AS base-hassio
# https://hub.docker.com/_/debian?tab=tags&page=1&name=bookworm
FROM debian:12.2-slim AS base-docker
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
FROM base-${BASEIMGTYPE} AS base
RUN pip install --no-cache-dir -U pip uv==0.6.14
COPY requirements.txt /
ARG TARGETARCH
ARG TARGETVARIANT
# Note that --break-system-packages is used below because
# https://peps.python.org/pep-0668/ added a safety check that prevents
# installing packages with the same name as a system package. This is
# not a problem for us because we are not concerned about overwriting
# system packages because we are running in an isolated container.
RUN \
uv pip install --no-cache-dir \
-r /requirements.txt
apt-get update \
# Use pinned versions so that we get updates with build caching
&& apt-get install -y --no-install-recommends \
python3-pip=23.0.1+dfsg-1 \
python3-setuptools=66.1.1-1+deb12u1 \
python3-venv=3.11.2-1+b1 \
python3-wheel=0.38.4-2 \
iputils-ping=3:20221126-1+deb12u1 \
git=1:2.39.5-0+deb12u2 \
curl=7.88.1-10+deb12u12 \
openssh-client=1:9.2p1-2+deb12u5 \
python3-cffi=1.15.1-5 \
libcairo2=1.16.0-7 \
libmagic1=1:5.44-3 \
patch=2.7.6-7 \
&& rm -rf \
/tmp/* \
/var/{cache,log}/* \
/var/lib/apt/lists/*
ENV \
# Fix click python3 lang warning https://click.palletsprojects.com/en/7.x/python3/
LANG=C.UTF-8 LC_ALL=C.UTF-8 \
# Store globally installed pio libs in /piolibs
PLATFORMIO_GLOBALLIB_DIR=/piolibs
RUN \
platformio settings set enable_telemetry No \
pip3 install \
--break-system-packages --no-cache-dir \
# Keep platformio version in sync with requirements.txt
platformio==6.1.18 \
# Change some platformio settings
&& platformio settings set enable_telemetry No \
&& platformio settings set check_platformio_interval 1000000 \
&& mkdir -p /piolibs
# First install requirements to leverage caching when requirements don't change
# tmpfs is for https://github.com/rust-lang/cargo/issues/8719
COPY requirements.txt requirements_optional.txt /
RUN --mount=type=tmpfs,target=/root/.cargo <<END-OF-RUN
# Fail on any non-zero status
set -e
# install build tools in case wheels are not available
BUILD_DEPS="
build-essential=12.9
python3-dev=3.11.2-1+b1
zlib1g-dev=1:1.2.13.dfsg-1
libjpeg-dev=1:2.1.5-2
libfreetype-dev=2.12.1+dfsg-5+deb12u4
libssl-dev=3.0.15-1~deb12u1
libffi-dev=3.4.4-1
cargo=0.66.0+ds1-1
pkg-config=1.8.1-1
"
LIB_DEPS="
libtiff6=4.5.0-6+deb12u1
libopenjp2-7=2.5.0-2+deb12u1
"
if [ "$TARGETARCH$TARGETVARIANT" = "arm64" ]
then
apt-get update
apt-get install -y --no-install-recommends $BUILD_DEPS $LIB_DEPS
fi
CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse CARGO_HOME=/root/.cargo
pip3 install --break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt
if [ "$TARGETARCH$TARGETVARIANT" = "arm64" ]
then
apt-get remove -y --purge --auto-remove $BUILD_DEPS
rm -rf /tmp/* /var/{cache,log}/* /var/lib/apt/lists/*
fi
END-OF-RUN
COPY script/platformio_install_deps.py platformio.ini /
RUN /platformio_install_deps.py /platformio.ini --libraries
ARG BUILD_VERSION
LABEL \
org.opencontainers.image.authors="The ESPHome Authors" \
org.opencontainers.image.title="ESPHome" \
org.opencontainers.image.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \
org.opencontainers.image.url="https://esphome.io/" \
org.opencontainers.image.documentation="https://esphome.io/" \
org.opencontainers.image.source="https://github.com/esphome/esphome" \
org.opencontainers.image.licenses="ESPHome" \
org.opencontainers.image.version=${BUILD_VERSION}
# Avoid unsafe git error when container user and file config volume permissions don't match
RUN git config --system --add safe.directory '*'
# ======================= docker-type image =======================
FROM base AS base-docker
FROM base AS docker
# Copy esphome and install
COPY . /esphome
RUN pip3 install --break-system-packages --no-cache-dir -e /esphome
# Settings for dashboard
ENV USERNAME="" PASSWORD=""
# Expose the dashboard to Docker
EXPOSE 6052
# Run healthcheck (heartbeat)
HEALTHCHECK --interval=30s --timeout=30s \
CMD curl --fail http://localhost:6052/version -A "HealthCheck" || exit 1
CMD curl --fail http://localhost:6052/version -A "HealthCheck" || exit 1
COPY docker/docker_entrypoint.sh /entrypoint.sh
@ -64,13 +139,43 @@ ENTRYPOINT ["/entrypoint.sh"]
CMD ["dashboard", "/config"]
# ======================= ha-addon-type image =======================
FROM base AS base-ha-addon
ARG BUILD_VERSION=dev
# Labels
LABEL \
org.opencontainers.image.authors="The ESPHome Authors" \
org.opencontainers.image.title="ESPHome" \
org.opencontainers.image.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \
org.opencontainers.image.url="https://esphome.io/" \
org.opencontainers.image.documentation="https://esphome.io/" \
org.opencontainers.image.source="https://github.com/esphome/esphome" \
org.opencontainers.image.licenses="ESPHome" \
org.opencontainers.image.version=${BUILD_VERSION}
# ======================= hassio-type image =======================
FROM base AS hassio
RUN \
apt-get update \
# Use pinned versions so that we get updates with build caching
&& apt-get install -y --no-install-recommends \
nginx-light=1.22.1-9+deb12u1 \
&& rm -rf \
/tmp/* \
/var/{cache,log}/* \
/var/lib/apt/lists/*
ARG BUILD_VERSION=dev
# Copy root filesystem
COPY docker/ha-addon-rootfs/ /
ARG BUILD_VERSION
# Copy esphome and install
COPY . /esphome
RUN pip3 install --break-system-packages --no-cache-dir -e /esphome
# Labels
LABEL \
io.hass.name="ESPHome" \
io.hass.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \
@ -78,9 +183,35 @@ LABEL \
io.hass.version="${BUILD_VERSION}"
# io.hass.arch is inherited from addon-debian-base
ARG BUILD_TYPE
FROM base-${BUILD_TYPE} AS final
# Copy esphome and install
COPY . /esphome
RUN uv pip install --no-cache-dir -e /esphome
# ======================= lint-type image =======================
FROM base AS lint
ENV \
PLATFORMIO_CORE_DIR=/esphome/.temp/platformio
RUN \
curl -L https://apt.llvm.org/llvm-snapshot.gpg.key -o /etc/apt/trusted.gpg.d/apt.llvm.org.asc \
&& echo "deb http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-18 main" > /etc/apt/sources.list.d/llvm.sources.list \
&& apt-get update \
# Use pinned versions so that we get updates with build caching
&& apt-get install -y --no-install-recommends \
clang-format-13=1:13.0.1-11+b2 \
patch=2.7.6-7 \
software-properties-common=0.99.30-4.1~deb12u1 \
nano=7.2-1+deb12u1 \
build-essential=12.9 \
python3-dev=3.11.2-1+b1 \
clang-tidy-18=1:18.1.8~++20240731024826+3b5b5c1ec4a3-1~exp1~20240731144843.145 \
&& rm -rf \
/tmp/* \
/var/{cache,log}/* \
/var/lib/apt/lists/*
COPY requirements_test.txt /
RUN pip3 install --break-system-packages --no-cache-dir -r /requirements_test.txt
VOLUME ["/esphome"]
WORKDIR /esphome

View File

@ -54,7 +54,7 @@ manifest_parser = subparsers.add_parser(
class DockerParams:
build_to: str
manifest_to: str
build_type: str
baseimgtype: str
platform: str
target: str
@ -66,19 +66,24 @@ class DockerParams:
TYPE_LINT: "esphome/esphome-lint",
}[build_type]
build_to = f"{prefix}-{arch}"
baseimgtype = {
TYPE_DOCKER: "docker",
TYPE_HA_ADDON: "hassio",
TYPE_LINT: "docker",
}[build_type]
platform = {
ARCH_AMD64: "linux/amd64",
ARCH_AARCH64: "linux/arm64",
}[arch]
target = {
TYPE_DOCKER: "final",
TYPE_HA_ADDON: "final",
TYPE_DOCKER: "docker",
TYPE_HA_ADDON: "hassio",
TYPE_LINT: "lint",
}[build_type]
return cls(
build_to=build_to,
manifest_to=prefix,
build_type=build_type,
baseimgtype=baseimgtype,
platform=platform,
target=target,
)
@ -140,7 +145,7 @@ def main():
"buildx",
"build",
"--build-arg",
f"BUILD_TYPE={params.build_type}",
f"BASEIMGTYPE={params.baseimgtype}",
"--build-arg",
f"BUILD_VERSION={args.tag}",
"--cache-from",

View File

@ -34,14 +34,16 @@ from esphome.const import (
CONF_PORT,
CONF_SUBSTITUTIONS,
CONF_TOPIC,
PLATFORM_BK72XX,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_RP2040,
PLATFORM_RTL87XX,
SECRETS_FILES,
)
from esphome.core import CORE, EsphomeError, coroutine
from esphome.helpers import get_bool_env, indent, is_ip_address
from esphome.log import AnsiFore, color, setup_log
from esphome.log import Fore, color, setup_log
from esphome.util import (
get_serial_ports,
list_yaml_files,
@ -81,7 +83,7 @@ def choose_prompt(options, purpose: str = None):
raise ValueError
break
except ValueError:
safe_print(color(AnsiFore.RED, f"Invalid option: '{opt}'"))
safe_print(color(Fore.RED, f"Invalid option: '{opt}'"))
return options[opt - 1][1]
@ -132,7 +134,6 @@ def get_port_type(port):
def run_miniterm(config, port, args):
from aioesphomeapi import LogParser
import serial
from esphome import platformio_api
@ -157,7 +158,6 @@ def run_miniterm(config, port, args):
ser.dtr = False
ser.rts = False
parser = LogParser()
tries = 0
while tries < 5:
try:
@ -174,7 +174,8 @@ def run_miniterm(config, port, args):
.decode("utf8", "backslashreplace")
)
time_str = datetime.now().time().strftime("[%H:%M:%S]")
safe_print(parser.parse_line(line, time_str))
message = time_str + line
safe_print(message)
backtrace_state = platformio_api.process_stacktrace(
config, line, backtrace_state=backtrace_state
@ -352,7 +353,7 @@ def upload_program(config, args, host):
if CORE.target_platform in (PLATFORM_RP2040):
return upload_using_platformio(config, args.device)
if CORE.is_libretiny:
if CORE.target_platform in (PLATFORM_BK72XX, PLATFORM_RTL87XX):
return upload_using_platformio(config, host)
return 1 # Unknown target platform
@ -592,38 +593,33 @@ def command_update_all(args):
middle_text = f" {middle_text} "
width = len(click.unstyle(middle_text))
half_line = "=" * ((twidth - width) // 2)
safe_print(f"{half_line}{middle_text}{half_line}")
click.echo(f"{half_line}{middle_text}{half_line}")
for f in files:
safe_print(f"Updating {color(AnsiFore.CYAN, f)}")
safe_print("-" * twidth)
safe_print()
if CORE.dashboard:
rc = run_external_process(
"esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
)
else:
rc = run_external_process(
"esphome", "run", f, "--no-logs", "--device", "OTA"
)
print(f"Updating {color(Fore.CYAN, f)}")
print("-" * twidth)
print()
rc = run_external_process(
"esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
)
if rc == 0:
print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {f}")
print_bar(f"[{color(Fore.BOLD_GREEN, 'SUCCESS')}] {f}")
success[f] = True
else:
print_bar(f"[{color(AnsiFore.BOLD_RED, 'ERROR')}] {f}")
print_bar(f"[{color(Fore.BOLD_RED, 'ERROR')}] {f}")
success[f] = False
safe_print()
safe_print()
safe_print()
print()
print()
print()
print_bar(f"[{color(AnsiFore.BOLD_WHITE, 'SUMMARY')}]")
print_bar(f"[{color(Fore.BOLD_WHITE, 'SUMMARY')}]")
failed = 0
for f in files:
if success[f]:
safe_print(f" - {f}: {color(AnsiFore.GREEN, 'SUCCESS')}")
print(f" - {f}: {color(Fore.GREEN, 'SUCCESS')}")
else:
safe_print(f" - {f}: {color(AnsiFore.BOLD_RED, 'FAILED')}")
print(f" - {f}: {color(Fore.BOLD_RED, 'FAILED')}")
failed += 1
return failed
@ -649,7 +645,7 @@ def command_rename(args, config):
if c not in ALLOWED_NAME_CHARS:
print(
color(
AnsiFore.BOLD_RED,
Fore.BOLD_RED,
f"'{c}' is an invalid character for names. Valid characters are: "
f"{ALLOWED_NAME_CHARS} (lowercase, no spaces)",
)
@ -662,9 +658,7 @@ def command_rename(args, config):
yaml = yaml_util.load_yaml(CORE.config_path)
if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]:
print(
color(
AnsiFore.BOLD_RED, "Complex YAML files cannot be automatically renamed."
)
color(Fore.BOLD_RED, "Complex YAML files cannot be automatically renamed.")
)
return 1
old_name = yaml[CONF_ESPHOME][CONF_NAME]
@ -687,7 +681,7 @@ def command_rename(args, config):
)
> 1
):
print(color(AnsiFore.BOLD_RED, "Too many matches in YAML to safely rename"))
print(color(Fore.BOLD_RED, "Too many matches in YAML to safely rename"))
return 1
new_raw = re.sub(
@ -699,7 +693,7 @@ def command_rename(args, config):
new_path = os.path.join(CORE.config_dir, args.name + ".yaml")
print(
f"Updating {color(AnsiFore.CYAN, CORE.config_path)} to {color(AnsiFore.CYAN, new_path)}"
f"Updating {color(Fore.CYAN, CORE.config_path)} to {color(Fore.CYAN, new_path)}"
)
print()
@ -708,7 +702,7 @@ def command_rename(args, config):
rc = run_external_process("esphome", "config", new_path)
if rc != 0:
print(color(AnsiFore.BOLD_RED, "Rename failed. Reverting changes."))
print(color(Fore.BOLD_RED, "Rename failed. Reverting changes."))
os.remove(new_path)
return 1
@ -734,7 +728,7 @@ def command_rename(args, config):
if CORE.config_path != new_path:
os.remove(CORE.config_path)
print(color(AnsiFore.BOLD_GREEN, "SUCCESS"))
print(color(Fore.BOLD_GREEN, "SUCCESS"))
print()
return 0

View File

@ -22,7 +22,6 @@ from esphome.cpp_generator import ( # noqa: F401
TemplateArguments,
add,
add_build_flag,
add_build_unflag,
add_define,
add_global,
add_library,
@ -35,7 +34,6 @@ from esphome.cpp_generator import ( # noqa: F401
process_lambda,
progmem_array,
safe_exp,
set_cpp_standard,
statement,
static_const_array,
templatable,

View File

@ -7,7 +7,7 @@ namespace a4988 {
static const char *const TAG = "a4988.stepper";
void A4988::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up A4988...");
if (this->sleep_pin_ != nullptr) {
this->sleep_pin_->setup();
this->sleep_pin_->digital_write(false);

View File

@ -7,7 +7,7 @@ namespace absolute_humidity {
static const char *const TAG = "absolute_humidity.sensor";
void AbsoluteHumidityComponent::setup() {
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str());
ESP_LOGCONFIG(TAG, "Setting up absolute humidity '%s'...", this->get_name().c_str());
ESP_LOGD(TAG, " Added callback for temperature '%s'", this->temperature_sensor_->get_name().c_str());
this->temperature_sensor_->add_on_state_callback([this](float state) { this->temperature_callback_(state); });
@ -40,11 +40,9 @@ void AbsoluteHumidityComponent::dump_config() {
break;
}
ESP_LOGCONFIG(TAG,
"Sources\n"
" Temperature: '%s'\n"
" Relative Humidity: '%s'",
this->temperature_sensor_->get_name().c_str(), this->humidity_sensor_->get_name().c_str());
ESP_LOGCONFIG(TAG, "Sources");
ESP_LOGCONFIG(TAG, " Temperature: '%s'", this->temperature_sensor_->get_name().c_str());
ESP_LOGCONFIG(TAG, " Relative Humidity: '%s'", this->humidity_sensor_->get_name().c_str());
}
float AbsoluteHumidityComponent::get_setup_priority() const { return setup_priority::DATA; }

View File

@ -4,7 +4,6 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <cmath>
#include <numbers>
#ifdef USE_ESP8266
#include <core_esp8266_waveform.h>
@ -115,14 +114,13 @@ void IRAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
// fully off, disable output immediately
this->gate_pin.digital_write(false);
} else {
auto min_us = this->cycle_time_us * this->min_power / 1000;
if (this->method == DIM_METHOD_TRAILING) {
this->enable_time_us = 1; // cannot be 0
// calculate time until disable in µs with integer arithmetic and take into account min_power
this->disable_time_us = std::max((uint32_t) 10, this->value * (this->cycle_time_us - min_us) / 65535 + min_us);
this->disable_time_us = std::max((uint32_t) 10, this->value * this->cycle_time_us / 65535);
} else {
// calculate time until enable in µs: (1.0-value)*cycle_time, but with integer arithmetic
// also take into account min_power
auto min_us = this->cycle_time_us * this->min_power / 1000;
this->enable_time_us = std::max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535);
if (this->method == DIM_METHOD_LEADING_PULSE) {
@ -194,17 +192,18 @@ void AcDimmer::setup() {
setTimer1Callback(&timer_interrupt);
#endif
#ifdef USE_ESP32
// timer frequency of 1mhz
dimmer_timer = timerBegin(1000000);
timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr);
// 80 Divider -> 1 count=1µs
dimmer_timer = timerBegin(0, 80, true);
timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr, true);
// For ESP32, we can't use dynamic interval calculation because the timerX functions
// are not callable from ISR (placed in flash storage).
// Here we just use an interrupt firing every 50 µs.
timerAlarm(dimmer_timer, 50, true, 0);
timerAlarmWrite(dimmer_timer, 50, true);
timerAlarmEnable(dimmer_timer);
#endif
}
void AcDimmer::write_state(float state) {
state = std::acos(1 - (2 * state)) / std::numbers::pi; // RMS power compensation
state = std::acos(1 - (2 * state)) / 3.14159; // RMS power compensation
auto new_value = static_cast<uint16_t>(roundf(state * 65535));
if (new_value != 0 && this->store_.value == 0)
this->store_.init_cycle = this->init_with_half_cycle_;
@ -214,10 +213,8 @@ void AcDimmer::dump_config() {
ESP_LOGCONFIG(TAG, "AcDimmer:");
LOG_PIN(" Output Pin: ", this->gate_pin_);
LOG_PIN(" Zero-Cross Pin: ", this->zero_cross_pin_);
ESP_LOGCONFIG(TAG,
" Min Power: %.1f%%\n"
" Init with half cycle: %s",
this->store_.min_power / 10.0f, YESNO(this->init_with_half_cycle_));
ESP_LOGCONFIG(TAG, " Min Power: %.1f%%", this->store_.min_power / 10.0f);
ESP_LOGCONFIG(TAG, " Init with half cycle: %s", YESNO(this->init_with_half_cycle_));
if (method_ == DIM_METHOD_LEADING_PULSE) {
ESP_LOGCONFIG(TAG, " Method: leading pulse");
} else if (method_ == DIM_METHOD_LEADING) {

View File

@ -10,15 +10,8 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S2,
VARIANT_ESP32S3,
)
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
CONF_ANALOG,
CONF_INPUT,
CONF_NUMBER,
PLATFORM_ESP8266,
PlatformFramework,
)
from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER, PLATFORM_ESP8266
from esphome.core import CORE
CODEOWNERS = ["@esphome/core"]
@ -54,10 +47,9 @@ SAMPLING_MODES = {
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
# pin to adc1 channel mapping
# https://github.com/espressif/esp-idf/blob/v4.4.8/components/driver/include/driver/adc.h
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32/include/soc/adc_channel.h
VARIANT_ESP32: {
36: adc1_channel_t.ADC1_CHANNEL_0,
37: adc1_channel_t.ADC1_CHANNEL_1,
@ -68,41 +60,6 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
34: adc1_channel_t.ADC1_CHANNEL_6,
35: adc1_channel_t.ADC1_CHANNEL_7,
},
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c2/include/soc/adc_channel.h
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,
},
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c3/include/soc/adc_channel.h
VARIANT_ESP32C3: {
0: adc1_channel_t.ADC1_CHANNEL_0,
1: adc1_channel_t.ADC1_CHANNEL_1,
2: adc1_channel_t.ADC1_CHANNEL_2,
3: adc1_channel_t.ADC1_CHANNEL_3,
4: adc1_channel_t.ADC1_CHANNEL_4,
},
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c6/include/soc/adc_channel.h
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,
},
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32h2/include/soc/adc_channel.h
VARIANT_ESP32H2: {
1: adc1_channel_t.ADC1_CHANNEL_0,
2: adc1_channel_t.ADC1_CHANNEL_1,
3: adc1_channel_t.ADC1_CHANNEL_2,
4: adc1_channel_t.ADC1_CHANNEL_3,
5: adc1_channel_t.ADC1_CHANNEL_4,
},
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/adc_channel.h
VARIANT_ESP32S2: {
1: adc1_channel_t.ADC1_CHANNEL_0,
2: adc1_channel_t.ADC1_CHANNEL_1,
@ -115,7 +72,6 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
9: adc1_channel_t.ADC1_CHANNEL_8,
10: adc1_channel_t.ADC1_CHANNEL_9,
},
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s3/include/soc/adc_channel.h
VARIANT_ESP32S3: {
1: adc1_channel_t.ADC1_CHANNEL_0,
2: adc1_channel_t.ADC1_CHANNEL_1,
@ -128,12 +84,40 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
9: adc1_channel_t.ADC1_CHANNEL_8,
10: adc1_channel_t.ADC1_CHANNEL_9,
},
VARIANT_ESP32C3: {
0: adc1_channel_t.ADC1_CHANNEL_0,
1: adc1_channel_t.ADC1_CHANNEL_1,
2: adc1_channel_t.ADC1_CHANNEL_2,
3: adc1_channel_t.ADC1_CHANNEL_3,
4: adc1_channel_t.ADC1_CHANNEL_4,
},
VARIANT_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: {
1: adc1_channel_t.ADC1_CHANNEL_0,
2: adc1_channel_t.ADC1_CHANNEL_1,
3: adc1_channel_t.ADC1_CHANNEL_2,
4: adc1_channel_t.ADC1_CHANNEL_3,
5: adc1_channel_t.ADC1_CHANNEL_4,
},
}
# pin to adc2 channel mapping
# https://github.com/espressif/esp-idf/blob/v4.4.8/components/driver/include/driver/adc.h
ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32/include/soc/adc_channel.h
# TODO: add other variants
VARIANT_ESP32: {
4: adc2_channel_t.ADC2_CHANNEL_0,
0: adc2_channel_t.ADC2_CHANNEL_1,
@ -146,19 +130,6 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
25: adc2_channel_t.ADC2_CHANNEL_8,
26: adc2_channel_t.ADC2_CHANNEL_9,
},
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c2/include/soc/adc_channel.h
VARIANT_ESP32C2: {
5: adc2_channel_t.ADC2_CHANNEL_0,
},
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c3/include/soc/adc_channel.h
VARIANT_ESP32C3: {
5: adc2_channel_t.ADC2_CHANNEL_0,
},
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c6/include/soc/adc_channel.h
VARIANT_ESP32C6: {}, # no ADC2
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32h2/include/soc/adc_channel.h
VARIANT_ESP32H2: {}, # no ADC2
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/adc_channel.h
VARIANT_ESP32S2: {
11: adc2_channel_t.ADC2_CHANNEL_0,
12: adc2_channel_t.ADC2_CHANNEL_1,
@ -171,7 +142,6 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
19: adc2_channel_t.ADC2_CHANNEL_8,
20: adc2_channel_t.ADC2_CHANNEL_9,
},
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s3/include/soc/adc_channel.h
VARIANT_ESP32S3: {
11: adc2_channel_t.ADC2_CHANNEL_0,
12: adc2_channel_t.ADC2_CHANNEL_1,
@ -184,6 +154,12 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
19: adc2_channel_t.ADC2_CHANNEL_8,
20: adc2_channel_t.ADC2_CHANNEL_9,
},
VARIANT_ESP32C3: {
5: adc2_channel_t.ADC2_CHANNEL_0,
},
VARIANT_ESP32C2: {},
VARIANT_ESP32C6: {},
VARIANT_ESP32H2: {},
}
@ -236,20 +212,3 @@ def validate_adc_pin(value):
)(value)
raise NotImplementedError
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"adc_sensor_esp32.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"adc_sensor_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"adc_sensor_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"adc_sensor_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
}
)

View File

@ -15,7 +15,8 @@ namespace adc {
#ifdef USE_ESP32
// clang-format off
#if (ESP_IDF_VERSION_MAJOR == 5 && \
#if (ESP_IDF_VERSION_MAJOR == 4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 7)) || \
(ESP_IDF_VERSION_MAJOR == 5 && \
((ESP_IDF_VERSION_MINOR == 0 && ESP_IDF_VERSION_PATCH >= 5) || \
(ESP_IDF_VERSION_MINOR == 1 && ESP_IDF_VERSION_PATCH >= 3) || \
(ESP_IDF_VERSION_MINOR >= 2)) \
@ -27,24 +28,19 @@ static const adc_atten_t ADC_ATTEN_DB_12_COMPAT = ADC_ATTEN_DB_11;
#endif
#endif // USE_ESP32
enum class SamplingMode : uint8_t {
AVG = 0,
MIN = 1,
MAX = 2,
};
enum class SamplingMode : uint8_t { AVG = 0, MIN = 1, MAX = 2 };
const LogString *sampling_mode_to_str(SamplingMode mode);
class Aggregator {
public:
Aggregator(SamplingMode mode);
void add_sample(uint32_t value);
uint32_t aggregate();
Aggregator(SamplingMode mode);
protected:
SamplingMode mode_{SamplingMode::AVG};
uint32_t aggr_{0};
uint32_t samples_{0};
SamplingMode mode_{SamplingMode::AVG};
};
class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
@ -85,9 +81,9 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
#endif // USE_RP2040
protected:
uint8_t sample_count_{1};
bool output_raw_{false};
InternalGPIOPin *pin_;
bool output_raw_{false};
uint8_t sample_count_{1};
SamplingMode sampling_mode_{SamplingMode::AVG};
#ifdef USE_RP2040
@ -99,7 +95,11 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
adc1_channel_t channel1_{ADC1_CHANNEL_MAX};
adc2_channel_t channel2_{ADC2_CHANNEL_MAX};
bool autorange_{false};
#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 // ESP_IDF_VERSION_MAJOR
#endif // USE_ESP32
};

View File

@ -61,7 +61,7 @@ uint32_t Aggregator::aggregate() {
void ADCSensor::update() {
float value_v = this->sample();
ESP_LOGV(TAG, "'%s': Voltage=%.4fV", this->get_name().c_str(), value_v);
ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
this->publish_state(value_v);
}

View File

@ -22,7 +22,7 @@ static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1;
static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1;
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str());
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
if (this->channel1_ != ADC1_CHANNEL_MAX) {
adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);
@ -55,40 +55,30 @@ void ADCSensor::setup() {
}
void ADCSensor::dump_config() {
static const char *const ATTEN_AUTO_STR = "auto";
static const char *const ATTEN_0DB_STR = "0 db";
static const char *const ATTEN_2_5DB_STR = "2.5 db";
static const char *const ATTEN_6DB_STR = "6 db";
static const char *const ATTEN_12DB_STR = "12 db";
const char *atten_str = ATTEN_AUTO_STR;
LOG_SENSOR("", "ADC Sensor", this);
LOG_PIN(" Pin: ", this->pin_);
if (!this->autorange_) {
if (this->autorange_) {
ESP_LOGCONFIG(TAG, " Attenuation: auto");
} else {
switch (this->attenuation_) {
case ADC_ATTEN_DB_0:
atten_str = ATTEN_0DB_STR;
ESP_LOGCONFIG(TAG, " Attenuation: 0db");
break;
case ADC_ATTEN_DB_2_5:
atten_str = ATTEN_2_5DB_STR;
ESP_LOGCONFIG(TAG, " Attenuation: 2.5db");
break;
case ADC_ATTEN_DB_6:
atten_str = ATTEN_6DB_STR;
ESP_LOGCONFIG(TAG, " Attenuation: 6db");
break;
case ADC_ATTEN_DB_12_COMPAT:
atten_str = ATTEN_12DB_STR;
ESP_LOGCONFIG(TAG, " Attenuation: 12db");
break;
default: // This is to satisfy the unused ADC_ATTEN_MAX
break;
}
}
ESP_LOGCONFIG(TAG,
" Attenuation: %s\n"
" Samples: %i\n"
" Sampling mode: %s",
atten_str, this->sample_count_, LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_);
ESP_LOGCONFIG(TAG, " Sampling mode: %s", LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
LOG_UPDATE_INTERVAL(this);
}

View File

@ -17,7 +17,7 @@ namespace adc {
static const char *const TAG = "adc.esp8266";
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str());
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
#ifndef USE_ADC_SENSOR_VCC
this->pin_->setup();
#endif
@ -30,10 +30,8 @@ void ADCSensor::dump_config() {
#else
LOG_PIN(" Pin: ", this->pin_);
#endif // USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG,
" Samples: %i\n"
" Sampling mode: %s",
this->sample_count_, LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_);
ESP_LOGCONFIG(TAG, " Sampling mode: %s", LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
LOG_UPDATE_INTERVAL(this);
}

View File

@ -9,7 +9,7 @@ namespace adc {
static const char *const TAG = "adc.libretiny";
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str());
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
#ifndef USE_ADC_SENSOR_VCC
this->pin_->setup();
#endif // !USE_ADC_SENSOR_VCC
@ -22,10 +22,8 @@ void ADCSensor::dump_config() {
#else // USE_ADC_SENSOR_VCC
LOG_PIN(" Pin: ", this->pin_);
#endif // USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG,
" Samples: %i\n"
" Sampling mode: %s",
this->sample_count_, LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_);
ESP_LOGCONFIG(TAG, " Sampling mode: %s", LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
LOG_UPDATE_INTERVAL(this);
}

View File

@ -14,7 +14,7 @@ namespace adc {
static const char *const TAG = "adc.rp2040";
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str());
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
static bool initialized = false;
if (!initialized) {
adc_init();
@ -33,10 +33,8 @@ void ADCSensor::dump_config() {
LOG_PIN(" Pin: ", this->pin_);
#endif // USE_ADC_SENSOR_VCC
}
ESP_LOGCONFIG(TAG,
" Samples: %i\n"
" Sampling mode: %s",
this->sample_count_, LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_);
ESP_LOGCONFIG(TAG, " Sampling mode: %s", LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
LOG_UPDATE_INTERVAL(this);
}

View File

@ -9,7 +9,7 @@ static const char *const TAG = "adc128s102";
float ADC128S102::get_setup_priority() const { return setup_priority::HARDWARE; }
void ADC128S102::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up adc128s102");
this->spi_setup();
}

View File

@ -177,14 +177,11 @@ void ADE7880::dump_config() {
LOG_SENSOR(" ", "Power Factor", this->channel_a_->power_factor);
LOG_SENSOR(" ", "Forward Active Energy", this->channel_a_->forward_active_energy);
LOG_SENSOR(" ", "Reverse Active Energy", this->channel_a_->reverse_active_energy);
ESP_LOGCONFIG(TAG,
" Calibration:\n"
" Current: %" PRId32 "\n"
" Voltage: %" PRId32 "\n"
" Power: %" PRId32 "\n"
" Phase Angle: %u",
this->channel_a_->current_gain_calibration, this->channel_a_->voltage_gain_calibration,
this->channel_a_->power_gain_calibration, this->channel_a_->phase_angle_calibration);
ESP_LOGCONFIG(TAG, " Calibration:");
ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_a_->current_gain_calibration);
ESP_LOGCONFIG(TAG, " Voltage: %" PRId32, this->channel_a_->voltage_gain_calibration);
ESP_LOGCONFIG(TAG, " Power: %" PRId32, this->channel_a_->power_gain_calibration);
ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_a_->phase_angle_calibration);
}
if (this->channel_b_ != nullptr) {
@ -196,14 +193,11 @@ void ADE7880::dump_config() {
LOG_SENSOR(" ", "Power Factor", this->channel_b_->power_factor);
LOG_SENSOR(" ", "Forward Active Energy", this->channel_b_->forward_active_energy);
LOG_SENSOR(" ", "Reverse Active Energy", this->channel_b_->reverse_active_energy);
ESP_LOGCONFIG(TAG,
" Calibration:\n"
" Current: %" PRId32 "\n"
" Voltage: %" PRId32 "\n"
" Power: %" PRId32 "\n"
" Phase Angle: %u",
this->channel_b_->current_gain_calibration, this->channel_b_->voltage_gain_calibration,
this->channel_b_->power_gain_calibration, this->channel_b_->phase_angle_calibration);
ESP_LOGCONFIG(TAG, " Calibration:");
ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_b_->current_gain_calibration);
ESP_LOGCONFIG(TAG, " Voltage: %" PRId32, this->channel_b_->voltage_gain_calibration);
ESP_LOGCONFIG(TAG, " Power: %" PRId32, this->channel_b_->power_gain_calibration);
ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_b_->phase_angle_calibration);
}
if (this->channel_c_ != nullptr) {
@ -215,23 +209,18 @@ void ADE7880::dump_config() {
LOG_SENSOR(" ", "Power Factor", this->channel_c_->power_factor);
LOG_SENSOR(" ", "Forward Active Energy", this->channel_c_->forward_active_energy);
LOG_SENSOR(" ", "Reverse Active Energy", this->channel_c_->reverse_active_energy);
ESP_LOGCONFIG(TAG,
" Calibration:\n"
" Current: %" PRId32 "\n"
" Voltage: %" PRId32 "\n"
" Power: %" PRId32 "\n"
" Phase Angle: %u",
this->channel_c_->current_gain_calibration, this->channel_c_->voltage_gain_calibration,
this->channel_c_->power_gain_calibration, this->channel_c_->phase_angle_calibration);
ESP_LOGCONFIG(TAG, " Calibration:");
ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_c_->current_gain_calibration);
ESP_LOGCONFIG(TAG, " Voltage: %" PRId32, this->channel_c_->voltage_gain_calibration);
ESP_LOGCONFIG(TAG, " Power: %" PRId32, this->channel_c_->power_gain_calibration);
ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_c_->phase_angle_calibration);
}
if (this->channel_n_ != nullptr) {
ESP_LOGCONFIG(TAG, " Neutral:");
LOG_SENSOR(" ", "Current", this->channel_n_->current);
ESP_LOGCONFIG(TAG,
" Calibration:\n"
" Current: %" PRId32,
this->channel_n_->current_gain_calibration);
ESP_LOGCONFIG(TAG, " Calibration:");
ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_n_->current_gain_calibration);
}
LOG_I2C_DEVICE(this);

View File

@ -85,6 +85,8 @@ class ADE7880 : public i2c::I2CDevice, public PollingComponent {
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
protected:
ADE7880Store store_{};
InternalGPIOPin *irq0_pin_{nullptr};

View File

@ -58,18 +58,15 @@ void ADE7953::dump_config() {
LOG_SENSOR(" ", "Active Power B Sensor", this->active_power_b_sensor_);
LOG_SENSOR(" ", "Rective Power A Sensor", this->reactive_power_a_sensor_);
LOG_SENSOR(" ", "Reactive Power B Sensor", this->reactive_power_b_sensor_);
ESP_LOGCONFIG(TAG,
" USE_ACC_ENERGY_REGS: %d\n"
" PGA_V_8: 0x%X\n"
" PGA_IA_8: 0x%X\n"
" PGA_IB_8: 0x%X\n"
" VGAIN_32: 0x%08jX\n"
" AIGAIN_32: 0x%08jX\n"
" BIGAIN_32: 0x%08jX\n"
" AWGAIN_32: 0x%08jX\n"
" BWGAIN_32: 0x%08jX",
this->use_acc_energy_regs_, pga_v_, pga_ia_, pga_ib_, (uintmax_t) vgain_, (uintmax_t) aigain_,
(uintmax_t) bigain_, (uintmax_t) awgain_, (uintmax_t) bwgain_);
ESP_LOGCONFIG(TAG, " USE_ACC_ENERGY_REGS: %d", this->use_acc_energy_regs_);
ESP_LOGCONFIG(TAG, " PGA_V_8: 0x%X", pga_v_);
ESP_LOGCONFIG(TAG, " PGA_IA_8: 0x%X", pga_ia_);
ESP_LOGCONFIG(TAG, " PGA_IB_8: 0x%X", pga_ib_);
ESP_LOGCONFIG(TAG, " VGAIN_32: 0x%08jX", (uintmax_t) vgain_);
ESP_LOGCONFIG(TAG, " AIGAIN_32: 0x%08jX", (uintmax_t) aigain_);
ESP_LOGCONFIG(TAG, " BIGAIN_32: 0x%08jX", (uintmax_t) bigain_);
ESP_LOGCONFIG(TAG, " AWGAIN_32: 0x%08jX", (uintmax_t) awgain_);
ESP_LOGCONFIG(TAG, " BWGAIN_32: 0x%08jX", (uintmax_t) bwgain_);
}
#define ADE_PUBLISH_(name, val, factor) \

View File

@ -1,6 +1,6 @@
#include "ade7953_i2c.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace ade7953_i2c {

View File

@ -1,6 +1,6 @@
#include "ade7953_spi.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace ade7953_spi {

View File

@ -10,13 +10,15 @@ static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00;
static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
void ADS1115Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
uint16_t value;
if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &value)) {
this->mark_failed();
return;
}
ESP_LOGCONFIG(TAG, "Configuring ADS1115...");
uint16_t config = 0;
// Clear single-shot bit
// 0b0xxxxxxxxxxxxxxx
@ -66,10 +68,10 @@ void ADS1115Component::setup() {
this->prev_config_ = config;
}
void ADS1115Component::dump_config() {
ESP_LOGCONFIG(TAG, "ADS1115:");
ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with ADS1115 failed!");
}
}
float ADS1115Component::request_measurement(ADS1115Multiplexer multiplexer, ADS1115Gain gain,

View File

@ -49,6 +49,7 @@ class ADS1115Component : public Component, public i2c::I2CDevice {
void setup() override;
void dump_config() override;
/// HARDWARE_LATE setup priority
float get_setup_priority() const override { return setup_priority::DATA; }
void set_continuous_mode(bool continuous_mode) { continuous_mode_ = continuous_mode; }
/// Helper method to request a measurement from a sensor.

View File

@ -1,5 +1,4 @@
#include "ads1118.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
@ -9,7 +8,7 @@ static const char *const TAG = "ads1118";
static const uint8_t ADS1118_DATA_RATE_860_SPS = 0b111;
void ADS1118::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up ads1118");
this->spi_setup();
this->config_ = 0;

View File

@ -34,6 +34,7 @@ class ADS1118 : public Component,
ADS1118() = default;
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
/// Helper method to request a measurement from a sensor.
float request_measurement(ADS1118Multiplexer multiplexer, ADS1118Gain gain, bool temperature_mode);

View File

@ -1,5 +1,4 @@
#include "ags10.h"
#include "esphome/core/helpers.h"
#include <cinttypes>
@ -24,7 +23,7 @@ static const uint16_t ZP_CURRENT = 0x0000;
static const uint16_t ZP_DEFAULT = 0xFFFF;
void AGS10Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up ags10...");
auto version = this->read_version_();
if (version) {
@ -66,7 +65,7 @@ void AGS10Component::dump_config() {
case NONE:
break;
case COMMUNICATION_FAILED:
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with AGS10 failed!");
break;
case CRC_CHECK_FAILED:
ESP_LOGE(TAG, "The crc check failed");

View File

@ -31,6 +31,8 @@ class AGS10Component : public PollingComponent, public i2c::I2CDevice {
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
/**
* Modifies target address of AGS10.
*

View File

@ -13,9 +13,8 @@
// results making successive requests; the current implementation makes 3 attempts with a delay of 30ms each time.
#include "aht10.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace aht10 {
@ -35,59 +34,57 @@ static const uint8_t AHT10_INIT_ATTEMPTS = 10;
static const uint8_t AHT10_STATUS_BUSY = 0x80;
static const float AHT10_DIVISOR = 1048576.0f; // 2^20, used for temperature and humidity calculations
void AHT10Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
if (this->write(AHT10_SOFTRESET_CMD, sizeof(AHT10_SOFTRESET_CMD)) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Reset failed");
ESP_LOGE(TAG, "Reset AHT10 failed!");
}
delay(AHT10_SOFTRESET_DELAY);
i2c::ErrorCode error_code = i2c::ERROR_INVALID_ARGUMENT;
switch (this->variant_) {
case AHT10Variant::AHT20:
ESP_LOGCONFIG(TAG, "Setting up AHT20");
error_code = this->write(AHT20_INITIALIZE_CMD, sizeof(AHT20_INITIALIZE_CMD));
break;
case AHT10Variant::AHT10:
ESP_LOGCONFIG(TAG, "Setting up AHT10");
error_code = this->write(AHT10_INITIALIZE_CMD, sizeof(AHT10_INITIALIZE_CMD));
break;
}
if (error_code != i2c::ERROR_OK) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with AHT10 failed!");
this->mark_failed();
return;
}
uint8_t cal_attempts = 0;
uint8_t data = AHT10_STATUS_BUSY;
int cal_attempts = 0;
while (data & AHT10_STATUS_BUSY) {
delay(AHT10_DEFAULT_DELAY);
if (this->read(&data, 1) != i2c::ERROR_OK) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with AHT10 failed!");
this->mark_failed();
return;
}
++cal_attempts;
if (cal_attempts > AHT10_INIT_ATTEMPTS) {
ESP_LOGE(TAG, "Initialization timed out");
ESP_LOGE(TAG, "AHT10 initialization timed out!");
this->mark_failed();
return;
}
}
if ((data & 0x68) != 0x08) { // Bit[6:5] = 0b00, NORMAL mode and Bit[3] = 0b1, CALIBRATED
ESP_LOGE(TAG, "Initialization failed");
ESP_LOGE(TAG, "AHT10 initialization failed!");
this->mark_failed();
return;
}
ESP_LOGV(TAG, "Initialization complete");
ESP_LOGV(TAG, "AHT10 initialization");
}
void AHT10Component::restart_read_() {
if (this->read_count_ == AHT10_ATTEMPTS) {
this->read_count_ = 0;
this->status_set_error("Reading timed out");
this->status_set_error("Measurements reading timed-out!");
return;
}
this->read_count_++;
@ -100,24 +97,24 @@ void AHT10Component::read_data_() {
ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_));
}
if (this->read(data, 6) != i2c::ERROR_OK) {
this->status_set_warning("Read failed, will retry");
this->status_set_warning("AHT10 read failed, retrying soon");
this->restart_read_();
return;
}
if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy
ESP_LOGD(TAG, "Device busy, will retry");
ESP_LOGD(TAG, "AHT10 is busy, waiting...");
this->restart_read_();
return;
}
if (data[1] == 0x0 && data[2] == 0x0 && (data[3] >> 4) == 0x0) {
// Invalid humidity (0x0)
// Unrealistic humidity (0x0)
if (this->humidity_sensor_ == nullptr) {
ESP_LOGV(TAG, "Invalid humidity (reading not required)");
ESP_LOGV(TAG, "ATH10 Unrealistic humidity (0x0), but humidity is not required");
} else {
ESP_LOGD(TAG, "Invalid humidity, retrying");
ESP_LOGD(TAG, "ATH10 Unrealistic humidity (0x0), retrying...");
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
this->status_set_warning("Communication with AHT10 failed!");
}
this->restart_read_();
return;
@ -126,17 +123,22 @@ void AHT10Component::read_data_() {
if (this->read_count_ > 1) {
ESP_LOGD(TAG, "Success at %ums", (unsigned) (millis() - this->start_time_));
}
uint32_t raw_temperature = encode_uint24(data[3] & 0xF, data[4], data[5]);
uint32_t raw_humidity = encode_uint24(data[1], data[2], data[3]) >> 4;
uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4;
if (this->temperature_sensor_ != nullptr) {
float temperature = ((200.0f * static_cast<float>(raw_temperature)) / AHT10_DIVISOR) - 50.0f;
float temperature = ((200.0f * (float) raw_temperature) / 1048576.0f) - 50.0f;
this->temperature_sensor_->publish_state(temperature);
}
if (this->humidity_sensor_ != nullptr) {
float humidity = raw_humidity == 0 ? NAN : static_cast<float>(raw_humidity) * 100.0f / AHT10_DIVISOR;
float humidity;
if (raw_humidity == 0) { // unrealistic value
humidity = NAN;
} else {
humidity = (float) raw_humidity * 100.0f / 1048576.0f;
}
if (std::isnan(humidity)) {
ESP_LOGW(TAG, "Invalid humidity reading (0%%), ");
ESP_LOGW(TAG, "Invalid humidity! Sensor reported 0%% Hum");
}
this->humidity_sensor_->publish_state(humidity);
}
@ -148,7 +150,7 @@ void AHT10Component::update() {
return;
this->start_time_ = millis();
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
this->status_set_warning("Communication with AHT10 failed!");
return;
}
this->restart_read_();
@ -160,7 +162,7 @@ void AHT10Component::dump_config() {
ESP_LOGCONFIG(TAG, "AHT10:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with AHT10 failed!");
}
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);

View File

@ -17,7 +17,7 @@ static const char *const TAG = "aic3204";
}
void AIC3204::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up AIC3204...");
// Set register page to 0
ERROR_CHECK(this->write_byte(AIC3204_PAGE_CTRL, 0x00), "Set page 0 failed");
@ -113,7 +113,7 @@ void AIC3204::dump_config() {
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with AIC3204 failed");
}
}

View File

@ -66,6 +66,7 @@ class AIC3204 : public audio_dac::AudioDac, public Component, public i2c::I2CDev
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
bool set_mute_off() override;
bool set_mute_on() override;

View File

@ -34,7 +34,7 @@ AirthingsWaveBase = airthings_wave_base_ns.class_(
BASE_SCHEMA = (
cv.Schema(
sensor.SENSOR_SCHEMA.extend(
{
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,

View File

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

View File

@ -73,29 +73,11 @@ void AirthingsWavePlus::dump_config() {
LOG_SENSOR(" ", "Illuminance", this->illuminance_sensor_);
}
void AirthingsWavePlus::setup() {
const char *service_uuid;
const char *characteristic_uuid;
const char *access_control_point_characteristic_uuid;
// Change UUIDs for Wave Radon Gen2
switch (this->wave_device_type_) {
case WaveDeviceType::WAVE_GEN2:
service_uuid = SERVICE_UUID_WAVE_RADON_GEN2;
characteristic_uuid = CHARACTERISTIC_UUID_WAVE_RADON_GEN2;
access_control_point_characteristic_uuid = ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID_WAVE_RADON_GEN2;
break;
default:
// Wave Plus
service_uuid = SERVICE_UUID;
characteristic_uuid = CHARACTERISTIC_UUID;
access_control_point_characteristic_uuid = ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID;
}
this->service_uuid_ = espbt::ESPBTUUID::from_raw(service_uuid);
this->sensors_data_characteristic_uuid_ = espbt::ESPBTUUID::from_raw(characteristic_uuid);
AirthingsWavePlus::AirthingsWavePlus() {
this->service_uuid_ = espbt::ESPBTUUID::from_raw(SERVICE_UUID);
this->sensors_data_characteristic_uuid_ = espbt::ESPBTUUID::from_raw(CHARACTERISTIC_UUID);
this->access_control_point_characteristic_uuid_ =
espbt::ESPBTUUID::from_raw(access_control_point_characteristic_uuid);
espbt::ESPBTUUID::from_raw(ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID);
}
} // namespace airthings_wave_plus

View File

@ -9,20 +9,13 @@ namespace airthings_wave_plus {
namespace espbt = esphome::esp32_ble_tracker;
enum WaveDeviceType : uint8_t { WAVE_PLUS = 0, WAVE_GEN2 = 1 };
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 ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID = "b42e2d06-ade7-11e4-89d3-123b93f75cba";
static const char *const SERVICE_UUID_WAVE_RADON_GEN2 = "b42e4a8e-ade7-11e4-89d3-123b93f75cba";
static const char *const CHARACTERISTIC_UUID_WAVE_RADON_GEN2 = "b42e4dcc-ade7-11e4-89d3-123b93f75cba";
static const char *const ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID_WAVE_RADON_GEN2 =
"b42e50d8-ade7-11e4-89d3-123b93f75cba";
class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase {
public:
void setup() override;
AirthingsWavePlus();
void dump_config() override;
@ -30,14 +23,12 @@ class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase {
void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; }
void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; }
void set_illuminance(sensor::Sensor *illuminance) { illuminance_sensor_ = illuminance; }
void set_device_type(WaveDeviceType wave_device_type) { wave_device_type_ = wave_device_type; }
protected:
bool is_valid_radon_value_(uint16_t radon);
bool is_valid_co2_value_(uint16_t co2);
void read_sensors(uint8_t *raw_value, uint16_t value_len) override;
WaveDeviceType wave_device_type_{WaveDeviceType::WAVE_PLUS};
sensor::Sensor *radon_sensor_{nullptr};
sensor::Sensor *radon_long_term_sensor_{nullptr};

View File

@ -7,7 +7,6 @@ from esphome.const import (
CONF_ILLUMINANCE,
CONF_RADON,
CONF_RADON_LONG_TERM,
CONF_TVOC,
DEVICE_CLASS_CARBON_DIOXIDE,
DEVICE_CLASS_ILLUMINANCE,
ICON_RADIOACTIVE,
@ -16,7 +15,6 @@ from esphome.const import (
UNIT_LUX,
UNIT_PARTS_PER_MILLION,
)
from esphome.types import ConfigType
DEPENDENCIES = airthings_wave_base.DEPENDENCIES
@ -27,59 +25,35 @@ AirthingsWavePlus = airthings_wave_plus_ns.class_(
"AirthingsWavePlus", airthings_wave_base.AirthingsWaveBase
)
CONF_DEVICE_TYPE = "device_type"
WaveDeviceType = airthings_wave_plus_ns.enum("WaveDeviceType")
DEVICE_TYPES = {
"WAVE_PLUS": WaveDeviceType.WAVE_PLUS,
"WAVE_GEN2": WaveDeviceType.WAVE_GEN2,
}
def validate_wave_gen2_config(config: ConfigType) -> ConfigType:
"""Validate that Wave Gen2 devices don't have CO2 or TVOC sensors."""
if config[CONF_DEVICE_TYPE] == "WAVE_GEN2":
if CONF_CO2 in config:
raise cv.Invalid("Wave Gen2 devices do not support CO2 sensor")
# Check for TVOC in the base schema config
if CONF_TVOC in config:
raise cv.Invalid("Wave Gen2 devices do not support TVOC sensor")
return config
CONFIG_SCHEMA = cv.All(
airthings_wave_base.BASE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(AirthingsWavePlus),
cv.Optional(CONF_RADON): sensor.sensor_schema(
unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
icon=ICON_RADIOACTIVE,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_RADON_LONG_TERM): sensor.sensor_schema(
unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
icon=ICON_RADIOACTIVE,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CO2): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
accuracy_decimals=0,
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
unit_of_measurement=UNIT_LUX,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_DEVICE_TYPE, default="WAVE_PLUS"): cv.enum(
DEVICE_TYPES, upper=True
),
}
),
validate_wave_gen2_config,
CONFIG_SCHEMA = airthings_wave_base.BASE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(AirthingsWavePlus),
cv.Optional(CONF_RADON): sensor.sensor_schema(
unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
icon=ICON_RADIOACTIVE,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_RADON_LONG_TERM): sensor.sensor_schema(
unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
icon=ICON_RADIOACTIVE,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CO2): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
accuracy_decimals=0,
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
unit_of_measurement=UNIT_LUX,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
@ -99,4 +73,3 @@ async def to_code(config):
if config_illuminance := config.get(CONF_ILLUMINANCE):
sens = await sensor.new_sensor(config_illuminance)
cg.add(var.set_illuminance(sens))
cg.add(var.set_device_type(config[CONF_DEVICE_TYPE]))

View File

@ -5,8 +5,6 @@ from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import (
CONF_CODE,
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_ID,
CONF_MQTT_ID,
CONF_ON_STATE,
@ -14,8 +12,7 @@ from esphome.const import (
CONF_WEB_SERVER,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@grahambrown11", "@hwstar"]
IS_PLATFORM_COMPONENT = True
@ -81,11 +78,12 @@ AlarmControlPanelCondition = alarm_control_panel_ns.class_(
"AlarmControlPanelCondition", automation.Condition
)
_ALARM_CONTROL_PANEL_SCHEMA = (
ALARM_CONTROL_PANEL_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.GenerateID(): cv.declare_id(AlarmControlPanel),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
mqtt.MQTTAlarmControlPanelComponent
),
@ -148,36 +146,6 @@ _ALARM_CONTROL_PANEL_SCHEMA = (
)
)
_ALARM_CONTROL_PANEL_SCHEMA.add_extra(entity_duplicate_validator("alarm_control_panel"))
def alarm_control_panel_schema(
class_: MockObjClass,
*,
entity_category: str = cv.UNDEFINED,
icon: str = cv.UNDEFINED,
) -> cv.Schema:
schema = {
cv.GenerateID(): cv.declare_id(class_),
}
for key, default, validator in [
(CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
(CONF_ICON, icon, cv.icon),
]:
if default is not cv.UNDEFINED:
schema[cv.Optional(key, default=default)] = validator
return _ALARM_CONTROL_PANEL_SCHEMA.extend(schema)
# Remove before 2025.11.0
ALARM_CONTROL_PANEL_SCHEMA = alarm_control_panel_schema(AlarmControlPanel)
ALARM_CONTROL_PANEL_SCHEMA.add_extra(
cv.deprecated_schema_constant("alarm_control_panel")
)
ALARM_CONTROL_PANEL_ACTION_SCHEMA = maybe_simple_id(
{
cv.GenerateID(): cv.use_id(AlarmControlPanel),
@ -193,7 +161,7 @@ ALARM_CONTROL_PANEL_CONDITION_SCHEMA = maybe_simple_id(
async def setup_alarm_control_panel_core_(var, config):
await setup_entity(var, config, "alarm_control_panel")
await setup_entity(var, config)
for conf in config.get(CONF_ON_STATE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
@ -238,16 +206,9 @@ async def register_alarm_control_panel(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_alarm_control_panel(var))
CORE.register_platform_component("alarm_control_panel", var)
await setup_alarm_control_panel_core_(var, config)
async def new_alarm_control_panel(config, *args):
var = cg.new_Pvariable(config[CONF_ID], *args)
await register_alarm_control_panel(var, config)
return var
@automation.register_action(
"alarm_control_panel.arm_away", ArmAwayAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
)

View File

@ -41,6 +41,7 @@ class Alpha3 : public esphome::ble_client::BLEClientNode, public PollingComponen
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; }

View File

@ -90,7 +90,7 @@ bool AM2315C::convert_(uint8_t *data, float &humidity, float &temperature) {
}
void AM2315C::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up AM2315C...");
// get status
uint8_t status = 0;
@ -188,7 +188,7 @@ void AM2315C::dump_config() {
ESP_LOGCONFIG(TAG, "AM2315C:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with AM2315C failed!");
}
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);

View File

@ -34,7 +34,7 @@ void AM2320Component::update() {
this->status_clear_warning();
}
void AM2320Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up AM2320...");
uint8_t data[8];
data[0] = 0;
data[1] = 4;
@ -47,7 +47,7 @@ void AM2320Component::dump_config() {
ESP_LOGD(TAG, "AM2320:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with AM2320 failed!");
}
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);

View File

@ -1,7 +1,7 @@
#pragma once
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace am43 {

View File

@ -1,7 +1,7 @@
import esphome.codegen as cg
from esphome.components import ble_client, cover
import esphome.config_validation as cv
from esphome.const import CONF_PIN
from esphome.const import CONF_ID, CONF_PIN
CODEOWNERS = ["@buxtronix"]
DEPENDENCIES = ["ble_client"]
@ -15,9 +15,9 @@ Am43Component = am43_ns.class_(
)
CONFIG_SCHEMA = (
cover.cover_schema(Am43Component)
.extend(
cover.COVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(Am43Component),
cv.Optional(CONF_PIN, default=8888): cv.int_range(min=0, max=0xFFFF),
cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean,
}
@ -28,8 +28,9 @@ CONFIG_SCHEMA = (
async def to_code(config):
var = await cover.new_cover(config)
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_pin(config[CONF_PIN]))
cg.add(var.set_invert_position(config[CONF_INVERT_POSITION]))
await cg.register_component(var, config)
await cover.register_cover(var, config)
await ble_client.register_ble_node(var, config)

View File

@ -12,10 +12,8 @@ using namespace esphome::cover;
void Am43Component::dump_config() {
LOG_COVER("", "AM43 Cover", this);
ESP_LOGCONFIG(TAG,
" Device Pin: %d\n"
" Invert Position: %d",
this->pin_, (int) this->invert_position_);
ESP_LOGCONFIG(TAG, " Device Pin: %d", this->pin_);
ESP_LOGCONFIG(TAG, " Invert Position: %d", (int) this->invert_position_);
}
void Am43Component::setup() {

View File

@ -22,6 +22,7 @@ class Am43Component : public cover::Cover, public esphome::ble_client::BLEClient
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
cover::CoverTraits get_traits() override;
void set_pin(uint16_t pin) { this->pin_ = pin; }
void set_invert_position(bool invert_position) { this->invert_position_ = invert_position; }

View File

@ -22,6 +22,7 @@ class Am43 : public esphome::ble_client::BLEClientNode, public PollingComponent
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_battery(sensor::Sensor *battery) { battery_ = battery; }
void set_illuminance(sensor::Sensor *illuminance) { illuminance_ = illuminance; }

View File

@ -14,8 +14,7 @@ void AnalogThresholdBinarySensor::setup() {
if (std::isnan(sensor_value)) {
this->publish_initial_state(false);
} else {
this->publish_initial_state(sensor_value >=
(this->lower_threshold_.value() + this->upper_threshold_.value()) / 2.0f);
this->publish_initial_state(sensor_value >= (this->lower_threshold_ + this->upper_threshold_) / 2.0f);
}
}
@ -25,8 +24,7 @@ void AnalogThresholdBinarySensor::set_sensor(sensor::Sensor *analog_sensor) {
this->sensor_->add_on_state_callback([this](float sensor_value) {
// if there is an invalid sensor reading, ignore the change and keep the current state
if (!std::isnan(sensor_value)) {
this->publish_state(sensor_value >=
(this->state ? this->lower_threshold_.value() : this->upper_threshold_.value()));
this->publish_state(sensor_value >= (this->state ? this->lower_threshold_ : this->upper_threshold_));
}
});
}
@ -34,10 +32,8 @@ void AnalogThresholdBinarySensor::set_sensor(sensor::Sensor *analog_sensor) {
void AnalogThresholdBinarySensor::dump_config() {
LOG_BINARY_SENSOR("", "Analog Threshold Binary Sensor", this);
LOG_SENSOR(" ", "Sensor", this->sensor_);
ESP_LOGCONFIG(TAG,
" Upper threshold: %.11f\n"
" Lower threshold: %.11f",
this->upper_threshold_.value(), this->lower_threshold_.value());
ESP_LOGCONFIG(TAG, " Upper threshold: %.11f", this->upper_threshold_);
ESP_LOGCONFIG(TAG, " Lower threshold: %.11f", this->lower_threshold_);
}
} // namespace analog_threshold

View File

@ -12,14 +12,17 @@ class AnalogThresholdBinarySensor : public Component, public binary_sensor::Bina
void dump_config() override;
void setup() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_sensor(sensor::Sensor *analog_sensor);
template<typename T> void set_upper_threshold(T upper_threshold) { this->upper_threshold_ = upper_threshold; }
template<typename T> void set_lower_threshold(T lower_threshold) { this->lower_threshold_ = lower_threshold; }
void set_upper_threshold(float threshold) { this->upper_threshold_ = threshold; }
void set_lower_threshold(float threshold) { this->lower_threshold_ = threshold; }
protected:
sensor::Sensor *sensor_{nullptr};
TemplatableValue<float> upper_threshold_{};
TemplatableValue<float> lower_threshold_{};
float upper_threshold_;
float lower_threshold_;
};
} // namespace analog_threshold

View File

@ -18,11 +18,11 @@ CONFIG_SCHEMA = (
{
cv.Required(CONF_SENSOR_ID): cv.use_id(sensor.Sensor),
cv.Required(CONF_THRESHOLD): cv.Any(
cv.templatable(cv.float_),
cv.float_,
cv.Schema(
{
cv.Required(CONF_UPPER): cv.templatable(cv.float_),
cv.Required(CONF_LOWER): cv.templatable(cv.float_),
cv.Required(CONF_UPPER): cv.float_,
cv.Required(CONF_LOWER): cv.float_,
}
),
),
@ -39,11 +39,9 @@ async def to_code(config):
sens = await cg.get_variable(config[CONF_SENSOR_ID])
cg.add(var.set_sensor(sens))
if isinstance(config[CONF_THRESHOLD], dict):
lower = await cg.templatable(config[CONF_THRESHOLD][CONF_LOWER], [], float)
upper = await cg.templatable(config[CONF_THRESHOLD][CONF_UPPER], [], float)
if isinstance(config[CONF_THRESHOLD], float):
cg.add(var.set_upper_threshold(config[CONF_THRESHOLD]))
cg.add(var.set_lower_threshold(config[CONF_THRESHOLD]))
else:
lower = await cg.templatable(config[CONF_THRESHOLD], [], float)
upper = lower
cg.add(var.set_upper_threshold(upper))
cg.add(var.set_lower_threshold(lower))
cg.add(var.set_upper_threshold(config[CONF_THRESHOLD][CONF_UPPER]))
cg.add(var.set_lower_threshold(config[CONF_THRESHOLD][CONF_LOWER]))

View File

@ -17,11 +17,7 @@ void Anova::setup() {
this->current_request_ = 0;
}
void Anova::loop() {
// Parent BLEClientNode has a loop() method, but this component uses
// polling via update() and BLE callbacks so loop isn't needed
this->disable_loop();
}
void Anova::loop() {}
void Anova::control(const ClimateCall &call) {
if (call.get_mode().has_value()) {

View File

@ -26,6 +26,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
climate::ClimateTraits traits() override {
auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(true);

View File

@ -1,7 +1,7 @@
import esphome.codegen as cg
from esphome.components import ble_client, climate
import esphome.config_validation as cv
from esphome.const import CONF_UNIT_OF_MEASUREMENT
from esphome.const import CONF_ID, CONF_UNIT_OF_MEASUREMENT
UNITS = {
"f": "f",
@ -17,9 +17,9 @@ Anova = anova_ns.class_(
)
CONFIG_SCHEMA = (
climate.climate_schema(Anova)
.extend(
climate.CLIMATE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(Anova),
cv.Required(CONF_UNIT_OF_MEASUREMENT): cv.enum(UNITS),
}
)
@ -29,7 +29,8 @@ CONFIG_SCHEMA = (
async def to_code(config):
var = await climate.new_climate(config)
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await climate.register_climate(var, config)
await ble_client.register_ble_node(var, config)
cg.add(var.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT]))

View File

@ -54,7 +54,7 @@ enum { // APDS9306 registers
}
void APDS9306::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up APDS9306...");
uint8_t id;
if (!this->read_byte(APDS9306_PART_ID, &id)) { // Part ID register
@ -97,7 +97,7 @@ void APDS9306::dump_config() {
if (this->is_failed()) {
switch (this->error_code_) {
case COMMUNICATION_FAILED:
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with APDS9306 failed!");
break;
case WRONG_ID:
ESP_LOGE(TAG, "APDS9306 has invalid id!");
@ -108,12 +108,9 @@ void APDS9306::dump_config() {
}
}
ESP_LOGCONFIG(TAG,
" Gain: %u\n"
" Measurement rate: %u\n"
" Measurement Resolution/Bit width: %d",
AMBIENT_LIGHT_GAIN_VALUES[this->gain_], MEASUREMENT_RATE_VALUES[this->measurement_rate_],
MEASUREMENT_BIT_WIDTH_VALUES[this->bit_width_]);
ESP_LOGCONFIG(TAG, " Gain: %u", AMBIENT_LIGHT_GAIN_VALUES[this->gain_]);
ESP_LOGCONFIG(TAG, " Measurement rate: %u", MEASUREMENT_RATE_VALUES[this->measurement_rate_]);
ESP_LOGCONFIG(TAG, " Measurement Resolution/Bit width: %d", MEASUREMENT_BIT_WIDTH_VALUES[this->bit_width_]);
LOG_UPDATE_INTERVAL(this);
}

View File

@ -15,7 +15,7 @@ static const char *const TAG = "apds9960";
#define APDS9960_WRITE_BYTE(reg, value) APDS9960_ERROR_CHECK(this->write_byte(reg, value));
void APDS9960::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up APDS9960...");
uint8_t id;
if (!this->read_byte(0x92, &id)) { // ID register
this->error_code_ = COMMUNICATION_FAILED;
@ -23,7 +23,7 @@ void APDS9960::setup() {
return;
}
if (id != 0xAB && id != 0x9C && id != 0xA8 && id != 0x9E) { // APDS9960 all should have one of these IDs
if (id != 0xAB && id != 0x9C && id != 0xA8) { // APDS9960 all should have one of these IDs
this->error_code_ = WRONG_ID;
this->mark_failed();
return;
@ -141,7 +141,7 @@ void APDS9960::dump_config() {
if (this->is_failed()) {
switch (this->error_code_) {
case COMMUNICATION_FAILED:
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with APDS9960 failed!");
break;
case WRONG_ID:
ESP_LOGE(TAG, "APDS9960 has invalid id!");

View File

@ -3,7 +3,6 @@ import base64
from esphome import automation
from esphome.automation import Condition
import esphome.codegen as cg
from esphome.config_helpers import get_logger_level
import esphome.config_validation as cv
from esphome.const import (
CONF_ACTION,
@ -24,9 +23,8 @@ from esphome.const import (
CONF_TRIGGER_ID,
CONF_VARIABLES,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.core import coroutine_with_priority
DOMAIN = "api"
DEPENDENCIES = ["network"]
AUTO_LOAD = ["socket"]
CODEOWNERS = ["@OttoWinter"]
@ -51,8 +49,6 @@ SERVICE_ARG_NATIVE_TYPES = {
"string[]": cg.std_vector.template(cg.std_string),
}
CONF_ENCRYPTION = "encryption"
CONF_BATCH_DELAY = "batch_delay"
CONF_CUSTOM_SERVICES = "custom_services"
def validate_encryption_key(value):
@ -86,19 +82,6 @@ ACTIONS_SCHEMA = automation.validate_automation(
),
)
ENCRYPTION_SCHEMA = cv.Schema(
{
cv.Optional(CONF_KEY): validate_encryption_key,
}
)
def _encryption_schema(config):
if config is None:
config = {}
return ENCRYPTION_SCHEMA(config)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
@ -112,12 +95,11 @@ CONFIG_SCHEMA = cv.All(
CONF_SERVICES, group_of_exclusion=CONF_ACTIONS
): ACTIONS_SCHEMA,
cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA,
cv.Optional(CONF_ENCRYPTION): _encryption_schema,
cv.Optional(CONF_BATCH_DELAY, default="100ms"): cv.All(
cv.positive_time_period_milliseconds,
cv.Range(max=cv.TimePeriod(milliseconds=65535)),
cv.Optional(CONF_ENCRYPTION): cv.Schema(
{
cv.Required(CONF_KEY): validate_encryption_key,
}
),
cv.Optional(CONF_CUSTOM_SERVICES, default=False): cv.boolean,
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
single=True
),
@ -136,35 +118,26 @@ async def to_code(config):
await cg.register_component(var, config)
cg.add(var.set_port(config[CONF_PORT]))
if config[CONF_PASSWORD]:
cg.add_define("USE_API_PASSWORD")
cg.add(var.set_password(config[CONF_PASSWORD]))
cg.add(var.set_password(config[CONF_PASSWORD]))
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
# Set USE_API_SERVICES if any services are enabled
if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:
cg.add_define("USE_API_SERVICES")
if actions := config.get(CONF_ACTIONS, []):
for conf in actions:
template_args = []
func_args = []
service_arg_names = []
for name, var_ in conf[CONF_VARIABLES].items():
native = SERVICE_ARG_NATIVE_TYPES[var_]
template_args.append(native)
func_args.append((native, name))
service_arg_names.append(name)
templ = cg.TemplateArguments(*template_args)
trigger = cg.new_Pvariable(
conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names
)
cg.add(var.register_user_service(trigger))
await automation.build_automation(trigger, func_args, conf)
for conf in config.get(CONF_ACTIONS, []):
template_args = []
func_args = []
service_arg_names = []
for name, var_ in conf[CONF_VARIABLES].items():
native = SERVICE_ARG_NATIVE_TYPES[var_]
template_args.append(native)
func_args.append((native, name))
service_arg_names.append(name)
templ = cg.TemplateArguments(*template_args)
trigger = cg.new_Pvariable(
conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names
)
cg.add(var.register_user_service(trigger))
await automation.build_automation(trigger, func_args, conf)
if CONF_ON_CLIENT_CONNECTED in config:
cg.add_define("USE_API_CLIENT_CONNECTED_TRIGGER")
await automation.build_automation(
var.get_client_connected_trigger(),
[(cg.std_string, "client_info"), (cg.std_string, "client_address")],
@ -172,26 +145,17 @@ async def to_code(config):
)
if CONF_ON_CLIENT_DISCONNECTED in config:
cg.add_define("USE_API_CLIENT_DISCONNECTED_TRIGGER")
await automation.build_automation(
var.get_client_disconnected_trigger(),
[(cg.std_string, "client_info"), (cg.std_string, "client_address")],
config[CONF_ON_CLIENT_DISCONNECTED],
)
if (encryption_config := config.get(CONF_ENCRYPTION, None)) is not None:
if key := encryption_config.get(CONF_KEY):
decoded = base64.b64decode(key)
cg.add(var.set_noise_psk(list(decoded)))
else:
# No key provided, but encryption desired
# This will allow a plaintext client to provide a noise key,
# send it to the device, and then switch to noise.
# The key will be saved in flash and used for future connections
# and plaintext disabled. Only a factory reset can remove it.
cg.add_define("USE_API_PLAINTEXT")
if encryption_config := config.get(CONF_ENCRYPTION):
decoded = base64.b64decode(encryption_config[CONF_KEY])
cg.add(var.set_noise_psk(list(decoded)))
cg.add_define("USE_API_NOISE")
cg.add_library("esphome/noise-c", "0.1.10")
cg.add_library("esphome/noise-c", "0.1.6")
else:
cg.add_define("USE_API_PLAINTEXT")
@ -320,25 +284,3 @@ async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, arg
@automation.register_condition("api.connected", APIConnectedCondition, {})
async def api_connected_to_code(config, condition_id, template_arg, args):
return cg.new_Pvariable(condition_id, template_arg)
def FILTER_SOURCE_FILES() -> list[str]:
"""Filter out api_pb2_dump.cpp when proto message dumping is not enabled
and user_services.cpp when no services are defined."""
files_to_filter = []
# api_pb2_dump.cpp is only needed when HAS_PROTO_MESSAGE_DUMP is defined
# This is a particularly large file that still needs to be opened and read
# all the way to the end even when ifdef'd out
#
# HAS_PROTO_MESSAGE_DUMP is defined when ESPHOME_LOG_HAS_VERY_VERBOSE is set,
# which happens when the logger level is VERY_VERBOSE
if get_logger_level() != "VERY_VERBOSE":
files_to_filter.append("api_pb2_dump.cpp")
# user_services.cpp is only needed when services are defined
config = CORE.config.get(DOMAIN, {})
if config and not config.get(CONF_ACTIONS) and not config[CONF_CUSTOM_SERVICES]:
files_to_filter.append("user_services.cpp")
return files_to_filter

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -8,23 +8,54 @@
#include "api_server.h"
#include "esphome/core/application.h"
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
#include <vector>
#include <functional>
namespace esphome {
namespace api {
// Keepalive timeout in milliseconds
static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000;
// Maximum number of entities to process in a single batch during initial state/info sending
static constexpr size_t MAX_INITIAL_PER_BATCH = 20;
using send_message_t = bool(APIConnection *, void *);
/*
This class holds a pointer to the source component that wants to publish a message, and a pointer to a function that
will lazily publish that message. The two pointers allow dedup in the deferred queue if multiple publishes for the
same component are backed up, and take up only 8 bytes of memory. The entry in the deferred queue (a std::vector) is
the DeferredMessage instance itself (not a pointer to one elsewhere in heap) so still only 8 bytes per entry. Even
100 backed up messages (you'd have to have at least 100 sensors publishing because of dedup) would take up only 0.8
kB.
*/
class DeferredMessageQueue {
struct DeferredMessage {
friend class DeferredMessageQueue;
protected:
void *source_;
send_message_t *send_message_;
public:
DeferredMessage(void *source, send_message_t *send_message) : source_(source), send_message_(send_message) {}
bool operator==(const DeferredMessage &test) const {
return (source_ == test.source_ && send_message_ == test.send_message_);
}
} __attribute__((packed));
protected:
// vector is used very specifically for its zero memory overhead even though items are popped from the front (memory
// footprint is more important than speed here)
std::vector<DeferredMessage> deferred_queue_;
APIConnection *api_connection_;
// helper for allowing only unique entries in the queue
void dmq_push_back_with_dedup_(void *source, send_message_t *send_message);
public:
DeferredMessageQueue(APIConnection *api_connection) : api_connection_(api_connection) {}
void process_queue();
void defer(void *source, send_message_t *send_message);
};
class APIConnection : public APIServerConnection {
public:
friend class APIServer;
friend class ListEntitiesIterator;
APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent);
virtual ~APIConnection();
@ -32,86 +63,149 @@ class APIConnection : public APIServerConnection {
void loop();
bool send_list_info_done() {
return this->schedule_message_(nullptr, &APIConnection::try_send_list_info_done,
ListEntitiesDoneResponse::MESSAGE_TYPE, ListEntitiesDoneResponse::ESTIMATED_SIZE);
ListEntitiesDoneResponse resp;
return this->send_list_entities_done_response(resp);
}
#ifdef USE_BINARY_SENSOR
bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor);
bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state);
void send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor);
static bool try_send_binary_sensor_state(APIConnection *api, void *v_binary_sensor);
static bool try_send_binary_sensor_state(APIConnection *api, binary_sensor::BinarySensor *binary_sensor, bool state);
static bool try_send_binary_sensor_info(APIConnection *api, void *v_binary_sensor);
#endif
#ifdef USE_COVER
bool send_cover_state(cover::Cover *cover);
void send_cover_info(cover::Cover *cover);
static bool try_send_cover_state(APIConnection *api, void *v_cover);
static bool try_send_cover_info(APIConnection *api, void *v_cover);
void cover_command(const CoverCommandRequest &msg) override;
#endif
#ifdef USE_FAN
bool send_fan_state(fan::Fan *fan);
void send_fan_info(fan::Fan *fan);
static bool try_send_fan_state(APIConnection *api, void *v_fan);
static bool try_send_fan_info(APIConnection *api, void *v_fan);
void fan_command(const FanCommandRequest &msg) override;
#endif
#ifdef USE_LIGHT
bool send_light_state(light::LightState *light);
void send_light_info(light::LightState *light);
static bool try_send_light_state(APIConnection *api, void *v_light);
static bool try_send_light_info(APIConnection *api, void *v_light);
void light_command(const LightCommandRequest &msg) override;
#endif
#ifdef USE_SENSOR
bool send_sensor_state(sensor::Sensor *sensor);
bool send_sensor_state(sensor::Sensor *sensor, float state);
void send_sensor_info(sensor::Sensor *sensor);
static bool try_send_sensor_state(APIConnection *api, void *v_sensor);
static bool try_send_sensor_state(APIConnection *api, sensor::Sensor *sensor, float state);
static bool try_send_sensor_info(APIConnection *api, void *v_sensor);
#endif
#ifdef USE_SWITCH
bool send_switch_state(switch_::Switch *a_switch);
bool send_switch_state(switch_::Switch *a_switch, bool state);
void send_switch_info(switch_::Switch *a_switch);
static bool try_send_switch_state(APIConnection *api, void *v_a_switch);
static bool try_send_switch_state(APIConnection *api, switch_::Switch *a_switch, bool state);
static bool try_send_switch_info(APIConnection *api, void *v_a_switch);
void switch_command(const SwitchCommandRequest &msg) override;
#endif
#ifdef USE_TEXT_SENSOR
bool send_text_sensor_state(text_sensor::TextSensor *text_sensor);
bool send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state);
void send_text_sensor_info(text_sensor::TextSensor *text_sensor);
static bool try_send_text_sensor_state(APIConnection *api, void *v_text_sensor);
static bool try_send_text_sensor_state(APIConnection *api, text_sensor::TextSensor *text_sensor, std::string state);
static bool try_send_text_sensor_info(APIConnection *api, void *v_text_sensor);
#endif
#ifdef USE_CAMERA
void set_camera_state(std::shared_ptr<camera::CameraImage> image);
#ifdef USE_ESP32_CAMERA
void set_camera_state(std::shared_ptr<esp32_camera::CameraImage> image);
void send_camera_info(esp32_camera::ESP32Camera *camera);
static bool try_send_camera_info(APIConnection *api, void *v_camera);
void camera_image(const CameraImageRequest &msg) override;
#endif
#ifdef USE_CLIMATE
bool send_climate_state(climate::Climate *climate);
void send_climate_info(climate::Climate *climate);
static bool try_send_climate_state(APIConnection *api, void *v_climate);
static bool try_send_climate_info(APIConnection *api, void *v_climate);
void climate_command(const ClimateCommandRequest &msg) override;
#endif
#ifdef USE_NUMBER
bool send_number_state(number::Number *number);
bool send_number_state(number::Number *number, float state);
void send_number_info(number::Number *number);
static bool try_send_number_state(APIConnection *api, void *v_number);
static bool try_send_number_state(APIConnection *api, number::Number *number, float state);
static bool try_send_number_info(APIConnection *api, void *v_number);
void number_command(const NumberCommandRequest &msg) override;
#endif
#ifdef USE_DATETIME_DATE
bool send_date_state(datetime::DateEntity *date);
void send_date_info(datetime::DateEntity *date);
static bool try_send_date_state(APIConnection *api, void *v_date);
static bool try_send_date_info(APIConnection *api, void *v_date);
void date_command(const DateCommandRequest &msg) override;
#endif
#ifdef USE_DATETIME_TIME
bool send_time_state(datetime::TimeEntity *time);
void send_time_info(datetime::TimeEntity *time);
static bool try_send_time_state(APIConnection *api, void *v_time);
static bool try_send_time_info(APIConnection *api, void *v_time);
void time_command(const TimeCommandRequest &msg) override;
#endif
#ifdef USE_DATETIME_DATETIME
bool send_datetime_state(datetime::DateTimeEntity *datetime);
void send_datetime_info(datetime::DateTimeEntity *datetime);
static bool try_send_datetime_state(APIConnection *api, void *v_datetime);
static bool try_send_datetime_info(APIConnection *api, void *v_datetime);
void datetime_command(const DateTimeCommandRequest &msg) override;
#endif
#ifdef USE_TEXT
bool send_text_state(text::Text *text);
bool send_text_state(text::Text *text, std::string state);
void send_text_info(text::Text *text);
static bool try_send_text_state(APIConnection *api, void *v_text);
static bool try_send_text_state(APIConnection *api, text::Text *text, std::string state);
static bool try_send_text_info(APIConnection *api, void *v_text);
void text_command(const TextCommandRequest &msg) override;
#endif
#ifdef USE_SELECT
bool send_select_state(select::Select *select);
bool send_select_state(select::Select *select, std::string state);
void send_select_info(select::Select *select);
static bool try_send_select_state(APIConnection *api, void *v_select);
static bool try_send_select_state(APIConnection *api, select::Select *select, std::string state);
static bool try_send_select_info(APIConnection *api, void *v_select);
void select_command(const SelectCommandRequest &msg) override;
#endif
#ifdef USE_BUTTON
void send_button_info(button::Button *button);
static bool try_send_button_info(APIConnection *api, void *v_button);
void button_command(const ButtonCommandRequest &msg) override;
#endif
#ifdef USE_LOCK
bool send_lock_state(lock::Lock *a_lock);
bool send_lock_state(lock::Lock *a_lock, lock::LockState state);
void send_lock_info(lock::Lock *a_lock);
static bool try_send_lock_state(APIConnection *api, void *v_a_lock);
static bool try_send_lock_state(APIConnection *api, lock::Lock *a_lock, lock::LockState state);
static bool try_send_lock_info(APIConnection *api, void *v_a_lock);
void lock_command(const LockCommandRequest &msg) override;
#endif
#ifdef USE_VALVE
bool send_valve_state(valve::Valve *valve);
void send_valve_info(valve::Valve *valve);
static bool try_send_valve_state(APIConnection *api, void *v_valve);
static bool try_send_valve_info(APIConnection *api, void *v_valve);
void valve_command(const ValveCommandRequest &msg) override;
#endif
#ifdef USE_MEDIA_PLAYER
bool send_media_player_state(media_player::MediaPlayer *media_player);
void send_media_player_info(media_player::MediaPlayer *media_player);
static bool try_send_media_player_state(APIConnection *api, void *v_media_player);
static bool try_send_media_player_info(APIConnection *api, void *v_media_player);
void media_player_command(const MediaPlayerCommandRequest &msg) override;
#endif
bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len);
bool try_send_log_message(int level, const char *tag, const char *line);
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
if (!this->flags_.service_call_subscription)
if (!this->service_call_subscription_)
return;
this->send_message(call);
this->send_homeassistant_service_response(call);
}
#ifdef USE_BLUETOOTH_PROXY
void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
@ -127,13 +221,12 @@ class APIConnection : public APIServerConnection {
void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) override;
BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free(
const SubscribeBluetoothConnectionsFreeRequest &msg) override;
void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) override;
#endif
#ifdef USE_HOMEASSISTANT_TIME
void send_time_request() {
GetTimeRequest req;
this->send_message(req);
this->send_get_time_request(req);
}
#endif
@ -151,22 +244,33 @@ class APIConnection : public APIServerConnection {
#ifdef USE_ALARM_CONTROL_PANEL
bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
void send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
static bool try_send_alarm_control_panel_state(APIConnection *api, void *v_a_alarm_control_panel);
static bool try_send_alarm_control_panel_info(APIConnection *api, void *v_a_alarm_control_panel);
void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
#endif
#ifdef USE_EVENT
void send_event(event::Event *event, const std::string &event_type);
void send_event(event::Event *event, std::string event_type);
void send_event_info(event::Event *event);
static bool try_send_event(APIConnection *api, void *v_event);
static bool try_send_event(APIConnection *api, event::Event *event, std::string event_type);
static bool try_send_event_info(APIConnection *api, void *v_event);
#endif
#ifdef USE_UPDATE
bool send_update_state(update::UpdateEntity *update);
void send_update_info(update::UpdateEntity *update);
static bool try_send_update_state(APIConnection *api, void *v_update);
static bool try_send_update_info(APIConnection *api, void *v_update);
void update_command(const UpdateCommandRequest &msg) override;
#endif
void on_disconnect_response(const DisconnectResponse &value) override;
void on_ping_response(const PingResponse &value) override {
// we initiated ping
this->flags_.sent_ping = false;
this->ping_retries_ = 0;
this->sent_ping_ = false;
}
void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override;
#ifdef USE_HOMEASSISTANT_TIME
@ -179,524 +283,80 @@ class APIConnection : public APIServerConnection {
DeviceInfoResponse device_info(const DeviceInfoRequest &msg) override;
void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); }
void subscribe_states(const SubscribeStatesRequest &msg) override {
this->flags_.state_subscription = true;
this->state_subscription_ = true;
this->initial_state_iterator_.begin();
}
void subscribe_logs(const SubscribeLogsRequest &msg) override {
this->flags_.log_subscription = msg.level;
this->log_subscription_ = msg.level;
if (msg.dump_config)
App.schedule_dump_config();
}
void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) override {
this->flags_.service_call_subscription = true;
this->service_call_subscription_ = true;
}
void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override;
GetTimeResponse get_time(const GetTimeRequest &msg) override {
// TODO
return {};
}
#ifdef USE_API_SERVICES
void execute_service(const ExecuteServiceRequest &msg) override;
#endif
#ifdef USE_API_NOISE
NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) override;
#endif
bool is_authenticated() override {
return static_cast<ConnectionState>(this->flags_.connection_state) == ConnectionState::AUTHENTICATED;
}
bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; }
bool is_connection_setup() override {
return static_cast<ConnectionState>(this->flags_.connection_state) == ConnectionState::CONNECTED ||
this->is_authenticated();
return this->connection_state_ == ConnectionState ::CONNECTED || this->is_authenticated();
}
void on_fatal_error() override;
void on_unauthenticated_access() override;
void on_no_setup_connection() override;
ProtoWriteBuffer create_buffer(uint32_t reserve_size) override {
ProtoWriteBuffer create_buffer() override {
// FIXME: ensure no recursive writes can happen
// Get header padding size - used for both reserve and insert
uint8_t header_padding = this->helper_->frame_header_padding();
// Get shared buffer from parent server
std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
shared_buf.clear();
// Reserve space for header padding + message + footer
// - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext)
// - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext)
shared_buf.reserve(reserve_size + header_padding + this->helper_->frame_footer_size());
// Resize to add header padding so message encoding starts at the correct position
shared_buf.resize(header_padding);
return {&shared_buf};
this->proto_write_buffer_.clear();
return {&this->proto_write_buffer_};
}
bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) override;
// Prepare buffer for next message in batch
ProtoWriteBuffer prepare_message_buffer(uint16_t message_size, bool is_first_message) {
// Get reference to shared buffer (it maintains state between batch messages)
std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
if (is_first_message) {
shared_buf.clear();
}
size_t current_size = shared_buf.size();
// Calculate padding to add:
// - First message: just header padding
// - Subsequent messages: footer for previous message + header padding for this message
size_t padding_to_add = is_first_message
? this->helper_->frame_header_padding()
: this->helper_->frame_header_padding() + this->helper_->frame_footer_size();
// Reserve space for padding + message
shared_buf.reserve(current_size + padding_to_add + message_size);
// Resize to add the padding bytes
shared_buf.resize(current_size + padding_to_add);
return {&shared_buf};
}
bool try_to_clear_buffer(bool log_out_of_space);
bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override;
std::string get_client_combined_info() const {
if (this->client_info_ == this->client_peername_) {
// Before Hello message, both are the same (just IP:port)
return this->client_info_;
}
return this->client_info_ + " (" + this->client_peername_ + ")";
}
// Buffer allocator methods for batch processing
ProtoWriteBuffer allocate_single_message_buffer(uint16_t size);
ProtoWriteBuffer allocate_batch_message_buffer(uint16_t size);
std::string get_client_combined_info() const { return this->client_combined_info_; }
protected:
// Helper function to fill common entity info fields
static void fill_entity_info_base(esphome::EntityBase *entity, InfoResponseProtoMessage &response) {
// Set common fields that are shared by all entity types
response.key = entity->get_object_id_hash();
response.object_id = entity->get_object_id();
friend APIServer;
if (entity->has_own_name())
response.name = entity->get_name();
bool send_(const void *buf, size_t len, bool force);
// Set common EntityBase properties
response.icon = entity->get_icon();
response.disabled_by_default = entity->is_disabled_by_default();
response.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category());
#ifdef USE_DEVICES
response.device_id = entity->get_device_id();
#endif
}
enum class ConnectionState {
WAITING_FOR_HELLO,
CONNECTED,
AUTHENTICATED,
} connection_state_{ConnectionState::WAITING_FOR_HELLO};
// Helper function to fill common entity state fields
static void fill_entity_state_base(esphome::EntityBase *entity, StateResponseProtoMessage &response) {
response.key = entity->get_object_id_hash();
#ifdef USE_DEVICES
response.device_id = entity->get_device_id();
#endif
}
bool remove_{false};
// Non-template helper to encode any ProtoMessage
static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn,
uint32_t remaining_size, bool is_single);
#ifdef USE_VOICE_ASSISTANT
// Helper to check voice assistant validity and connection ownership
inline bool check_voice_assistant_api_connection_() const;
#endif
// Helper method to process multiple entities from an iterator in a batch
template<typename Iterator> void process_iterator_batch_(Iterator &iterator) {
size_t initial_size = this->deferred_batch_.size();
while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < MAX_INITIAL_PER_BATCH) {
iterator.advance();
}
// If the batch is full, process it immediately
// Note: iterator.advance() already calls schedule_batch_() via schedule_message_()
if (this->deferred_batch_.size() >= MAX_INITIAL_PER_BATCH) {
this->process_batch_();
}
}
#ifdef USE_BINARY_SENSOR
static uint16_t try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_binary_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_COVER
static uint16_t try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_cover_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
#endif
#ifdef USE_FAN
static uint16_t try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
static uint16_t try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
#endif
#ifdef USE_LIGHT
static uint16_t try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_light_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
#endif
#ifdef USE_SENSOR
static uint16_t try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_SWITCH
static uint16_t try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_switch_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_TEXT_SENSOR
static uint16_t try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_text_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_CLIMATE
static uint16_t try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_climate_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_NUMBER
static uint16_t try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_number_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_DATETIME_DATE
static uint16_t try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
static uint16_t try_send_date_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
#endif
#ifdef USE_DATETIME_TIME
static uint16_t try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
static uint16_t try_send_time_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
#endif
#ifdef USE_DATETIME_DATETIME
static uint16_t try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_datetime_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_TEXT
static uint16_t try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
static uint16_t try_send_text_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
#endif
#ifdef USE_SELECT
static uint16_t try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_select_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_BUTTON
static uint16_t try_send_button_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_LOCK
static uint16_t try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
static uint16_t try_send_lock_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
#endif
#ifdef USE_VALVE
static uint16_t try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_valve_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
#endif
#ifdef USE_MEDIA_PLAYER
static uint16_t try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_media_player_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_ALARM_CONTROL_PANEL
static uint16_t try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_alarm_control_panel_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_EVENT
static uint16_t try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn,
uint32_t remaining_size, bool is_single);
static uint16_t try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
#endif
#ifdef USE_UPDATE
static uint16_t try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_update_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_CAMERA
static uint16_t try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
// Method for ListEntitiesDone batching
static uint16_t try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
// Method for DisconnectRequest batching
static uint16_t try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
// Batch message method for ping requests
static uint16_t try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
// === Optimal member ordering for 32-bit systems ===
// Group 1: Pointers (4 bytes each on 32-bit)
// Buffer used to encode proto messages
// Re-use to prevent allocations
std::vector<uint8_t> proto_write_buffer_;
std::unique_ptr<APIFrameHelper> helper_;
APIServer *parent_;
// Group 2: Larger objects (must be 4-byte aligned)
// These contain vectors/pointers internally, so putting them early ensures good alignment
InitialStateIterator initial_state_iterator_;
ListEntitiesIterator list_entities_iterator_;
#ifdef USE_CAMERA
std::unique_ptr<camera::CameraImageReader> image_reader_;
#endif
// Group 3: Strings (12 bytes each on 32-bit, 4-byte aligned)
std::string client_info_;
std::string client_peername_;
std::string client_combined_info_;
uint32_t client_api_version_major_{0};
uint32_t client_api_version_minor_{0};
#ifdef USE_ESP32_CAMERA
esp32_camera::CameraImageReader image_reader_;
#endif
// Group 4: 4-byte types
bool state_subscription_{false};
int log_subscription_{ESPHOME_LOG_LEVEL_NONE};
uint32_t last_traffic_;
uint32_t next_ping_retry_{0};
uint8_t ping_retries_{0};
bool sent_ping_{false};
bool service_call_subscription_{false};
bool next_close_ = false;
APIServer *parent_;
DeferredMessageQueue deferred_message_queue_;
InitialStateIterator initial_state_iterator_;
ListEntitiesIterator list_entities_iterator_;
int state_subs_at_ = -1;
// Function pointer type for message encoding
using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single);
class MessageCreator {
public:
// Constructor for function pointer
MessageCreator(MessageCreatorPtr ptr) { data_.function_ptr = ptr; }
// Constructor for string state capture
explicit MessageCreator(const std::string &str_value) { data_.string_ptr = new std::string(str_value); }
// No destructor - cleanup must be called explicitly with message_type
// Delete copy operations - MessageCreator should only be moved
MessageCreator(const MessageCreator &other) = delete;
MessageCreator &operator=(const MessageCreator &other) = delete;
// Move constructor
MessageCreator(MessageCreator &&other) noexcept : data_(other.data_) { other.data_.function_ptr = nullptr; }
// Move assignment
MessageCreator &operator=(MessageCreator &&other) noexcept {
if (this != &other) {
// IMPORTANT: Caller must ensure cleanup() was called if this contains a string!
// In our usage, this happens in add_item() deduplication and vector::erase()
data_ = other.data_;
other.data_.function_ptr = nullptr;
}
return *this;
}
// Call operator - uses message_type to determine union type
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single,
uint8_t message_type) const;
// Manual cleanup method - must be called before destruction for string types
void cleanup(uint8_t message_type) {
#ifdef USE_EVENT
if (message_type == EventResponse::MESSAGE_TYPE && data_.string_ptr != nullptr) {
delete data_.string_ptr;
data_.string_ptr = nullptr;
}
#endif
}
private:
union Data {
MessageCreatorPtr function_ptr;
std::string *string_ptr;
} data_; // 4 bytes on 32-bit, 8 bytes on 64-bit - same as before
};
// Generic batching mechanism for both state updates and entity info
struct DeferredBatch {
struct BatchItem {
EntityBase *entity; // Entity pointer
MessageCreator creator; // Function that creates the message when needed
uint8_t message_type; // Message type for overhead calculation (max 255)
uint8_t estimated_size; // Estimated message size (max 255 bytes)
// Constructor for creating BatchItem
BatchItem(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size)
: entity(entity), creator(std::move(creator)), message_type(message_type), estimated_size(estimated_size) {}
};
std::vector<BatchItem> items;
uint32_t batch_start_time{0};
private:
// Helper to cleanup items from the beginning
void cleanup_items_(size_t count) {
for (size_t i = 0; i < count; i++) {
items[i].creator.cleanup(items[i].message_type);
}
}
public:
DeferredBatch() {
// Pre-allocate capacity for typical batch sizes to avoid reallocation
items.reserve(8);
}
~DeferredBatch() {
// Ensure cleanup of any remaining items
clear();
}
// Add item to the batch
void add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
// Add item to the front of the batch (for high priority messages like ping)
void add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
// Clear all items with proper cleanup
void clear() {
cleanup_items_(items.size());
items.clear();
batch_start_time = 0;
}
// Remove processed items from the front with proper cleanup
void remove_front(size_t count) {
cleanup_items_(count);
items.erase(items.begin(), items.begin() + count);
}
bool empty() const { return items.empty(); }
size_t size() const { return items.size(); }
const BatchItem &operator[](size_t index) const { return items[index]; }
};
// DeferredBatch here (16 bytes, 4-byte aligned)
DeferredBatch deferred_batch_;
// ConnectionState enum for type safety
enum class ConnectionState : uint8_t {
WAITING_FOR_HELLO = 0,
CONNECTED = 1,
AUTHENTICATED = 2,
};
// Group 5: Pack all small members together to minimize padding
// This group starts at a 4-byte boundary after DeferredBatch
struct APIFlags {
// Connection state only needs 2 bits (3 states)
uint8_t connection_state : 2;
// Log subscription needs 3 bits (log levels 0-7)
uint8_t log_subscription : 3;
// Boolean flags (1 bit each)
uint8_t remove : 1;
uint8_t state_subscription : 1;
uint8_t sent_ping : 1;
uint8_t service_call_subscription : 1;
uint8_t next_close : 1;
uint8_t batch_scheduled : 1;
uint8_t batch_first_message : 1; // For batch buffer allocation
uint8_t should_try_send_immediately : 1; // True after initial states are sent
#ifdef HAS_PROTO_MESSAGE_DUMP
uint8_t log_only_mode : 1;
#endif
} flags_{}; // 2 bytes total
// 2-byte types immediately after flags_ (no padding between them)
uint16_t client_api_version_major_{0};
uint16_t client_api_version_minor_{0};
// Total: 2 (flags) + 2 + 2 = 6 bytes, then 2 bytes padding to next 4-byte boundary
uint32_t get_batch_delay_ms_() const;
// Message will use 8 more bytes than the minimum size, and typical
// MTU is 1500. Sometimes users will see as low as 1460 MTU.
// If its IPv6 the header is 40 bytes, and if its IPv4
// the header is 20 bytes. So we have 1460 - 40 = 1420 bytes
// available for the payload. But we also need to add the size of
// the protobuf overhead, which is 8 bytes.
//
// To be safe we pick 1390 bytes as the maximum size
// to send in one go. This is the maximum size of a single packet
// that can be sent over the network.
// This is to avoid fragmentation of the packet.
static constexpr size_t MAX_BATCH_PACKET_SIZE = 1390; // MTU
bool schedule_batch_();
void process_batch_();
void clear_batch_() {
this->deferred_batch_.clear();
this->flags_.batch_scheduled = false;
}
#ifdef HAS_PROTO_MESSAGE_DUMP
// Helper to log a proto message from a MessageCreator object
void log_proto_message_(EntityBase *entity, const MessageCreator &creator, uint8_t message_type) {
this->flags_.log_only_mode = true;
creator(entity, this, MAX_BATCH_PACKET_SIZE, true, message_type);
this->flags_.log_only_mode = false;
}
void log_batch_item_(const DeferredBatch::BatchItem &item) {
// Use the helper to log the message
this->log_proto_message_(item.entity, item.creator, item.message_type);
}
#endif
// Helper method to send a message either immediately or via batching
bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint8_t message_type,
uint8_t estimated_size) {
// Try to send immediately if:
// 1. We should try to send immediately (should_try_send_immediately = true)
// 2. Batch delay is 0 (user has opted in to immediate sending)
// 3. Buffer has space available
if (this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0 &&
this->helper_->can_write_without_blocking()) {
// Now actually encode and send
if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true) &&
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) {
#ifdef HAS_PROTO_MESSAGE_DUMP
// Log the message in verbose mode
this->log_proto_message_(entity, MessageCreator(creator), message_type);
#endif
return true;
}
// If immediate send failed, fall through to batching
}
// Fall back to scheduled batching
return this->schedule_message_(entity, creator, message_type, estimated_size);
}
// Helper function to schedule a deferred message with known message type
bool schedule_message_(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) {
this->deferred_batch_.add_item(entity, std::move(creator), message_type, estimated_size);
return this->schedule_batch_();
}
// Overload for function pointers (for info messages and current state reads)
bool schedule_message_(EntityBase *entity, MessageCreatorPtr function_ptr, uint8_t message_type,
uint8_t estimated_size) {
return schedule_message_(entity, MessageCreator(function_ptr), message_type, estimated_size);
}
// Helper function to schedule a high priority message at the front of the batch
bool schedule_message_front_(EntityBase *entity, MessageCreatorPtr function_ptr, uint8_t message_type,
uint8_t estimated_size) {
this->deferred_batch_.add_item_front(entity, MessageCreator(function_ptr), message_type, estimated_size);
return this->schedule_batch_();
}
};
} // namespace api

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,6 @@
#pragma once
#include <cstdint>
#include <deque>
#include <limits>
#include <span>
#include <utility>
#include <vector>
@ -14,30 +12,25 @@
#include "api_noise_context.h"
#include "esphome/components/socket/socket.h"
#include "esphome/core/application.h"
namespace esphome {
namespace api {
class ProtoWriteBuffer;
struct ReadPacketBuffer {
std::vector<uint8_t> container;
uint16_t type;
uint16_t data_offset;
uint16_t data_len;
size_t data_offset;
size_t data_len;
};
// Packed packet info structure to minimize memory usage
struct PacketInfo {
uint16_t offset; // Offset in buffer where message starts
uint16_t payload_size; // Size of the message payload
uint8_t message_type; // Message type (0-255)
PacketInfo(uint8_t type, uint16_t off, uint16_t size) : offset(off), payload_size(size), message_type(type) {}
struct PacketBuffer {
const std::vector<uint8_t> container;
uint16_t type;
uint8_t data_offset;
uint8_t data_len;
};
enum class APIError : uint16_t {
enum class APIError : int {
OK = 0,
WOULD_BLOCK = 1001,
BAD_HANDSHAKE_PACKET_LEN = 1002,
@ -67,214 +60,131 @@ const char *api_error_to_str(APIError err);
class APIFrameHelper {
public:
APIFrameHelper() = default;
explicit APIFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_owned_(std::move(socket)) {
socket_ = socket_owned_.get();
}
virtual ~APIFrameHelper() = default;
virtual APIError init() = 0;
virtual APIError loop();
virtual APIError loop() = 0;
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
bool can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
std::string getpeername() { return socket_->getpeername(); }
int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); }
APIError close() {
state_ = State::CLOSED;
int err = this->socket_->close();
if (err == -1)
return APIError::CLOSE_FAILED;
return APIError::OK;
}
APIError shutdown(int how) {
int err = this->socket_->shutdown(how);
if (err == -1)
return APIError::SHUTDOWN_FAILED;
if (how == SHUT_RDWR) {
state_ = State::CLOSED;
}
return APIError::OK;
}
virtual bool can_write_without_blocking() = 0;
virtual APIError write_packet(uint16_t type, const uint8_t *data, size_t len) = 0;
virtual std::string getpeername() = 0;
virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0;
virtual APIError close() = 0;
virtual APIError shutdown(int how) = 0;
// Give this helper a name for logging
void set_log_info(std::string info) { info_ = std::move(info); }
virtual APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) = 0;
// Write multiple protobuf packets in a single operation
// packets contains (message_type, offset, length) for each message in the buffer
// The buffer contains all messages with appropriate padding before each
virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) = 0;
// Get the frame header padding required by this protocol
virtual uint8_t frame_header_padding() = 0;
// Get the frame footer size required by this protocol
virtual uint8_t frame_footer_size() = 0;
// Check if socket has data ready to read
bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
protected:
// Struct for holding parsed frame data
struct ParsedFrame {
std::vector<uint8_t> msg;
};
// Buffer containing data to be sent
struct SendBuffer {
std::vector<uint8_t> data;
uint16_t offset{0}; // Current offset within the buffer (uint16_t to reduce memory usage)
// Using uint16_t reduces memory usage since ESPHome API messages are limited to UINT16_MAX (65535) bytes
uint16_t remaining() const { return static_cast<uint16_t>(data.size()) - offset; }
const uint8_t *current_data() const { return data.data() + offset; }
};
// Common implementation for writing raw data to socket
APIError write_raw_(const struct iovec *iov, int iovcnt);
// Try to send data from the tx buffer
APIError try_send_tx_buf_();
// Helper method to buffer data from IOVs
void buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len);
template<typename StateEnum>
APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf,
const std::string &info, StateEnum &state, StateEnum failed_state);
// Pointers first (4 bytes each)
socket::Socket *socket_{nullptr};
std::unique_ptr<socket::Socket> socket_owned_;
// Common state enum for all frame helpers
// Note: Not all states are used by all implementations
// - INITIALIZE: Used by both Noise and Plaintext
// - CLIENT_HELLO, SERVER_HELLO, HANDSHAKE: Only used by Noise protocol
// - DATA: Used by both Noise and Plaintext
// - CLOSED: Used by both Noise and Plaintext
// - FAILED: Used by both Noise and Plaintext
// - EXPLICIT_REJECT: Only used by Noise protocol
enum class State : uint8_t {
INITIALIZE = 1,
CLIENT_HELLO = 2, // Noise only
SERVER_HELLO = 3, // Noise only
HANDSHAKE = 4, // Noise only
DATA = 5,
CLOSED = 6,
FAILED = 7,
EXPLICIT_REJECT = 8, // Noise only
};
// Containers (size varies, but typically 12+ bytes on 32-bit)
std::deque<SendBuffer> tx_buf_;
std::string info_;
std::vector<struct iovec> reusable_iovs_;
std::vector<uint8_t> rx_buf_;
// Group smaller types together
uint16_t rx_buf_len_ = 0;
State state_{State::INITIALIZE};
uint8_t frame_header_padding_{0};
uint8_t frame_footer_size_{0};
// 5 bytes total, 3 bytes padding
// Common initialization for both plaintext and noise protocols
APIError init_common_();
// Helper method to handle socket read results
APIError handle_socket_read_result_(ssize_t received);
virtual void set_log_info(std::string info) = 0;
};
#ifdef USE_API_NOISE
class APINoiseFrameHelper : public APIFrameHelper {
public:
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx)
: APIFrameHelper(std::move(socket)), ctx_(std::move(ctx)) {
// Noise header structure:
// Pos 0: indicator (0x01)
// Pos 1-2: encrypted payload size (16-bit big-endian)
// Pos 3-6: encrypted type (16-bit) + data_len (16-bit)
// Pos 7+: actual payload data
frame_header_padding_ = 7;
}
: socket_(std::move(socket)), ctx_(std::move(std::move(ctx))) {}
~APINoiseFrameHelper() override;
APIError init() override;
APIError loop() override;
APIError read_packet(ReadPacketBuffer *buffer) override;
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
// Get the frame header padding required by this protocol
uint8_t frame_header_padding() override { return frame_header_padding_; }
// Get the frame footer size required by this protocol
uint8_t frame_footer_size() override { return frame_footer_size_; }
bool can_write_without_blocking() override;
APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
std::string getpeername() override { return this->socket_->getpeername(); }
int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
return this->socket_->getpeername(addr, addrlen);
}
APIError close() override;
APIError shutdown(int how) override;
// Give this helper a name for logging
void set_log_info(std::string info) override { info_ = std::move(info); }
protected:
struct ParsedFrame {
std::vector<uint8_t> msg;
};
APIError state_action_();
APIError try_read_frame_(ParsedFrame *frame);
APIError write_frame_(const uint8_t *data, uint16_t len);
APIError try_send_tx_buf_();
APIError write_frame_(const uint8_t *data, size_t len);
APIError write_raw_(const struct iovec *iov, int iovcnt);
APIError init_handshake_();
APIError check_handshake_finished_();
void send_explicit_handshake_reject_(const std::string &reason);
// Pointers first (4 bytes each)
std::unique_ptr<socket::Socket> socket_;
std::string info_;
uint8_t rx_header_buf_[3];
size_t rx_header_buf_len_ = 0;
std::vector<uint8_t> rx_buf_;
size_t rx_buf_len_ = 0;
std::vector<uint8_t> tx_buf_;
std::vector<uint8_t> prologue_;
std::shared_ptr<APINoiseContext> ctx_;
NoiseHandshakeState *handshake_{nullptr};
NoiseCipherState *send_cipher_{nullptr};
NoiseCipherState *recv_cipher_{nullptr};
// Shared pointer (8 bytes on 32-bit = 4 bytes control block pointer + 4 bytes object pointer)
std::shared_ptr<APINoiseContext> ctx_;
// Vector (12 bytes on 32-bit)
std::vector<uint8_t> prologue_;
// NoiseProtocolId (size depends on implementation)
NoiseProtocolId nid_;
// Group small types together
// Fixed-size header buffer for noise protocol:
// 1 byte for indicator + 2 bytes for message size (16-bit value, not varint)
// Note: Maximum message size is UINT16_MAX (65535), with a limit of 128 bytes during handshake phase
uint8_t rx_header_buf_[3];
uint8_t rx_header_buf_len_ = 0;
// 4 bytes total, no padding
enum class State {
INITIALIZE = 1,
CLIENT_HELLO = 2,
SERVER_HELLO = 3,
HANDSHAKE = 4,
DATA = 5,
CLOSED = 6,
FAILED = 7,
EXPLICIT_REJECT = 8,
} state_ = State::INITIALIZE;
};
#endif // USE_API_NOISE
#ifdef USE_API_PLAINTEXT
class APIPlaintextFrameHelper : public APIFrameHelper {
public:
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : APIFrameHelper(std::move(socket)) {
// Plaintext header structure (worst case):
// Pos 0: indicator (0x00)
// Pos 1-3: payload size varint (up to 3 bytes)
// Pos 4-5: message type varint (up to 2 bytes)
// Pos 6+: actual payload data
frame_header_padding_ = 6;
}
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_(std::move(socket)) {}
~APIPlaintextFrameHelper() override = default;
APIError init() override;
APIError loop() override;
APIError read_packet(ReadPacketBuffer *buffer) override;
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
uint8_t frame_header_padding() override { return frame_header_padding_; }
// Get the frame footer size required by this protocol
uint8_t frame_footer_size() override { return frame_footer_size_; }
bool can_write_without_blocking() override;
APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
std::string getpeername() override { return this->socket_->getpeername(); }
int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
return this->socket_->getpeername(addr, addrlen);
}
APIError close() override;
APIError shutdown(int how) override;
// Give this helper a name for logging
void set_log_info(std::string info) override { info_ = std::move(info); }
protected:
struct ParsedFrame {
std::vector<uint8_t> msg;
};
APIError try_read_frame_(ParsedFrame *frame);
APIError try_send_tx_buf_();
APIError write_raw_(const struct iovec *iov, int iovcnt);
// Group 2-byte aligned types
uint16_t rx_header_parsed_type_ = 0;
uint16_t rx_header_parsed_len_ = 0;
std::unique_ptr<socket::Socket> socket_;
// Group 1-byte types together
// Fixed-size header buffer for plaintext protocol:
// We now store the indicator byte + the two varints.
// To match noise protocol's maximum message size (UINT16_MAX = 65535), we need:
// 1 byte for indicator + 3 bytes for message size varint (supports up to 2097151) + 2 bytes for message type varint
//
// While varints could theoretically be up to 10 bytes each for 64-bit values,
// attempting to process messages with headers that large would likely crash the
// ESP32 due to memory constraints.
uint8_t rx_header_buf_[6]; // 1 byte indicator + 5 bytes for varints (3 for size + 2 for type)
uint8_t rx_header_buf_pos_ = 0;
std::string info_;
std::vector<uint8_t> rx_header_buf_;
bool rx_header_parsed_ = false;
// 8 bytes total, no padding needed
uint32_t rx_header_parsed_type_ = 0;
uint32_t rx_header_parsed_len_ = 0;
std::vector<uint8_t> rx_buf_;
size_t rx_buf_len_ = 0;
std::vector<uint8_t> tx_buf_;
enum class State {
INITIALIZE = 1,
DATA = 2,
CLOSED = 3,
FAILED = 4,
} state_ = State::INITIALIZE;
};
#endif

View File

@ -1,6 +1,6 @@
#pragma once
#include <array>
#include <cstdint>
#include <array>
#include "esphome/core/defines.h"
namespace esphome {
@ -11,20 +11,11 @@ using psk_t = std::array<uint8_t, 32>;
class APINoiseContext {
public:
void set_psk(psk_t psk) {
this->psk_ = psk;
bool has_psk = false;
for (auto i : psk) {
has_psk |= i;
}
this->has_psk_ = has_psk;
}
const psk_t &get_psk() const { return this->psk_; }
bool has_psk() const { return this->has_psk_; }
void set_psk(psk_t psk) { psk_ = psk; }
const psk_t &get_psk() const { return psk_; }
protected:
psk_t psk_{};
bool has_psk_{false};
psk_t psk_;
};
#endif // USE_API_NOISE

View File

@ -21,5 +21,4 @@ extend google.protobuf.MessageOptions {
optional string ifdef = 1038;
optional bool log = 1039 [default=true];
optional bool no_delay = 1040 [default=false];
optional string base_class = 1041;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,106 +1,156 @@
// This file was automatically generated with a tool.
// See script/api_protobuf/api_protobuf.py
// See scripts/api_protobuf/api_protobuf.py
#pragma once
#include "esphome/core/defines.h"
#include "api_pb2.h"
#include "esphome/core/defines.h"
namespace esphome {
namespace api {
class APIServerConnectionBase : public ProtoService {
public:
#ifdef HAS_PROTO_MESSAGE_DUMP
protected:
void log_send_message_(const char *name, const std::string &dump);
public:
#endif
template<typename T> bool send_message(const T &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_send_message_(msg.message_name(), msg.dump());
#endif
return this->send_message_(msg, T::MESSAGE_TYPE);
}
virtual void on_hello_request(const HelloRequest &value){};
bool send_hello_response(const HelloResponse &msg);
virtual void on_connect_request(const ConnectRequest &value){};
bool send_connect_response(const ConnectResponse &msg);
bool send_disconnect_request(const DisconnectRequest &msg);
virtual void on_disconnect_request(const DisconnectRequest &value){};
bool send_disconnect_response(const DisconnectResponse &msg);
virtual void on_disconnect_response(const DisconnectResponse &value){};
bool send_ping_request(const PingRequest &msg);
virtual void on_ping_request(const PingRequest &value){};
bool send_ping_response(const PingResponse &msg);
virtual void on_ping_response(const PingResponse &value){};
virtual void on_device_info_request(const DeviceInfoRequest &value){};
bool send_device_info_response(const DeviceInfoResponse &msg);
virtual void on_list_entities_request(const ListEntitiesRequest &value){};
bool send_list_entities_done_response(const ListEntitiesDoneResponse &msg);
virtual void on_subscribe_states_request(const SubscribeStatesRequest &value){};
#ifdef USE_BINARY_SENSOR
bool send_list_entities_binary_sensor_response(const ListEntitiesBinarySensorResponse &msg);
#endif
#ifdef USE_BINARY_SENSOR
bool send_binary_sensor_state_response(const BinarySensorStateResponse &msg);
#endif
#ifdef USE_COVER
bool send_list_entities_cover_response(const ListEntitiesCoverResponse &msg);
#endif
#ifdef USE_COVER
bool send_cover_state_response(const CoverStateResponse &msg);
#endif
#ifdef USE_COVER
virtual void on_cover_command_request(const CoverCommandRequest &value){};
#endif
#ifdef USE_FAN
bool send_list_entities_fan_response(const ListEntitiesFanResponse &msg);
#endif
#ifdef USE_FAN
bool send_fan_state_response(const FanStateResponse &msg);
#endif
#ifdef USE_FAN
virtual void on_fan_command_request(const FanCommandRequest &value){};
#endif
#ifdef USE_LIGHT
bool send_list_entities_light_response(const ListEntitiesLightResponse &msg);
#endif
#ifdef USE_LIGHT
bool send_light_state_response(const LightStateResponse &msg);
#endif
#ifdef USE_LIGHT
virtual void on_light_command_request(const LightCommandRequest &value){};
#endif
#ifdef USE_SENSOR
bool send_list_entities_sensor_response(const ListEntitiesSensorResponse &msg);
#endif
#ifdef USE_SENSOR
bool send_sensor_state_response(const SensorStateResponse &msg);
#endif
#ifdef USE_SWITCH
bool send_list_entities_switch_response(const ListEntitiesSwitchResponse &msg);
#endif
#ifdef USE_SWITCH
bool send_switch_state_response(const SwitchStateResponse &msg);
#endif
#ifdef USE_SWITCH
virtual void on_switch_command_request(const SwitchCommandRequest &value){};
#endif
#ifdef USE_TEXT_SENSOR
bool send_list_entities_text_sensor_response(const ListEntitiesTextSensorResponse &msg);
#endif
#ifdef USE_TEXT_SENSOR
bool send_text_sensor_state_response(const TextSensorStateResponse &msg);
#endif
virtual void on_subscribe_logs_request(const SubscribeLogsRequest &value){};
#ifdef USE_API_NOISE
virtual void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){};
#endif
bool send_subscribe_logs_response(const SubscribeLogsResponse &msg);
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
bool send_homeassistant_service_response(const HomeassistantServiceResponse &msg);
virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
bool send_subscribe_home_assistant_state_response(const SubscribeHomeAssistantStateResponse &msg);
virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
bool send_get_time_request(const GetTimeRequest &msg);
virtual void on_get_time_request(const GetTimeRequest &value){};
bool send_get_time_response(const GetTimeResponse &msg);
virtual void on_get_time_response(const GetTimeResponse &value){};
#ifdef USE_API_SERVICES
bool send_list_entities_services_response(const ListEntitiesServicesResponse &msg);
virtual void on_execute_service_request(const ExecuteServiceRequest &value){};
#ifdef USE_ESP32_CAMERA
bool send_list_entities_camera_response(const ListEntitiesCameraResponse &msg);
#endif
#ifdef USE_CAMERA
#ifdef USE_ESP32_CAMERA
bool send_camera_image_response(const CameraImageResponse &msg);
#endif
#ifdef USE_ESP32_CAMERA
virtual void on_camera_image_request(const CameraImageRequest &value){};
#endif
#ifdef USE_CLIMATE
bool send_list_entities_climate_response(const ListEntitiesClimateResponse &msg);
#endif
#ifdef USE_CLIMATE
bool send_climate_state_response(const ClimateStateResponse &msg);
#endif
#ifdef USE_CLIMATE
virtual void on_climate_command_request(const ClimateCommandRequest &value){};
#endif
#ifdef USE_NUMBER
bool send_list_entities_number_response(const ListEntitiesNumberResponse &msg);
#endif
#ifdef USE_NUMBER
bool send_number_state_response(const NumberStateResponse &msg);
#endif
#ifdef USE_NUMBER
virtual void on_number_command_request(const NumberCommandRequest &value){};
#endif
#ifdef USE_SELECT
bool send_list_entities_select_response(const ListEntitiesSelectResponse &msg);
#endif
#ifdef USE_SELECT
bool send_select_state_response(const SelectStateResponse &msg);
#endif
#ifdef USE_SELECT
virtual void on_select_command_request(const SelectCommandRequest &value){};
#endif
#ifdef USE_SIREN
virtual void on_siren_command_request(const SirenCommandRequest &value){};
#ifdef USE_LOCK
bool send_list_entities_lock_response(const ListEntitiesLockResponse &msg);
#endif
#ifdef USE_LOCK
bool send_lock_state_response(const LockStateResponse &msg);
#endif
#ifdef USE_LOCK
virtual void on_lock_command_request(const LockCommandRequest &value){};
#endif
#ifdef USE_BUTTON
bool send_list_entities_button_response(const ListEntitiesButtonResponse &msg);
#endif
#ifdef USE_BUTTON
virtual void on_button_command_request(const ButtonCommandRequest &value){};
#endif
#ifdef USE_MEDIA_PLAYER
bool send_list_entities_media_player_response(const ListEntitiesMediaPlayerResponse &msg);
#endif
#ifdef USE_MEDIA_PLAYER
bool send_media_player_state_response(const MediaPlayerStateResponse &msg);
#endif
#ifdef USE_MEDIA_PLAYER
virtual void on_media_player_command_request(const MediaPlayerCommandRequest &value){};
#endif
@ -108,19 +158,33 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_subscribe_bluetooth_le_advertisements_request(
const SubscribeBluetoothLEAdvertisementsRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_le_raw_advertisements_response(const BluetoothLERawAdvertisementsResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_device_request(const BluetoothDeviceRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_device_connection_response(const BluetoothDeviceConnectionResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_gatt_get_services_response(const BluetoothGATTGetServicesResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_gatt_get_services_done_response(const BluetoothGATTGetServicesDoneResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value){};
#endif
@ -133,23 +197,43 @@ class APIServerConnectionBase : public ProtoService {
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_connections_free_response(const BluetoothConnectionsFreeResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_gatt_error_response(const BluetoothGATTErrorResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_device_pairing_response(const BluetoothDevicePairingResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_device_unpairing_response(const BluetoothDeviceUnpairingResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_unsubscribe_bluetooth_le_advertisements_request(
const UnsubscribeBluetoothLEAdvertisementsRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &value){};
bool send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &msg);
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){};
#endif
#ifdef USE_VOICE_ASSISTANT
bool send_voice_assistant_request(const VoiceAssistantRequest &msg);
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_response(const VoiceAssistantResponse &value){};
#endif
@ -157,6 +241,7 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){};
#endif
#ifdef USE_VOICE_ASSISTANT
bool send_voice_assistant_audio(const VoiceAssistantAudio &msg);
virtual void on_voice_assistant_audio(const VoiceAssistantAudio &value){};
#endif
#ifdef USE_VOICE_ASSISTANT
@ -165,44 +250,89 @@ class APIServerConnectionBase : public ProtoService {
#ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &value){};
#endif
#ifdef USE_VOICE_ASSISTANT
bool send_voice_assistant_announce_finished(const VoiceAssistantAnnounceFinished &msg);
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &value){};
#endif
#ifdef USE_VOICE_ASSISTANT
bool send_voice_assistant_configuration_response(const VoiceAssistantConfigurationResponse &msg);
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value){};
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg);
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool send_alarm_control_panel_state_response(const AlarmControlPanelStateResponse &msg);
#endif
#ifdef USE_ALARM_CONTROL_PANEL
virtual void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &value){};
#endif
#ifdef USE_TEXT
bool send_list_entities_text_response(const ListEntitiesTextResponse &msg);
#endif
#ifdef USE_TEXT
bool send_text_state_response(const TextStateResponse &msg);
#endif
#ifdef USE_TEXT
virtual void on_text_command_request(const TextCommandRequest &value){};
#endif
#ifdef USE_DATETIME_DATE
bool send_list_entities_date_response(const ListEntitiesDateResponse &msg);
#endif
#ifdef USE_DATETIME_DATE
bool send_date_state_response(const DateStateResponse &msg);
#endif
#ifdef USE_DATETIME_DATE
virtual void on_date_command_request(const DateCommandRequest &value){};
#endif
#ifdef USE_DATETIME_TIME
bool send_list_entities_time_response(const ListEntitiesTimeResponse &msg);
#endif
#ifdef USE_DATETIME_TIME
bool send_time_state_response(const TimeStateResponse &msg);
#endif
#ifdef USE_DATETIME_TIME
virtual void on_time_command_request(const TimeCommandRequest &value){};
#endif
#ifdef USE_EVENT
bool send_list_entities_event_response(const ListEntitiesEventResponse &msg);
#endif
#ifdef USE_EVENT
bool send_event_response(const EventResponse &msg);
#endif
#ifdef USE_VALVE
bool send_list_entities_valve_response(const ListEntitiesValveResponse &msg);
#endif
#ifdef USE_VALVE
bool send_valve_state_response(const ValveStateResponse &msg);
#endif
#ifdef USE_VALVE
virtual void on_valve_command_request(const ValveCommandRequest &value){};
#endif
#ifdef USE_DATETIME_DATETIME
bool send_list_entities_date_time_response(const ListEntitiesDateTimeResponse &msg);
#endif
#ifdef USE_DATETIME_DATETIME
bool send_date_time_state_response(const DateTimeStateResponse &msg);
#endif
#ifdef USE_DATETIME_DATETIME
virtual void on_date_time_command_request(const DateTimeCommandRequest &value){};
#endif
#ifdef USE_UPDATE
bool send_list_entities_update_response(const ListEntitiesUpdateResponse &msg);
#endif
#ifdef USE_UPDATE
bool send_update_state_response(const UpdateStateResponse &msg);
#endif
#ifdef USE_UPDATE
virtual void on_update_command_request(const UpdateCommandRequest &value){};
#endif
protected:
void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
};
class APIServerConnection : public APIServerConnectionBase {
@ -218,66 +348,58 @@ class APIServerConnection : public APIServerConnectionBase {
virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0;
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0;
#ifdef USE_API_SERVICES
virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
#endif
#ifdef USE_API_NOISE
virtual NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) = 0;
#endif
#ifdef USE_BUTTON
virtual void button_command(const ButtonCommandRequest &msg) = 0;
#endif
#ifdef USE_CAMERA
virtual void camera_image(const CameraImageRequest &msg) = 0;
#endif
#ifdef USE_CLIMATE
virtual void climate_command(const ClimateCommandRequest &msg) = 0;
#endif
#ifdef USE_COVER
virtual void cover_command(const CoverCommandRequest &msg) = 0;
#endif
#ifdef USE_DATETIME_DATE
virtual void date_command(const DateCommandRequest &msg) = 0;
#endif
#ifdef USE_DATETIME_DATETIME
virtual void datetime_command(const DateTimeCommandRequest &msg) = 0;
#endif
#ifdef USE_FAN
virtual void fan_command(const FanCommandRequest &msg) = 0;
#endif
#ifdef USE_LIGHT
virtual void light_command(const LightCommandRequest &msg) = 0;
#endif
#ifdef USE_LOCK
virtual void lock_command(const LockCommandRequest &msg) = 0;
#ifdef USE_SWITCH
virtual void switch_command(const SwitchCommandRequest &msg) = 0;
#endif
#ifdef USE_MEDIA_PLAYER
virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0;
#ifdef USE_ESP32_CAMERA
virtual void camera_image(const CameraImageRequest &msg) = 0;
#endif
#ifdef USE_CLIMATE
virtual void climate_command(const ClimateCommandRequest &msg) = 0;
#endif
#ifdef USE_NUMBER
virtual void number_command(const NumberCommandRequest &msg) = 0;
#endif
#ifdef USE_TEXT
virtual void text_command(const TextCommandRequest &msg) = 0;
#endif
#ifdef USE_SELECT
virtual void select_command(const SelectCommandRequest &msg) = 0;
#endif
#ifdef USE_SIREN
virtual void siren_command(const SirenCommandRequest &msg) = 0;
#ifdef USE_BUTTON
virtual void button_command(const ButtonCommandRequest &msg) = 0;
#endif
#ifdef USE_SWITCH
virtual void switch_command(const SwitchCommandRequest &msg) = 0;
#ifdef USE_LOCK
virtual void lock_command(const LockCommandRequest &msg) = 0;
#endif
#ifdef USE_TEXT
virtual void text_command(const TextCommandRequest &msg) = 0;
#ifdef USE_VALVE
virtual void valve_command(const ValveCommandRequest &msg) = 0;
#endif
#ifdef USE_MEDIA_PLAYER
virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0;
#endif
#ifdef USE_DATETIME_DATE
virtual void date_command(const DateCommandRequest &msg) = 0;
#endif
#ifdef USE_DATETIME_TIME
virtual void time_command(const TimeCommandRequest &msg) = 0;
#endif
#ifdef USE_DATETIME_DATETIME
virtual void datetime_command(const DateTimeCommandRequest &msg) = 0;
#endif
#ifdef USE_UPDATE
virtual void update_command(const UpdateCommandRequest &msg) = 0;
#endif
#ifdef USE_VALVE
virtual void valve_command(const ValveCommandRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
#endif
@ -309,9 +431,6 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_BLUETOOTH_PROXY
virtual void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) = 0;
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0;
#endif
@ -337,66 +456,58 @@ class APIServerConnection : public APIServerConnectionBase {
void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override;
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
void on_get_time_request(const GetTimeRequest &msg) override;
#ifdef USE_API_SERVICES
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
#endif
#ifdef USE_API_NOISE
void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override;
#endif
#ifdef USE_BUTTON
void on_button_command_request(const ButtonCommandRequest &msg) override;
#endif
#ifdef USE_CAMERA
void on_camera_image_request(const CameraImageRequest &msg) override;
#endif
#ifdef USE_CLIMATE
void on_climate_command_request(const ClimateCommandRequest &msg) override;
#endif
#ifdef USE_COVER
void on_cover_command_request(const CoverCommandRequest &msg) override;
#endif
#ifdef USE_DATETIME_DATE
void on_date_command_request(const DateCommandRequest &msg) override;
#endif
#ifdef USE_DATETIME_DATETIME
void on_date_time_command_request(const DateTimeCommandRequest &msg) override;
#endif
#ifdef USE_FAN
void on_fan_command_request(const FanCommandRequest &msg) override;
#endif
#ifdef USE_LIGHT
void on_light_command_request(const LightCommandRequest &msg) override;
#endif
#ifdef USE_LOCK
void on_lock_command_request(const LockCommandRequest &msg) override;
#ifdef USE_SWITCH
void on_switch_command_request(const SwitchCommandRequest &msg) override;
#endif
#ifdef USE_MEDIA_PLAYER
void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override;
#ifdef USE_ESP32_CAMERA
void on_camera_image_request(const CameraImageRequest &msg) override;
#endif
#ifdef USE_CLIMATE
void on_climate_command_request(const ClimateCommandRequest &msg) override;
#endif
#ifdef USE_NUMBER
void on_number_command_request(const NumberCommandRequest &msg) override;
#endif
#ifdef USE_TEXT
void on_text_command_request(const TextCommandRequest &msg) override;
#endif
#ifdef USE_SELECT
void on_select_command_request(const SelectCommandRequest &msg) override;
#endif
#ifdef USE_SIREN
void on_siren_command_request(const SirenCommandRequest &msg) override;
#ifdef USE_BUTTON
void on_button_command_request(const ButtonCommandRequest &msg) override;
#endif
#ifdef USE_SWITCH
void on_switch_command_request(const SwitchCommandRequest &msg) override;
#ifdef USE_LOCK
void on_lock_command_request(const LockCommandRequest &msg) override;
#endif
#ifdef USE_TEXT
void on_text_command_request(const TextCommandRequest &msg) override;
#ifdef USE_VALVE
void on_valve_command_request(const ValveCommandRequest &msg) override;
#endif
#ifdef USE_MEDIA_PLAYER
void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override;
#endif
#ifdef USE_DATETIME_DATE
void on_date_command_request(const DateCommandRequest &msg) override;
#endif
#ifdef USE_DATETIME_TIME
void on_time_command_request(const TimeCommandRequest &msg) override;
#endif
#ifdef USE_DATETIME_DATETIME
void on_date_time_command_request(const DateTimeCommandRequest &msg) override;
#endif
#ifdef USE_UPDATE
void on_update_command_request(const UpdateCommandRequest &msg) override;
#endif
#ifdef USE_VALVE
void on_valve_command_request(const ValveCommandRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
#endif
@ -428,9 +539,6 @@ class APIServerConnection : public APIServerConnectionBase {
void on_unsubscribe_bluetooth_le_advertisements_request(
const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) override;
#endif
#ifdef USE_VOICE_ASSISTANT
void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override;
#endif

View File

@ -22,49 +22,22 @@ namespace api {
static const char *const TAG = "api";
// APIServer
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
APIServer::APIServer() {
global_api_server = this;
// Pre-allocate shared write buffer
shared_write_buffer_.reserve(64);
}
void APIServer::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server...");
this->setup_controller();
#ifdef USE_API_NOISE
uint32_t hash = 88491486UL;
this->noise_pref_ = global_preferences->make_preference<SavedNoisePsk>(hash, true);
SavedNoisePsk noise_pref_saved{};
if (this->noise_pref_.load(&noise_pref_saved)) {
ESP_LOGD(TAG, "Loaded saved Noise PSK");
this->set_noise_psk(noise_pref_saved.psk);
}
#endif
// Schedule reboot if no clients connect within timeout
if (this->reboot_timeout_ != 0) {
this->schedule_reboot_timeout_();
}
this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
if (this->socket_ == nullptr) {
ESP_LOGW(TAG, "Could not create socket");
socket_ = socket::socket_ip(SOCK_STREAM, 0);
if (socket_ == nullptr) {
ESP_LOGW(TAG, "Could not create socket.");
this->mark_failed();
return;
}
int enable = 1;
int err = this->socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
int err = socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
if (err != 0) {
ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err);
// we can still continue
}
err = this->socket_->setblocking(false);
err = socket_->setblocking(false);
if (err != 0) {
ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err);
this->mark_failed();
@ -80,14 +53,14 @@ void APIServer::setup() {
return;
}
err = this->socket_->bind((struct sockaddr *) &server, sl);
err = socket_->bind((struct sockaddr *) &server, sl);
if (err != 0) {
ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno);
this->mark_failed();
return;
}
err = this->socket_->listen(4);
err = socket_->listen(4);
if (err != 0) {
ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
this->mark_failed();
@ -96,131 +69,83 @@ void APIServer::setup() {
#ifdef USE_LOGGER
if (logger::global_logger != nullptr) {
logger::global_logger->add_on_log_callback(
[this](int level, const char *tag, const char *message, size_t message_len) {
if (this->shutting_down_) {
// Don't try to send logs during shutdown
// as it could result in a recursion and
// we would be filling a buffer we are trying to clear
return;
}
for (auto &c : this->clients_) {
if (!c->flags_.remove)
c->try_send_log_message(level, tag, message, message_len);
}
});
}
#endif
#ifdef USE_CAMERA
if (camera::Camera::instance() != nullptr && !camera::Camera::instance()->is_internal()) {
camera::Camera::instance()->add_image_callback([this](const std::shared_ptr<camera::CameraImage> &image) {
logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) {
for (auto &c : this->clients_) {
if (!c->flags_.remove)
c->set_camera_state(image);
if (!c->remove_)
c->try_send_log_message(level, tag, message);
}
});
}
#endif
}
void APIServer::schedule_reboot_timeout_() {
this->status_set_warning();
this->set_timeout("api_reboot", this->reboot_timeout_, []() {
if (!global_api_server->is_connected()) {
ESP_LOGE(TAG, "No clients; rebooting");
App.reboot();
}
});
}
this->last_connected_ = millis();
void APIServer::loop() {
// Accept new clients only if the socket exists and has incoming connections
if (this->socket_ && this->socket_->ready()) {
while (true) {
struct sockaddr_storage source_addr;
socklen_t addr_len = sizeof(source_addr);
auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
if (!sock)
break;
ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str());
auto *conn = new APIConnection(std::move(sock), this);
this->clients_.emplace_back(conn);
conn->start();
// Clear warning status and cancel reboot when first client connects
if (this->clients_.size() == 1 && this->reboot_timeout_ != 0) {
this->status_clear_warning();
this->cancel_timeout("api_reboot");
}
}
#ifdef USE_ESP32_CAMERA
if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) {
esp32_camera::global_esp32_camera->add_image_callback(
[this](const std::shared_ptr<esp32_camera::CameraImage> &image) {
for (auto &c : this->clients_) {
if (!c->remove_)
c->set_camera_state(image);
}
});
}
if (this->clients_.empty()) {
return;
}
// Process clients and remove disconnected ones in a single pass
// Check network connectivity once for all clients
if (!network::is_connected()) {
// Network is down - disconnect all clients
for (auto &client : this->clients_) {
client->on_fatal_error();
ESP_LOGW(TAG, "%s: Network down; disconnect", client->get_client_combined_info().c_str());
}
// Continue to process and clean up the clients below
}
size_t client_index = 0;
while (client_index < this->clients_.size()) {
auto &client = this->clients_[client_index];
if (!client->flags_.remove) {
// Common case: process active client
client->loop();
client_index++;
continue;
}
// Rare case: handle disconnection
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
this->client_disconnected_trigger_->trigger(client->client_info_, client->client_peername_);
#endif
ESP_LOGV(TAG, "Remove connection %s", client->client_info_.c_str());
}
void APIServer::loop() {
// Accept new clients
while (true) {
struct sockaddr_storage source_addr;
socklen_t addr_len = sizeof(source_addr);
auto sock = socket_->accept((struct sockaddr *) &source_addr, &addr_len);
if (!sock)
break;
ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str());
// Swap with the last element and pop (avoids expensive vector shifts)
if (client_index < this->clients_.size() - 1) {
std::swap(this->clients_[client_index], this->clients_.back());
}
this->clients_.pop_back();
auto *conn = new APIConnection(std::move(sock), this);
clients_.emplace_back(conn);
conn->start();
}
// Schedule reboot when last client disconnects
if (this->clients_.empty() && this->reboot_timeout_ != 0) {
this->schedule_reboot_timeout_();
// Partition clients into remove and active
auto new_end = std::partition(this->clients_.begin(), this->clients_.end(),
[](const std::unique_ptr<APIConnection> &conn) { return !conn->remove_; });
// print disconnection messages
for (auto it = new_end; it != this->clients_.end(); ++it) {
this->client_disconnected_trigger_->trigger((*it)->client_info_, (*it)->client_peername_);
ESP_LOGV(TAG, "Removing connection to %s", (*it)->client_info_.c_str());
}
// resize vector
this->clients_.erase(new_end, this->clients_.end());
for (auto &client : this->clients_) {
client->loop();
}
if (this->reboot_timeout_ != 0) {
const uint32_t now = millis();
if (!this->is_connected()) {
if (now - this->last_connected_ > this->reboot_timeout_) {
ESP_LOGE(TAG, "No client connected to API. Rebooting...");
App.reboot();
}
this->status_set_warning();
} else {
this->last_connected_ = now;
this->status_clear_warning();
}
// Don't increment client_index since we need to process the swapped element
}
}
void APIServer::dump_config() {
ESP_LOGCONFIG(TAG,
"API Server:\n"
" Address: %s:%u",
network::get_use_address().c_str(), this->port_);
ESP_LOGCONFIG(TAG, "API Server:");
ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->port_);
#ifdef USE_API_NOISE
ESP_LOGCONFIG(TAG, " Using noise encryption: %s", YESNO(this->noise_ctx_->has_psk()));
if (!this->noise_ctx_->has_psk()) {
ESP_LOGCONFIG(TAG, " Supports noise encryption: YES");
}
ESP_LOGCONFIG(TAG, " Using noise encryption: YES");
#else
ESP_LOGCONFIG(TAG, " Using noise encryption: NO");
#endif
}
#ifdef USE_API_PASSWORD
bool APIServer::uses_password() const { return !this->password_.empty(); }
bool APIServer::check_password(const std::string &password) const {
// depend only on input password length
const char *a = this->password_.c_str();
@ -249,136 +174,186 @@ bool APIServer::check_password(const std::string &password) const {
return result == 0;
}
#endif
void APIServer::handle_disconnect(APIConnection *conn) {}
// Macro for entities without extra parameters
#define API_DISPATCH_UPDATE(entity_type, entity_name) \
void APIServer::on_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \
if (obj->is_internal()) \
return; \
for (auto &c : this->clients_) \
c->send_##entity_name##_state(obj); \
}
// Macro for entities with extra parameters (but parameters not used in send)
#define API_DISPATCH_UPDATE_IGNORE_PARAMS(entity_type, entity_name, ...) \
void APIServer::on_##entity_name##_update(entity_type *obj, __VA_ARGS__) { /* NOLINT(bugprone-macro-parentheses) */ \
if (obj->is_internal()) \
return; \
for (auto &c : this->clients_) \
c->send_##entity_name##_state(obj); \
}
#ifdef USE_BINARY_SENSOR
API_DISPATCH_UPDATE(binary_sensor::BinarySensor, binary_sensor)
void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_binary_sensor_state(obj, state);
}
#endif
#ifdef USE_COVER
API_DISPATCH_UPDATE(cover::Cover, cover)
void APIServer::on_cover_update(cover::Cover *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_cover_state(obj);
}
#endif
#ifdef USE_FAN
API_DISPATCH_UPDATE(fan::Fan, fan)
void APIServer::on_fan_update(fan::Fan *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_fan_state(obj);
}
#endif
#ifdef USE_LIGHT
API_DISPATCH_UPDATE(light::LightState, light)
void APIServer::on_light_update(light::LightState *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_light_state(obj);
}
#endif
#ifdef USE_SENSOR
API_DISPATCH_UPDATE_IGNORE_PARAMS(sensor::Sensor, sensor, float state)
void APIServer::on_sensor_update(sensor::Sensor *obj, float state) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_sensor_state(obj, state);
}
#endif
#ifdef USE_SWITCH
API_DISPATCH_UPDATE_IGNORE_PARAMS(switch_::Switch, switch, bool state)
void APIServer::on_switch_update(switch_::Switch *obj, bool state) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_switch_state(obj, state);
}
#endif
#ifdef USE_TEXT_SENSOR
API_DISPATCH_UPDATE_IGNORE_PARAMS(text_sensor::TextSensor, text_sensor, const std::string &state)
void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_text_sensor_state(obj, state);
}
#endif
#ifdef USE_CLIMATE
API_DISPATCH_UPDATE(climate::Climate, climate)
void APIServer::on_climate_update(climate::Climate *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_climate_state(obj);
}
#endif
#ifdef USE_NUMBER
API_DISPATCH_UPDATE_IGNORE_PARAMS(number::Number, number, float state)
void APIServer::on_number_update(number::Number *obj, float state) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_number_state(obj, state);
}
#endif
#ifdef USE_DATETIME_DATE
API_DISPATCH_UPDATE(datetime::DateEntity, date)
void APIServer::on_date_update(datetime::DateEntity *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_date_state(obj);
}
#endif
#ifdef USE_DATETIME_TIME
API_DISPATCH_UPDATE(datetime::TimeEntity, time)
void APIServer::on_time_update(datetime::TimeEntity *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_time_state(obj);
}
#endif
#ifdef USE_DATETIME_DATETIME
API_DISPATCH_UPDATE(datetime::DateTimeEntity, datetime)
void APIServer::on_datetime_update(datetime::DateTimeEntity *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_datetime_state(obj);
}
#endif
#ifdef USE_TEXT
API_DISPATCH_UPDATE_IGNORE_PARAMS(text::Text, text, const std::string &state)
void APIServer::on_text_update(text::Text *obj, const std::string &state) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_text_state(obj, state);
}
#endif
#ifdef USE_SELECT
API_DISPATCH_UPDATE_IGNORE_PARAMS(select::Select, select, const std::string &state, size_t index)
void APIServer::on_select_update(select::Select *obj, const std::string &state, size_t index) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_select_state(obj, state);
}
#endif
#ifdef USE_LOCK
API_DISPATCH_UPDATE(lock::Lock, lock)
void APIServer::on_lock_update(lock::Lock *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_lock_state(obj, obj->state);
}
#endif
#ifdef USE_VALVE
API_DISPATCH_UPDATE(valve::Valve, valve)
void APIServer::on_valve_update(valve::Valve *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_valve_state(obj);
}
#endif
#ifdef USE_MEDIA_PLAYER
API_DISPATCH_UPDATE(media_player::MediaPlayer, media_player)
void APIServer::on_media_player_update(media_player::MediaPlayer *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_media_player_state(obj);
}
#endif
#ifdef USE_EVENT
// Event is a special case - it's the only entity that passes extra parameters to the send method
void APIServer::on_event(event::Event *obj, const std::string &event_type) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_event(obj, event_type);
}
#endif
#ifdef USE_UPDATE
// Update is a special case - the method is called on_update, not on_update_update
void APIServer::on_update(update::UpdateEntity *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_update_state(obj);
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
API_DISPATCH_UPDATE(alarm_control_panel::AlarmControlPanel, alarm_control_panel)
#endif
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
void APIServer::set_port(uint16_t port) { this->port_ = port; }
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
#ifdef USE_API_PASSWORD
void APIServer::set_password(const std::string &password) { this->password_ = password; }
#endif
void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; }
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
for (auto &client : this->clients_) {
client->send_homeassistant_service_call(call);
}
}
APIServer::APIServer() { global_api_server = this; }
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f) {
this->state_subs_.push_back(HomeAssistantStateSubscription{
@ -388,7 +363,6 @@ void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<s
.once = false,
});
}
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f) {
this->state_subs_.push_back(HomeAssistantStateSubscription{
@ -398,91 +372,35 @@ void APIServer::get_home_assistant_state(std::string entity_id, optional<std::st
.once = true,
});
};
const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const {
return this->state_subs_;
}
uint16_t APIServer::get_port() const { return this->port_; }
void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
#ifdef USE_API_NOISE
bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
auto &old_psk = this->noise_ctx_->get_psk();
if (std::equal(old_psk.begin(), old_psk.end(), psk.begin())) {
ESP_LOGW(TAG, "New PSK matches old");
return true;
}
SavedNoisePsk new_saved_psk{psk};
if (!this->noise_pref_.save(&new_saved_psk)) {
ESP_LOGW(TAG, "Failed to save Noise PSK");
return false;
}
// ensure it's written immediately
if (!global_preferences->sync()) {
ESP_LOGW(TAG, "Failed to sync preferences");
return false;
}
ESP_LOGD(TAG, "Noise PSK saved");
if (make_active) {
this->set_timeout(100, [this, psk]() {
ESP_LOGW(TAG, "Disconnecting all clients to reset connections");
this->set_noise_psk(psk);
for (auto &c : this->clients_) {
c->send_message(DisconnectRequest());
}
});
}
return true;
}
#endif
#ifdef USE_HOMEASSISTANT_TIME
void APIServer::request_time() {
for (auto &client : this->clients_) {
if (!client->flags_.remove && client->is_authenticated())
if (!client->remove_ && client->is_authenticated())
client->send_time_request();
}
}
#endif
bool APIServer::is_connected() const { return !this->clients_.empty(); }
void APIServer::on_shutdown() {
this->shutting_down_ = true;
// Close the listening socket to prevent new connections
if (this->socket_) {
this->socket_->close();
this->socket_ = nullptr;
}
// Change batch delay to 5ms for quick flushing during shutdown
this->batch_delay_ = 5;
// Send disconnect requests to all connected clients
for (auto &c : this->clients_) {
if (!c->send_message(DisconnectRequest())) {
// If we can't send the disconnect request directly (tx_buffer full),
// schedule it at the front of the batch so it will be sent with priority
c->schedule_message_front_(nullptr, &APIConnection::try_send_disconnect_request, DisconnectRequest::MESSAGE_TYPE,
DisconnectRequest::ESTIMATED_SIZE);
}
c->send_disconnect_request(DisconnectRequest());
}
delay(10);
}
bool APIServer::teardown() {
// If network is disconnected, no point trying to flush buffers
if (!network::is_connected()) {
return true;
}
this->loop();
// Return true only when all clients have been torn down
return this->clients_.empty();
#ifdef USE_ALARM_CONTROL_PANEL
void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_alarm_control_panel_state(obj);
}
#endif
} // namespace api
} // namespace esphome

View File

@ -12,21 +12,13 @@
#include "esphome/core/log.h"
#include "list_entities.h"
#include "subscribe_state.h"
#ifdef USE_API_SERVICES
#include "user_services.h"
#endif
#include <vector>
namespace esphome {
namespace api {
#ifdef USE_API_NOISE
struct SavedNoisePsk {
psk_t psk;
} PACKED; // NOLINT
#endif
class APIServer : public Component, public Controller {
public:
APIServer();
@ -36,29 +28,20 @@ class APIServer : public Component, public Controller {
void loop() override;
void dump_config() override;
void on_shutdown() override;
bool teardown() override;
#ifdef USE_API_PASSWORD
bool check_password(const std::string &password) const;
bool uses_password() const;
void set_password(const std::string &password);
#endif
void set_port(uint16_t port);
void set_password(const std::string &password);
void set_reboot_timeout(uint32_t reboot_timeout);
void set_batch_delay(uint16_t batch_delay);
uint16_t get_batch_delay() const { return batch_delay_; }
// Get reference to shared buffer for API connections
std::vector<uint8_t> &get_shared_buffer_ref() { return shared_write_buffer_; }
#ifdef USE_API_NOISE
bool save_noise_psk(psk_t psk, bool make_active = true);
void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(psk); }
std::shared_ptr<APINoiseContext> get_noise_ctx() { return noise_ctx_; }
#endif // USE_API_NOISE
void handle_disconnect(APIConnection *conn);
#ifdef USE_BINARY_SENSOR
void on_binary_sensor_update(binary_sensor::BinarySensor *obj) override;
void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override;
#endif
#ifdef USE_COVER
void on_cover_update(cover::Cover *obj) override;
@ -109,9 +92,7 @@ class APIServer : public Component, public Controller {
void on_media_player_update(media_player::MediaPlayer *obj) override;
#endif
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
#ifdef USE_API_SERVICES
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
#endif
#ifdef USE_HOMEASSISTANT_TIME
void request_time();
#endif
@ -140,53 +121,27 @@ class APIServer : public Component, public Controller {
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f);
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
#ifdef USE_API_SERVICES
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }
#endif
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
Trigger<std::string, std::string> *get_client_connected_trigger() const { return this->client_connected_trigger_; }
#endif
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
Trigger<std::string, std::string> *get_client_disconnected_trigger() const {
return this->client_disconnected_trigger_;
}
#endif
protected:
void schedule_reboot_timeout_();
// Pointers and pointer-like types first (4 bytes each)
std::unique_ptr<socket::Socket> socket_ = nullptr;
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>();
#endif
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
Trigger<std::string, std::string> *client_disconnected_trigger_ = new Trigger<std::string, std::string>();
#endif
// 4-byte aligned types
uint32_t reboot_timeout_{300000};
// Vectors and strings (12 bytes each on 32-bit)
std::vector<std::unique_ptr<APIConnection>> clients_;
#ifdef USE_API_PASSWORD
std::string password_;
#endif
std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections
std::vector<HomeAssistantStateSubscription> state_subs_;
#ifdef USE_API_SERVICES
std::vector<UserServiceDescriptor *> user_services_;
#endif
// Group smaller types together
uint16_t port_{6053};
uint16_t batch_delay_{100};
bool shutting_down_ = false;
// 5 bytes used, 3 bytes padding
uint32_t reboot_timeout_{300000};
uint32_t last_connected_{0};
std::vector<std::unique_ptr<APIConnection>> clients_;
std::string password_;
std::vector<HomeAssistantStateSubscription> state_subs_;
std::vector<UserServiceDescriptor *> user_services_;
Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>();
Trigger<std::string, std::string> *client_disconnected_trigger_ = new Trigger<std::string, std::string>();
#ifdef USE_API_NOISE
std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>();
ESPPreferenceObject noise_pref_;
#endif // USE_API_NOISE
};

View File

@ -4,15 +4,9 @@ import asyncio
from datetime import datetime
import logging
from typing import TYPE_CHECKING, Any
import warnings
# Suppress protobuf version warnings
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore", category=UserWarning, message=".*Protobuf gencode version.*"
)
from aioesphomeapi import APIClient, parse_log_message
from aioesphomeapi.log_runner import async_run
from aioesphomeapi import APIClient
from aioesphomeapi.log_runner import async_run
from esphome.const import CONF_KEY, CONF_PASSWORD, CONF_PORT, __version__
from esphome.core import CORE
@ -35,8 +29,8 @@ async def async_run_logs(config: dict[str, Any], address: str) -> None:
port: int = int(conf[CONF_PORT])
password: str = conf[CONF_PASSWORD]
noise_psk: str | None = None
if (encryption := conf.get(CONF_ENCRYPTION)) and (key := encryption.get(CONF_KEY)):
noise_psk = key
if CONF_ENCRYPTION in conf:
noise_psk = conf[CONF_ENCRYPTION][CONF_KEY]
_LOGGER.info("Starting log output from %s using esphome API", address)
cli = APIClient(
address,
@ -52,10 +46,9 @@ async def async_run_logs(config: dict[str, Any], address: str) -> None:
time_ = datetime.now()
message: bytes = msg.message
text = message.decode("utf8", "backslashreplace")
for parsed_msg in parse_log_message(
text, f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}]"
):
print(parsed_msg.replace("\033", "\\033") if dashboard else parsed_msg)
if dashboard:
text = text.replace("\033", "\\033")
print(f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}]{text}")
stop = await async_run(cli, on_log, name=name)
try:

View File

@ -3,13 +3,10 @@
#include <map>
#include "api_server.h"
#ifdef USE_API
#ifdef USE_API_SERVICES
#include "user_services.h"
#endif
namespace esphome {
namespace api {
#ifdef USE_API_SERVICES
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceBase<Ts...> {
public:
CustomAPIDeviceService(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names, T *obj,
@ -22,7 +19,6 @@ template<typename T, typename... Ts> class CustomAPIDeviceService : public UserS
T *obj_;
void (T::*callback_)(Ts...);
};
#endif // USE_API_SERVICES
class CustomAPIDevice {
public:
@ -50,14 +46,12 @@ class CustomAPIDevice {
* @param name The name of the service to register.
* @param arg_names The name of the arguments for the service, must match the arguments of the function.
*/
#ifdef USE_API_SERVICES
template<typename T, typename... Ts>
void register_service(void (T::*callback)(Ts...), const std::string &name,
const std::array<std::string, sizeof...(Ts)> &arg_names) {
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT
global_api_server->register_user_service(service);
}
#endif
/** Register a custom native API service that will show up in Home Assistant.
*
@ -77,12 +71,10 @@ class CustomAPIDevice {
* @param callback The member function to call when the service is triggered.
* @param name The name of the arguments for the service, must match the arguments of the function.
*/
#ifdef USE_API_SERVICES
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT
global_api_server->register_user_service(service);
}
#endif
/** Subscribe to the state (or attribute state) of an entity from Home Assistant.
*

View File

@ -3,8 +3,8 @@
#include "api_server.h"
#ifdef USE_API
#include "api_pb2.h"
#include "esphome/core/automation.h"
#include "esphome/core/helpers.h"
#include "esphome/core/automation.h"
#include <vector>
namespace esphome {

View File

@ -1,7 +1,6 @@
#include "list_entities.h"
#ifdef USE_API
#include "api_connection.h"
#include "api_pb2.h"
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "esphome/core/util.h"
@ -9,84 +8,152 @@
namespace esphome {
namespace api {
// Generate entity handler implementations using macros
#ifdef USE_BINARY_SENSOR
LIST_ENTITIES_HANDLER(binary_sensor, binary_sensor::BinarySensor, ListEntitiesBinarySensorResponse)
bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
this->client_->send_binary_sensor_info(binary_sensor);
return true;
}
#endif
#ifdef USE_COVER
LIST_ENTITIES_HANDLER(cover, cover::Cover, ListEntitiesCoverResponse)
bool ListEntitiesIterator::on_cover(cover::Cover *cover) {
this->client_->send_cover_info(cover);
return true;
}
#endif
#ifdef USE_FAN
LIST_ENTITIES_HANDLER(fan, fan::Fan, ListEntitiesFanResponse)
bool ListEntitiesIterator::on_fan(fan::Fan *fan) {
this->client_->send_fan_info(fan);
return true;
}
#endif
#ifdef USE_LIGHT
LIST_ENTITIES_HANDLER(light, light::LightState, ListEntitiesLightResponse)
bool ListEntitiesIterator::on_light(light::LightState *light) {
this->client_->send_light_info(light);
return true;
}
#endif
#ifdef USE_SENSOR
LIST_ENTITIES_HANDLER(sensor, sensor::Sensor, ListEntitiesSensorResponse)
bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) {
this->client_->send_sensor_info(sensor);
return true;
}
#endif
#ifdef USE_SWITCH
LIST_ENTITIES_HANDLER(switch, switch_::Switch, ListEntitiesSwitchResponse)
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) {
this->client_->send_switch_info(a_switch);
return true;
}
#endif
#ifdef USE_BUTTON
LIST_ENTITIES_HANDLER(button, button::Button, ListEntitiesButtonResponse)
bool ListEntitiesIterator::on_button(button::Button *button) {
this->client_->send_button_info(button);
return true;
}
#endif
#ifdef USE_TEXT_SENSOR
LIST_ENTITIES_HANDLER(text_sensor, text_sensor::TextSensor, ListEntitiesTextSensorResponse)
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
this->client_->send_text_sensor_info(text_sensor);
return true;
}
#endif
#ifdef USE_LOCK
LIST_ENTITIES_HANDLER(lock, lock::Lock, ListEntitiesLockResponse)
bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) {
this->client_->send_lock_info(a_lock);
return true;
}
#endif
#ifdef USE_VALVE
LIST_ENTITIES_HANDLER(valve, valve::Valve, ListEntitiesValveResponse)
#endif
#ifdef USE_CAMERA
LIST_ENTITIES_HANDLER(camera, camera::Camera, ListEntitiesCameraResponse)
#endif
#ifdef USE_CLIMATE
LIST_ENTITIES_HANDLER(climate, climate::Climate, ListEntitiesClimateResponse)
#endif
#ifdef USE_NUMBER
LIST_ENTITIES_HANDLER(number, number::Number, ListEntitiesNumberResponse)
#endif
#ifdef USE_DATETIME_DATE
LIST_ENTITIES_HANDLER(date, datetime::DateEntity, ListEntitiesDateResponse)
#endif
#ifdef USE_DATETIME_TIME
LIST_ENTITIES_HANDLER(time, datetime::TimeEntity, ListEntitiesTimeResponse)
#endif
#ifdef USE_DATETIME_DATETIME
LIST_ENTITIES_HANDLER(datetime, datetime::DateTimeEntity, ListEntitiesDateTimeResponse)
#endif
#ifdef USE_TEXT
LIST_ENTITIES_HANDLER(text, text::Text, ListEntitiesTextResponse)
#endif
#ifdef USE_SELECT
LIST_ENTITIES_HANDLER(select, select::Select, ListEntitiesSelectResponse)
#endif
#ifdef USE_MEDIA_PLAYER
LIST_ENTITIES_HANDLER(media_player, media_player::MediaPlayer, ListEntitiesMediaPlayerResponse)
#endif
#ifdef USE_ALARM_CONTROL_PANEL
LIST_ENTITIES_HANDLER(alarm_control_panel, alarm_control_panel::AlarmControlPanel,
ListEntitiesAlarmControlPanelResponse)
#endif
#ifdef USE_EVENT
LIST_ENTITIES_HANDLER(event, event::Event, ListEntitiesEventResponse)
#endif
#ifdef USE_UPDATE
LIST_ENTITIES_HANDLER(update, update::UpdateEntity, ListEntitiesUpdateResponse)
bool ListEntitiesIterator::on_valve(valve::Valve *valve) {
this->client_->send_valve_info(valve);
return true;
}
#endif
// Special cases that don't follow the pattern
bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); }
ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {}
#ifdef USE_API_SERVICES
bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
auto resp = service->encode_list_service_response();
return this->client_->send_message(resp);
return this->client_->send_list_entities_services_response(resp);
}
#ifdef USE_ESP32_CAMERA
bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) {
this->client_->send_camera_info(camera);
return true;
}
#endif
#ifdef USE_CLIMATE
bool ListEntitiesIterator::on_climate(climate::Climate *climate) {
this->client_->send_climate_info(climate);
return true;
}
#endif
#ifdef USE_NUMBER
bool ListEntitiesIterator::on_number(number::Number *number) {
this->client_->send_number_info(number);
return true;
}
#endif
#ifdef USE_DATETIME_DATE
bool ListEntitiesIterator::on_date(datetime::DateEntity *date) {
this->client_->send_date_info(date);
return true;
}
#endif
#ifdef USE_DATETIME_TIME
bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) {
this->client_->send_time_info(time);
return true;
}
#endif
#ifdef USE_DATETIME_DATETIME
bool ListEntitiesIterator::on_datetime(datetime::DateTimeEntity *datetime) {
this->client_->send_datetime_info(datetime);
return true;
}
#endif
#ifdef USE_TEXT
bool ListEntitiesIterator::on_text(text::Text *text) {
this->client_->send_text_info(text);
return true;
}
#endif
#ifdef USE_SELECT
bool ListEntitiesIterator::on_select(select::Select *select) {
this->client_->send_select_info(select);
return true;
}
#endif
#ifdef USE_MEDIA_PLAYER
bool ListEntitiesIterator::on_media_player(media_player::MediaPlayer *media_player) {
this->client_->send_media_player_info(media_player);
return true;
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
this->client_->send_alarm_control_panel_info(a_alarm_control_panel);
return true;
}
#endif
#ifdef USE_EVENT
bool ListEntitiesIterator::on_event(event::Event *event) {
this->client_->send_event_info(event);
return true;
}
#endif
#ifdef USE_UPDATE
bool ListEntitiesIterator::on_update(update::UpdateEntity *update) {
this->client_->send_update_info(update);
return true;
}
#endif

View File

@ -9,85 +9,75 @@ namespace api {
class APIConnection;
// Macro for generating ListEntitiesIterator handlers
// Calls schedule_message_ with try_send_*_info
#define LIST_ENTITIES_HANDLER(entity_type, EntityClass, ResponseType) \
bool ListEntitiesIterator::on_##entity_type(EntityClass *entity) { /* NOLINT(bugprone-macro-parentheses) */ \
return this->client_->schedule_message_(entity, &APIConnection::try_send_##entity_type##_info, \
ResponseType::MESSAGE_TYPE, ResponseType::ESTIMATED_SIZE); \
}
class ListEntitiesIterator : public ComponentIterator {
public:
ListEntitiesIterator(APIConnection *client);
#ifdef USE_BINARY_SENSOR
bool on_binary_sensor(binary_sensor::BinarySensor *entity) override;
bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override;
#endif
#ifdef USE_COVER
bool on_cover(cover::Cover *entity) override;
bool on_cover(cover::Cover *cover) override;
#endif
#ifdef USE_FAN
bool on_fan(fan::Fan *entity) override;
bool on_fan(fan::Fan *fan) override;
#endif
#ifdef USE_LIGHT
bool on_light(light::LightState *entity) override;
bool on_light(light::LightState *light) override;
#endif
#ifdef USE_SENSOR
bool on_sensor(sensor::Sensor *entity) override;
bool on_sensor(sensor::Sensor *sensor) override;
#endif
#ifdef USE_SWITCH
bool on_switch(switch_::Switch *entity) override;
bool on_switch(switch_::Switch *a_switch) override;
#endif
#ifdef USE_BUTTON
bool on_button(button::Button *entity) override;
bool on_button(button::Button *button) override;
#endif
#ifdef USE_TEXT_SENSOR
bool on_text_sensor(text_sensor::TextSensor *entity) override;
bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
#endif
#ifdef USE_API_SERVICES
bool on_service(UserServiceDescriptor *service) override;
#endif
#ifdef USE_CAMERA
bool on_camera(camera::Camera *entity) override;
#ifdef USE_ESP32_CAMERA
bool on_camera(esp32_camera::ESP32Camera *camera) override;
#endif
#ifdef USE_CLIMATE
bool on_climate(climate::Climate *entity) override;
bool on_climate(climate::Climate *climate) override;
#endif
#ifdef USE_NUMBER
bool on_number(number::Number *entity) override;
bool on_number(number::Number *number) override;
#endif
#ifdef USE_DATETIME_DATE
bool on_date(datetime::DateEntity *entity) override;
bool on_date(datetime::DateEntity *date) override;
#endif
#ifdef USE_DATETIME_TIME
bool on_time(datetime::TimeEntity *entity) override;
bool on_time(datetime::TimeEntity *time) override;
#endif
#ifdef USE_DATETIME_DATETIME
bool on_datetime(datetime::DateTimeEntity *entity) override;
bool on_datetime(datetime::DateTimeEntity *datetime) override;
#endif
#ifdef USE_TEXT
bool on_text(text::Text *entity) override;
bool on_text(text::Text *text) override;
#endif
#ifdef USE_SELECT
bool on_select(select::Select *entity) override;
bool on_select(select::Select *select) override;
#endif
#ifdef USE_LOCK
bool on_lock(lock::Lock *entity) override;
bool on_lock(lock::Lock *a_lock) override;
#endif
#ifdef USE_VALVE
bool on_valve(valve::Valve *entity) override;
bool on_valve(valve::Valve *valve) override;
#endif
#ifdef USE_MEDIA_PLAYER
bool on_media_player(media_player::MediaPlayer *entity) override;
bool on_media_player(media_player::MediaPlayer *media_player) override;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *entity) override;
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override;
#endif
#ifdef USE_EVENT
bool on_event(event::Event *entity) override;
bool on_event(event::Event *event) override;
#endif
#ifdef USE_UPDATE
bool on_update(update::UpdateEntity *entity) override;
bool on_update(update::UpdateEntity *update) override;
#endif
bool on_end() override;
bool completed() { return this->state_ == IteratorState::NONE; }

View File

@ -1,6 +1,5 @@
#include "proto.h"
#include <cinttypes>
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {

View File

@ -1,10 +1,9 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include <cassert>
#include <vector>
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
@ -21,26 +20,16 @@ class ProtoVarInt {
explicit ProtoVarInt(uint64_t value) : value_(value) {}
static optional<ProtoVarInt> parse(const uint8_t *buffer, uint32_t len, uint32_t *consumed) {
if (len == 0) {
if (consumed != nullptr)
*consumed = 0;
if (consumed != nullptr)
*consumed = 0;
if (len == 0)
return {};
}
// Most common case: single-byte varint (values 0-127)
if ((buffer[0] & 0x80) == 0) {
if (consumed != nullptr)
*consumed = 1;
return ProtoVarInt(buffer[0]);
}
uint64_t result = 0;
uint8_t bitpos = 0;
// General case for multi-byte varints
// Since we know buffer[0]'s high bit is set, initialize with its value
uint64_t result = buffer[0] & 0x7F;
uint8_t bitpos = 7;
// Start from the second byte since we've already processed the first
for (uint32_t i = 1; i < len; i++) {
for (uint32_t i = 0; i < len; i++) {
uint8_t val = buffer[i];
result |= uint64_t(val & 0x7F) << uint64_t(bitpos);
bitpos += 7;
@ -51,15 +40,13 @@ class ProtoVarInt {
}
}
if (consumed != nullptr)
*consumed = 0;
return {}; // Incomplete or invalid varint
return {};
}
uint16_t as_uint16() const { return this->value_; }
uint32_t as_uint32() const { return this->value_; }
uint64_t as_uint64() const { return this->value_; }
bool as_bool() const { return this->value_; }
template<typename T> T as_enum() const { return static_cast<T>(this->as_uint32()); }
int32_t as_int32() const {
// Not ZigZag encoded
return static_cast<int32_t>(this->as_int64());
@ -84,34 +71,6 @@ class ProtoVarInt {
return static_cast<int64_t>(this->value_ >> 1);
}
}
/**
* Encode the varint value to a pre-allocated buffer without bounds checking.
*
* @param buffer The pre-allocated buffer to write the encoded varint to
* @param len The size of the buffer in bytes
*
* @note The caller is responsible for ensuring the buffer is large enough
* to hold the encoded value. Use ProtoSize::varint() to calculate
* the exact size needed before calling this method.
* @note No bounds checking is performed for performance reasons.
*/
void encode_to_buffer_unchecked(uint8_t *buffer, size_t len) {
uint64_t val = this->value_;
if (val <= 0x7F) {
buffer[0] = val;
return;
}
size_t i = 0;
while (val && i < len) {
uint8_t temp = val & 0x7F;
val >>= 7;
if (val) {
buffer[i++] = temp | 0x80;
} else {
buffer[i++] = temp;
}
}
}
void encode(std::vector<uint8_t> &out) {
uint64_t val = this->value_;
if (val <= 0x7F) {
@ -133,24 +92,15 @@ class ProtoVarInt {
uint64_t value_;
};
// Forward declaration for decode_to_message and encode_to_writer
class ProtoMessage;
class ProtoLengthDelimited {
public:
explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {}
std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); }
/**
* Decode the length-delimited data into an existing ProtoMessage instance.
*
* This method allows decoding without templates, enabling use in contexts
* where the message type is not known at compile time. The ProtoMessage's
* decode() method will be called with the raw data and length.
*
* @param msg The ProtoMessage instance to decode into
*/
void decode_to_message(ProtoMessage &msg) const;
template<class C> C as_message() const {
auto msg = C();
msg.decode(this->value_, this->length_);
return msg;
}
protected:
const uint8_t *const value_;
@ -199,18 +149,6 @@ class ProtoWriteBuffer {
void write(uint8_t value) { this->buffer_->push_back(value); }
void encode_varint_raw(ProtoVarInt value) { value.encode(*this->buffer_); }
void encode_varint_raw(uint32_t value) { this->encode_varint_raw(ProtoVarInt(value)); }
/**
* Encode a field key (tag/wire type combination).
*
* @param field_id Field number (tag) in the protobuf message
* @param type Wire type value:
* - 0: Varint (int32, int64, uint32, uint64, sint32, sint64, bool, enum)
* - 1: 64-bit (fixed64, sfixed64, double)
* - 2: Length-delimited (string, bytes, embedded messages, packed repeated fields)
* - 5: 32-bit (fixed32, sfixed32, float)
*
* Following https://protobuf.dev/programming-guides/encoding/#structure
*/
void encode_field_raw(uint32_t field_id, uint32_t type) {
uint32_t val = (field_id << 3) | (type & 0b111);
this->encode_varint_raw(val);
@ -219,13 +157,13 @@ class ProtoWriteBuffer {
if (len == 0 && !force)
return;
this->encode_field_raw(field_id, 2); // type 2: Length-delimited string
this->encode_field_raw(field_id, 2);
this->encode_varint_raw(len);
auto *data = reinterpret_cast<const uint8_t *>(string);
this->buffer_->insert(this->buffer_->end(), data, data + len);
}
void encode_string(uint32_t field_id, const std::string &value, bool force = false) {
this->encode_string(field_id, value.data(), value.size(), force);
this->encode_string(field_id, value.data(), value.size());
}
void encode_bytes(uint32_t field_id, const uint8_t *data, size_t len, bool force = false) {
this->encode_string(field_id, reinterpret_cast<const char *>(data), len, force);
@ -233,26 +171,26 @@ class ProtoWriteBuffer {
void encode_uint32(uint32_t field_id, uint32_t value, bool force = false) {
if (value == 0 && !force)
return;
this->encode_field_raw(field_id, 0); // type 0: Varint - uint32
this->encode_field_raw(field_id, 0);
this->encode_varint_raw(value);
}
void encode_uint64(uint32_t field_id, uint64_t value, bool force = false) {
if (value == 0 && !force)
return;
this->encode_field_raw(field_id, 0); // type 0: Varint - uint64
this->encode_field_raw(field_id, 0);
this->encode_varint_raw(ProtoVarInt(value));
}
void encode_bool(uint32_t field_id, bool value, bool force = false) {
if (!value && !force)
return;
this->encode_field_raw(field_id, 0); // type 0: Varint - bool
this->encode_field_raw(field_id, 0);
this->write(0x01);
}
void encode_fixed32(uint32_t field_id, uint32_t value, bool force = false) {
if (value == 0 && !force)
return;
this->encode_field_raw(field_id, 5); // type 5: 32-bit fixed32
this->encode_field_raw(field_id, 5);
this->write((value >> 0) & 0xFF);
this->write((value >> 8) & 0xFF);
this->write((value >> 16) & 0xFF);
@ -262,7 +200,7 @@ class ProtoWriteBuffer {
if (value == 0 && !force)
return;
this->encode_field_raw(field_id, 1); // type 1: 64-bit fixed64
this->encode_field_raw(field_id, 5);
this->write((value >> 0) & 0xFF);
this->write((value >> 8) & 0xFF);
this->write((value >> 16) & 0xFF);
@ -272,6 +210,9 @@ class ProtoWriteBuffer {
this->write((value >> 48) & 0xFF);
this->write((value >> 56) & 0xFF);
}
template<typename T> void encode_enum(uint32_t field_id, T value, bool force = false) {
this->encode_uint32(field_id, static_cast<uint32_t>(value), force);
}
void encode_float(uint32_t field_id, float value, bool force = false) {
if (value == 0.0f && !force)
return;
@ -312,7 +253,18 @@ class ProtoWriteBuffer {
}
this->encode_uint64(field_id, uvalue, force);
}
void encode_message(uint32_t field_id, const ProtoMessage &value, bool force = false);
template<class C> void encode_message(uint32_t field_id, const C &value, bool force = false) {
this->encode_field_raw(field_id, 2);
size_t begin = this->buffer_->size();
value.encode(*this);
const uint32_t nested_length = this->buffer_->size() - begin;
// add size varint
std::vector<uint8_t> var;
ProtoVarInt(nested_length).encode(var);
this->buffer_->insert(this->buffer_->begin() + begin, var.begin(), var.end());
}
std::vector<uint8_t> *get_buffer() const { return buffer_; }
protected:
@ -322,15 +274,11 @@ class ProtoWriteBuffer {
class ProtoMessage {
public:
virtual ~ProtoMessage() = default;
// Default implementation for messages with no fields
virtual void encode(ProtoWriteBuffer buffer) const {}
virtual void encode(ProtoWriteBuffer buffer) const = 0;
void decode(const uint8_t *buffer, size_t length);
// Default implementation for messages with no fields
virtual void calculate_size(uint32_t &total_size) const {}
#ifdef HAS_PROTO_MESSAGE_DUMP
std::string dump() const;
virtual void dump_to(std::string &out) const = 0;
virtual const char *message_name() const { return "unknown"; }
#endif
protected:
@ -340,494 +288,6 @@ class ProtoMessage {
virtual bool decode_64bit(uint32_t field_id, Proto64Bit value) { return false; }
};
class ProtoSize {
public:
/**
* @brief ProtoSize class for Protocol Buffer serialization size calculation
*
* This class provides static methods to calculate the exact byte counts needed
* for encoding various Protocol Buffer field types. All methods are designed to be
* efficient for the common case where many fields have default values.
*
* Implements Protocol Buffer encoding size calculation according to:
* https://protobuf.dev/programming-guides/encoding/
*
* Key features:
* - Early-return optimization for zero/default values
* - Direct total_size updates to avoid unnecessary additions
* - Specialized handling for different field types according to protobuf spec
* - Templated helpers for repeated fields and messages
*/
/**
* @brief Calculates the size in bytes needed to encode a uint32_t value as a varint
*
* @param value The uint32_t value to calculate size for
* @return The number of bytes needed to encode the value
*/
static inline uint32_t varint(uint32_t value) {
// Optimized varint size calculation using leading zeros
// Each 7 bits requires one byte in the varint encoding
if (value < 128)
return 1; // 7 bits, common case for small values
// For larger values, count bytes needed based on the position of the highest bit set
if (value < 16384) {
return 2; // 14 bits
} else if (value < 2097152) {
return 3; // 21 bits
} else if (value < 268435456) {
return 4; // 28 bits
} else {
return 5; // 32 bits (maximum for uint32_t)
}
}
/**
* @brief Calculates the size in bytes needed to encode a uint64_t value as a varint
*
* @param value The uint64_t value to calculate size for
* @return The number of bytes needed to encode the value
*/
static inline uint32_t varint(uint64_t value) {
// Handle common case of values fitting in uint32_t (vast majority of use cases)
if (value <= UINT32_MAX) {
return varint(static_cast<uint32_t>(value));
}
// For larger values, determine size based on highest bit position
if (value < (1ULL << 35)) {
return 5; // 35 bits
} else if (value < (1ULL << 42)) {
return 6; // 42 bits
} else if (value < (1ULL << 49)) {
return 7; // 49 bits
} else if (value < (1ULL << 56)) {
return 8; // 56 bits
} else if (value < (1ULL << 63)) {
return 9; // 63 bits
} else {
return 10; // 64 bits (maximum for uint64_t)
}
}
/**
* @brief Calculates the size in bytes needed to encode an int32_t value as a varint
*
* Special handling is needed for negative values, which are sign-extended to 64 bits
* in Protocol Buffers, resulting in a 10-byte varint.
*
* @param value The int32_t value to calculate size for
* @return The number of bytes needed to encode the value
*/
static inline uint32_t varint(int32_t value) {
// Negative values are sign-extended to 64 bits in protocol buffers,
// which always results in a 10-byte varint for negative int32
if (value < 0) {
return 10; // Negative int32 is always 10 bytes long
}
// For non-negative values, use the uint32_t implementation
return varint(static_cast<uint32_t>(value));
}
/**
* @brief Calculates the size in bytes needed to encode an int64_t value as a varint
*
* @param value The int64_t value to calculate size for
* @return The number of bytes needed to encode the value
*/
static inline uint32_t varint(int64_t value) {
// For int64_t, we convert to uint64_t and calculate the size
// This works because the bit pattern determines the encoding size,
// and we've handled negative int32 values as a special case above
return varint(static_cast<uint64_t>(value));
}
/**
* @brief Calculates the size in bytes needed to encode a field ID and wire type
*
* @param field_id The field identifier
* @param type The wire type value (from the WireType enum in the protobuf spec)
* @return The number of bytes needed to encode the field ID and wire type
*/
static inline uint32_t field(uint32_t field_id, uint32_t type) {
uint32_t tag = (field_id << 3) | (type & 0b111);
return varint(tag);
}
/**
* @brief Common parameters for all add_*_field methods
*
* All add_*_field methods follow these common patterns:
*
* @param total_size Reference to the total message size to update
* @param field_id_size Pre-calculated size of the field ID in bytes
* @param value The value to calculate size for (type varies)
* @param force Whether to calculate size even if the value is default/zero/empty
*
* Each method follows this implementation pattern:
* 1. Skip calculation if value is default (0, false, empty) and not forced
* 2. Calculate the size based on the field's encoding rules
* 3. Add the field_id_size + calculated value size to total_size
*/
/**
* @brief Calculates and adds the size of an int32 field to the total message size
*/
static inline void add_int32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
}
// Calculate and directly add to total_size
if (value < 0) {
// Negative values are encoded as 10-byte varints in protobuf
total_size += field_id_size + 10;
} else {
// For non-negative values, use the standard varint size
total_size += field_id_size + varint(static_cast<uint32_t>(value));
}
}
/**
* @brief Calculates and adds the size of an int32 field to the total message size (repeated field version)
*/
static inline void add_int32_field_repeated(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
// Always calculate size for repeated fields
if (value < 0) {
// Negative values are encoded as 10-byte varints in protobuf
total_size += field_id_size + 10;
} else {
// For non-negative values, use the standard varint size
total_size += field_id_size + varint(static_cast<uint32_t>(value));
}
}
/**
* @brief Calculates and adds the size of a uint32 field to the total message size
*/
static inline void add_uint32_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
}
// Calculate and directly add to total_size
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a uint32 field to the total message size (repeated field version)
*/
static inline void add_uint32_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
// Always calculate size for repeated fields
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a boolean field to the total message size
*/
static inline void add_bool_field(uint32_t &total_size, uint32_t field_id_size, bool value) {
// Skip calculation if value is false
if (!value) {
return; // No need to update total_size
}
// Boolean fields always use 1 byte when true
total_size += field_id_size + 1;
}
/**
* @brief Calculates and adds the size of a boolean field to the total message size (repeated field version)
*/
static inline void add_bool_field_repeated(uint32_t &total_size, uint32_t field_id_size, bool value) {
// Always calculate size for repeated fields
// Boolean fields always use 1 byte
total_size += field_id_size + 1;
}
/**
* @brief Calculates and adds the size of a fixed field to the total message size
*
* Fixed fields always take exactly N bytes (4 for fixed32/float, 8 for fixed64/double).
*
* @tparam NumBytes The number of bytes for this fixed field (4 or 8)
* @param is_nonzero Whether the value is non-zero
*/
template<uint32_t NumBytes>
static inline void add_fixed_field(uint32_t &total_size, uint32_t field_id_size, bool is_nonzero) {
// Skip calculation if value is zero
if (!is_nonzero) {
return; // No need to update total_size
}
// Fixed fields always take exactly NumBytes
total_size += field_id_size + NumBytes;
}
/**
* @brief Calculates and adds the size of an enum field to the total message size
*
* Enum fields are encoded as uint32 varints.
*/
static inline void add_enum_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
}
// Enums are encoded as uint32
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of an enum field to the total message size (repeated field version)
*
* Enum fields are encoded as uint32 varints.
*/
static inline void add_enum_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
// Always calculate size for repeated fields
// Enums are encoded as uint32
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a sint32 field to the total message size
*
* Sint32 fields use ZigZag encoding, which is more efficient for negative values.
*/
static inline void add_sint32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
}
// ZigZag encoding for sint32: (n << 1) ^ (n >> 31)
uint32_t zigzag = (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
total_size += field_id_size + varint(zigzag);
}
/**
* @brief Calculates and adds the size of a sint32 field to the total message size (repeated field version)
*
* Sint32 fields use ZigZag encoding, which is more efficient for negative values.
*/
static inline void add_sint32_field_repeated(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
// Always calculate size for repeated fields
// ZigZag encoding for sint32: (n << 1) ^ (n >> 31)
uint32_t zigzag = (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
total_size += field_id_size + varint(zigzag);
}
/**
* @brief Calculates and adds the size of an int64 field to the total message size
*/
static inline void add_int64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
}
// Calculate and directly add to total_size
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of an int64 field to the total message size (repeated field version)
*/
static inline void add_int64_field_repeated(uint32_t &total_size, uint32_t field_id_size, int64_t value) {
// Always calculate size for repeated fields
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a uint64 field to the total message size
*/
static inline void add_uint64_field(uint32_t &total_size, uint32_t field_id_size, uint64_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
}
// Calculate and directly add to total_size
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a uint64 field to the total message size (repeated field version)
*/
static inline void add_uint64_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint64_t value) {
// Always calculate size for repeated fields
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a sint64 field to the total message size
*
* Sint64 fields use ZigZag encoding, which is more efficient for negative values.
*/
static inline void add_sint64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
}
// ZigZag encoding for sint64: (n << 1) ^ (n >> 63)
uint64_t zigzag = (static_cast<uint64_t>(value) << 1) ^ (static_cast<uint64_t>(value >> 63));
total_size += field_id_size + varint(zigzag);
}
/**
* @brief Calculates and adds the size of a sint64 field to the total message size (repeated field version)
*
* Sint64 fields use ZigZag encoding, which is more efficient for negative values.
*/
static inline void add_sint64_field_repeated(uint32_t &total_size, uint32_t field_id_size, int64_t value) {
// Always calculate size for repeated fields
// ZigZag encoding for sint64: (n << 1) ^ (n >> 63)
uint64_t zigzag = (static_cast<uint64_t>(value) << 1) ^ (static_cast<uint64_t>(value >> 63));
total_size += field_id_size + varint(zigzag);
}
/**
* @brief Calculates and adds the size of a string/bytes field to the total message size
*/
static inline void add_string_field(uint32_t &total_size, uint32_t field_id_size, const std::string &str) {
// Skip calculation if string is empty
if (str.empty()) {
return; // No need to update total_size
}
// Calculate and directly add to total_size
const uint32_t str_size = static_cast<uint32_t>(str.size());
total_size += field_id_size + varint(str_size) + str_size;
}
/**
* @brief Calculates and adds the size of a string/bytes field to the total message size (repeated field version)
*/
static inline void add_string_field_repeated(uint32_t &total_size, uint32_t field_id_size, const std::string &str) {
// Always calculate size for repeated fields
const uint32_t str_size = static_cast<uint32_t>(str.size());
total_size += field_id_size + varint(str_size) + str_size;
}
/**
* @brief Calculates and adds the size of a nested message field to the total message size
*
* This helper function directly updates the total_size reference if the nested size
* is greater than zero.
*
* @param nested_size The pre-calculated size of the nested message
*/
static inline void add_message_field(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size) {
// Skip calculation if nested message is empty
if (nested_size == 0) {
return; // No need to update total_size
}
// Calculate and directly add to total_size
// Field ID + length varint + nested message content
total_size += field_id_size + varint(nested_size) + nested_size;
}
/**
* @brief Calculates and adds the size of a nested message field to the total message size (repeated field version)
*
* @param nested_size The pre-calculated size of the nested message
*/
static inline void add_message_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size) {
// Always calculate size for repeated fields
// Field ID + length varint + nested message content
total_size += field_id_size + varint(nested_size) + nested_size;
}
/**
* @brief Calculates and adds the size of a nested message field to the total message size
*
* This version takes a ProtoMessage object, calculates its size internally,
* and updates the total_size reference. This eliminates the need for a temporary variable
* at the call site.
*
* @param message The nested message object
*/
static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const ProtoMessage &message) {
uint32_t nested_size = 0;
message.calculate_size(nested_size);
// Use the base implementation with the calculated nested_size
add_message_field(total_size, field_id_size, nested_size);
}
/**
* @brief Calculates and adds the size of a nested message field to the total message size (repeated field version)
*
* @param message The nested message object
*/
static inline void add_message_object_repeated(uint32_t &total_size, uint32_t field_id_size,
const ProtoMessage &message) {
uint32_t nested_size = 0;
message.calculate_size(nested_size);
// Use the base implementation with the calculated nested_size
add_message_field_repeated(total_size, field_id_size, nested_size);
}
/**
* @brief Calculates and adds the sizes of all messages in a repeated field to the total message size
*
* This helper processes a vector of message objects, calculating the size for each message
* and adding it to the total size.
*
* @tparam MessageType The type of the nested messages in the vector
* @param messages Vector of message objects
*/
template<typename MessageType>
static inline void add_repeated_message(uint32_t &total_size, uint32_t field_id_size,
const std::vector<MessageType> &messages) {
// Skip if the vector is empty
if (messages.empty()) {
return;
}
// Use the repeated field version for all messages
for (const auto &message : messages) {
add_message_object_repeated(total_size, field_id_size, message);
}
}
};
// Implementation of encode_message - must be after ProtoMessage is defined
inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessage &value, bool force) {
this->encode_field_raw(field_id, 2); // type 2: Length-delimited message
// Calculate the message size first
uint32_t msg_length_bytes = 0;
value.calculate_size(msg_length_bytes);
// Calculate how many bytes the length varint needs
uint32_t varint_length_bytes = ProtoSize::varint(msg_length_bytes);
// Reserve exact space for the length varint
size_t begin = this->buffer_->size();
this->buffer_->resize(this->buffer_->size() + varint_length_bytes);
// Write the length varint directly
ProtoVarInt(msg_length_bytes).encode_to_buffer_unchecked(this->buffer_->data() + begin, varint_length_bytes);
// Now encode the message content - it will append to the buffer
value.encode(*this);
// Verify that the encoded size matches what we calculated
assert(this->buffer_->size() == begin + varint_length_bytes + msg_length_bytes);
}
// Implementation of decode_to_message - must be after ProtoMessage is defined
inline void ProtoLengthDelimited::decode_to_message(ProtoMessage &msg) const {
msg.decode(this->value_, this->length_);
}
template<typename T> const char *proto_enum_to_string(T value);
class ProtoService {
@ -838,51 +298,15 @@ class ProtoService {
virtual void on_fatal_error() = 0;
virtual void on_unauthenticated_access() = 0;
virtual void on_no_setup_connection() = 0;
/**
* Create a buffer with a reserved size.
* @param reserve_size The number of bytes to pre-allocate in the buffer. This is a hint
* to optimize memory usage and avoid reallocations during encoding.
* Implementations should aim to allocate at least this size.
* @return A ProtoWriteBuffer object with the reserved size.
*/
virtual ProtoWriteBuffer create_buffer(uint32_t reserve_size) = 0;
virtual bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) = 0;
virtual void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0;
virtual ProtoWriteBuffer create_buffer() = 0;
virtual bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) = 0;
virtual bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0;
// Optimized method that pre-allocates buffer based on message size
bool send_message_(const ProtoMessage &msg, uint8_t message_type) {
uint32_t msg_size = 0;
msg.calculate_size(msg_size);
// Create a pre-sized buffer
auto buffer = this->create_buffer(msg_size);
// Encode message into the buffer
template<class C> bool send_message_(const C &msg, uint32_t message_type) {
auto buffer = this->create_buffer();
msg.encode(buffer);
// Send the buffer
return this->send_buffer(buffer, message_type);
}
// Authentication helper methods
bool check_connection_setup_() {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return false;
}
return true;
}
bool check_authenticated_() {
if (!this->check_connection_setup_()) {
return false;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return false;
}
return true;
}
};
} // namespace api

View File

@ -6,67 +6,81 @@
namespace esphome {
namespace api {
// Generate entity handler implementations using macros
#ifdef USE_BINARY_SENSOR
INITIAL_STATE_HANDLER(binary_sensor, binary_sensor::BinarySensor)
bool InitialStateIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
return this->client_->send_binary_sensor_state(binary_sensor, binary_sensor->state);
}
#endif
#ifdef USE_COVER
INITIAL_STATE_HANDLER(cover, cover::Cover)
bool InitialStateIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_state(cover); }
#endif
#ifdef USE_FAN
INITIAL_STATE_HANDLER(fan, fan::Fan)
bool InitialStateIterator::on_fan(fan::Fan *fan) { return this->client_->send_fan_state(fan); }
#endif
#ifdef USE_LIGHT
INITIAL_STATE_HANDLER(light, light::LightState)
bool InitialStateIterator::on_light(light::LightState *light) { return this->client_->send_light_state(light); }
#endif
#ifdef USE_SENSOR
INITIAL_STATE_HANDLER(sensor, sensor::Sensor)
bool InitialStateIterator::on_sensor(sensor::Sensor *sensor) {
return this->client_->send_sensor_state(sensor, sensor->state);
}
#endif
#ifdef USE_SWITCH
INITIAL_STATE_HANDLER(switch, switch_::Switch)
bool InitialStateIterator::on_switch(switch_::Switch *a_switch) {
return this->client_->send_switch_state(a_switch, a_switch->state);
}
#endif
#ifdef USE_TEXT_SENSOR
INITIAL_STATE_HANDLER(text_sensor, text_sensor::TextSensor)
bool InitialStateIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
return this->client_->send_text_sensor_state(text_sensor, text_sensor->state);
}
#endif
#ifdef USE_CLIMATE
INITIAL_STATE_HANDLER(climate, climate::Climate)
bool InitialStateIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_state(climate); }
#endif
#ifdef USE_NUMBER
INITIAL_STATE_HANDLER(number, number::Number)
bool InitialStateIterator::on_number(number::Number *number) {
return this->client_->send_number_state(number, number->state);
}
#endif
#ifdef USE_DATETIME_DATE
INITIAL_STATE_HANDLER(date, datetime::DateEntity)
bool InitialStateIterator::on_date(datetime::DateEntity *date) { return this->client_->send_date_state(date); }
#endif
#ifdef USE_DATETIME_TIME
INITIAL_STATE_HANDLER(time, datetime::TimeEntity)
bool InitialStateIterator::on_time(datetime::TimeEntity *time) { return this->client_->send_time_state(time); }
#endif
#ifdef USE_DATETIME_DATETIME
INITIAL_STATE_HANDLER(datetime, datetime::DateTimeEntity)
bool InitialStateIterator::on_datetime(datetime::DateTimeEntity *datetime) {
return this->client_->send_datetime_state(datetime);
}
#endif
#ifdef USE_TEXT
INITIAL_STATE_HANDLER(text, text::Text)
bool InitialStateIterator::on_text(text::Text *text) { return this->client_->send_text_state(text, text->state); }
#endif
#ifdef USE_SELECT
INITIAL_STATE_HANDLER(select, select::Select)
bool InitialStateIterator::on_select(select::Select *select) {
return this->client_->send_select_state(select, select->state);
}
#endif
#ifdef USE_LOCK
INITIAL_STATE_HANDLER(lock, lock::Lock)
bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock, a_lock->state); }
#endif
#ifdef USE_VALVE
INITIAL_STATE_HANDLER(valve, valve::Valve)
bool InitialStateIterator::on_valve(valve::Valve *valve) { return this->client_->send_valve_state(valve); }
#endif
#ifdef USE_MEDIA_PLAYER
INITIAL_STATE_HANDLER(media_player, media_player::MediaPlayer)
bool InitialStateIterator::on_media_player(media_player::MediaPlayer *media_player) {
return this->client_->send_media_player_state(media_player);
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
INITIAL_STATE_HANDLER(alarm_control_panel, alarm_control_panel::AlarmControlPanel)
bool InitialStateIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
return this->client_->send_alarm_control_panel_state(a_alarm_control_panel);
}
#endif
#ifdef USE_UPDATE
INITIAL_STATE_HANDLER(update, update::UpdateEntity)
bool InitialStateIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_state(update); }
#endif
// Special cases (button and event) are already defined inline in subscribe_state.h
InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {}
} // namespace api

View File

@ -10,78 +10,71 @@ namespace api {
class APIConnection;
// Macro for generating InitialStateIterator handlers
// Calls send_*_state
#define INITIAL_STATE_HANDLER(entity_type, EntityClass) \
bool InitialStateIterator::on_##entity_type(EntityClass *entity) { /* NOLINT(bugprone-macro-parentheses) */ \
return this->client_->send_##entity_type##_state(entity); \
}
class InitialStateIterator : public ComponentIterator {
public:
InitialStateIterator(APIConnection *client);
#ifdef USE_BINARY_SENSOR
bool on_binary_sensor(binary_sensor::BinarySensor *entity) override;
bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override;
#endif
#ifdef USE_COVER
bool on_cover(cover::Cover *entity) override;
bool on_cover(cover::Cover *cover) override;
#endif
#ifdef USE_FAN
bool on_fan(fan::Fan *entity) override;
bool on_fan(fan::Fan *fan) override;
#endif
#ifdef USE_LIGHT
bool on_light(light::LightState *entity) override;
bool on_light(light::LightState *light) override;
#endif
#ifdef USE_SENSOR
bool on_sensor(sensor::Sensor *entity) override;
bool on_sensor(sensor::Sensor *sensor) override;
#endif
#ifdef USE_SWITCH
bool on_switch(switch_::Switch *entity) override;
bool on_switch(switch_::Switch *a_switch) override;
#endif
#ifdef USE_BUTTON
bool on_button(button::Button *button) override { return true; };
#endif
#ifdef USE_TEXT_SENSOR
bool on_text_sensor(text_sensor::TextSensor *entity) override;
bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
#endif
#ifdef USE_CLIMATE
bool on_climate(climate::Climate *entity) override;
bool on_climate(climate::Climate *climate) override;
#endif
#ifdef USE_NUMBER
bool on_number(number::Number *entity) override;
bool on_number(number::Number *number) override;
#endif
#ifdef USE_DATETIME_DATE
bool on_date(datetime::DateEntity *entity) override;
bool on_date(datetime::DateEntity *date) override;
#endif
#ifdef USE_DATETIME_TIME
bool on_time(datetime::TimeEntity *entity) override;
bool on_time(datetime::TimeEntity *time) override;
#endif
#ifdef USE_DATETIME_DATETIME
bool on_datetime(datetime::DateTimeEntity *entity) override;
bool on_datetime(datetime::DateTimeEntity *datetime) override;
#endif
#ifdef USE_TEXT
bool on_text(text::Text *entity) override;
bool on_text(text::Text *text) override;
#endif
#ifdef USE_SELECT
bool on_select(select::Select *entity) override;
bool on_select(select::Select *select) override;
#endif
#ifdef USE_LOCK
bool on_lock(lock::Lock *entity) override;
bool on_lock(lock::Lock *a_lock) override;
#endif
#ifdef USE_VALVE
bool on_valve(valve::Valve *entity) override;
bool on_valve(valve::Valve *valve) override;
#endif
#ifdef USE_MEDIA_PLAYER
bool on_media_player(media_player::MediaPlayer *entity) override;
bool on_media_player(media_player::MediaPlayer *media_player) override;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *entity) override;
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override;
#endif
#ifdef USE_EVENT
bool on_event(event::Event *event) override { return true; };
#endif
#ifdef USE_UPDATE
bool on_update(update::UpdateEntity *entity) override;
bool on_update(update::UpdateEntity *update) override;
#endif
bool completed() { return this->state_ == IteratorState::NONE; }

View File

@ -7,7 +7,6 @@
#include "esphome/core/automation.h"
#include "api_pb2.h"
#ifdef USE_API_SERVICES
namespace esphome {
namespace api {
@ -74,4 +73,3 @@ template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...
} // namespace api
} // namespace esphome
#endif // USE_API_SERVICES

View File

@ -7,7 +7,7 @@ namespace as3935 {
static const char *const TAG = "as3935";
void AS3935Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up AS3935...");
this->irq_pin_->setup();
LOG_PIN(" IRQ Pin: ", this->irq_pin_);
@ -282,7 +282,7 @@ void AS3935Component::display_oscillator(bool state, uint8_t osc) {
// based on the resonance frequency of the antenna and so it should be trimmed
// before the calibration is done.
bool AS3935Component::calibrate_oscillator() {
ESP_LOGI(TAG, "Starting oscillators calibration");
ESP_LOGI(TAG, "Starting oscillators calibration...");
this->write_register(CALIB_RCO, WIPE_ALL, DIRECT_COMMAND, 0); // Send command to calibrate the oscillators
this->display_oscillator(true, 2);
@ -307,7 +307,7 @@ bool AS3935Component::calibrate_oscillator() {
}
void AS3935Component::tune_antenna() {
ESP_LOGI(TAG, "Starting antenna tuning");
ESP_LOGI(TAG, "Starting antenna tuning...");
uint8_t div_ratio = this->read_div_ratio();
uint8_t tune_val = this->read_capacitance();
ESP_LOGI(TAG, "Division Ratio is set to: %d", div_ratio);

View File

@ -1,7 +1,10 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/as3935/as3935.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
namespace esphome {
namespace as3935_i2c {

View File

@ -23,7 +23,7 @@ static const uint8_t REGISTER_AGC = 0x1A; // 8 bytes / R
static const uint8_t REGISTER_MAGNITUDE = 0x1B; // 16 bytes / R
void AS5600Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
ESP_LOGCONFIG(TAG, "Setting up AS5600...");
if (!this->read_byte(REGISTER_STATUS).has_value()) {
this->mark_failed();
@ -91,17 +91,15 @@ void AS5600Component::dump_config() {
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
ESP_LOGE(TAG, "Communication with AS5600 failed!");
return;
}
ESP_LOGCONFIG(TAG,
" Watchdog: %d\n"
" Fast Filter: %d\n"
" Slow Filter: %d\n"
" Hysteresis: %d\n"
" Start Position: %d",
this->watchdog_, this->fast_filter_, this->slow_filter_, this->hysteresis_, this->start_position_);
ESP_LOGCONFIG(TAG, " Watchdog: %d", this->watchdog_);
ESP_LOGCONFIG(TAG, " Fast Filter: %d", this->fast_filter_);
ESP_LOGCONFIG(TAG, " Slow Filter: %d", this->slow_filter_);
ESP_LOGCONFIG(TAG, " Hysteresis: %d", this->hysteresis_);
ESP_LOGCONFIG(TAG, " Start Position: %d", this->start_position_);
if (this->end_mode_ == END_MODE_POSITION) {
ESP_LOGCONFIG(TAG, " End Position: %d", this->end_position_);
} else {

View File

@ -50,6 +50,7 @@ class AS5600Component : public Component, public i2c::I2CDevice {
void setup() override;
void dump_config() override;
/// HARDWARE_LATE setup priority
float get_setup_priority() const override { return setup_priority::DATA; }
// configuration setters
void set_dir_pin(InternalGPIOPin *pin) { this->dir_pin_ = pin; }

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