Compare commits

..

18 Commits

Author SHA1 Message Date
J. Nick Koston
45e08ed584 Merge branch 'improve_ble_batching' into loop_runtime_stats_ble_batching 2025-05-13 11:41:30 -05:00
J. Nick Koston
a7449dce92 Improve batching of BLE advertisements for better airtime efficiency 2025-05-13 11:31:48 -05:00
J. Nick Koston
8067caf16f preen 2025-05-13 11:30:26 -05:00
J. Nick Koston
5fbb066ee7 preen 2025-05-13 11:30:06 -05:00
J. Nick Koston
c9680a1ccb preen 2025-05-13 11:29:22 -05:00
J. Nick Koston
03399e6dd6 preen 2025-05-13 11:22:33 -05:00
J. Nick Koston
7f838ece00 preen 2025-05-13 11:22:05 -05:00
J. Nick Koston
3f87010c0e preen 2025-05-13 11:21:32 -05:00
J. Nick Koston
a960d9966d preen 2025-05-13 03:55:08 -05:00
J. Nick Koston
02c390c6c3 tweak 2025-05-13 03:51:22 -05:00
J. Nick Koston
eebefdf026 preen 2025-05-13 03:38:42 -05:00
J. Nick Koston
cb748bbb02 preen 2025-05-13 03:32:57 -05:00
J. Nick Koston
c35db19995 preen 2025-05-13 03:30:19 -05:00
J. Nick Koston
71b493bd8b its too much 2025-05-13 03:25:49 -05:00
J. Nick Koston
f67e02c653 its too much 2025-05-13 03:24:48 -05:00
J. Nick Koston
9db52b17f2 its too much 2025-05-13 03:24:36 -05:00
J. Nick Koston
d728382542 its too much 2025-05-13 03:24:07 -05:00
J. Nick Koston
d95bbfc6c4 its too much 2025-05-13 03:00:38 -05:00
1459 changed files with 13422 additions and 39392 deletions

View File

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

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", "name": "ESPHome Dev",
"context": "..", "image": "ghcr.io/esphome/esphome-lint:dev",
"dockerFile": "Dockerfile",
"postCreateCommand": [ "postCreateCommand": [
"script/devcontainer-post-create" "script/devcontainer-post-create"
], ],
"features": { "containerEnv": {
"ghcr.io/devcontainers/features/github-cli:1": {} "DEVCONTAINER": "1",
"PIP_BREAK_SYSTEM_PACKAGES": "1",
"PIP_ROOT_USER_ACTION": "ignore"
}, },
"runArgs": [ "runArgs": [
"--privileged", "--privileged",
"-e", "-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 // uncomment and edit the path in order to pass though local USB serial to the conatiner
// , "--device=/dev/ttyACM0" // , "--device=/dev/ttyACM0"
], ],

View File

@@ -47,7 +47,7 @@ runs:
- name: Build and push to ghcr by digest - name: Build and push to ghcr by digest
id: build-ghcr id: build-ghcr
uses: docker/build-push-action@v6.18.0 uses: docker/build-push-action@v6.16.0
env: env:
DOCKER_BUILD_SUMMARY: false DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false DOCKER_BUILD_RECORD_UPLOAD: false
@@ -73,7 +73,7 @@ runs:
- name: Build and push to dockerhub by digest - name: Build and push to dockerhub by digest
id: build-dockerhub id: build-dockerhub
uses: docker/build-push-action@v6.18.0 uses: docker/build-push-action@v6.16.0
env: env:
DOCKER_BUILD_SUMMARY: false DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false DOCKER_BUILD_RECORD_UPLOAD: false

View File

@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.1.7
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5.6.0 uses: actions/setup-python@v5.6.0
with: with:

View File

@@ -43,13 +43,13 @@ jobs:
- "docker" - "docker"
# - "lint" # - "lint"
steps: steps:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.1.7
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5.6.0 uses: actions/setup-python@v5.6.0
with: with:
python-version: "3.10" python-version: "3.9"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.11.1 uses: docker/setup-buildx-action@v3.10.0
- name: Set TAG - name: Set TAG
run: | run: |

View File

@@ -20,8 +20,8 @@ permissions:
contents: read contents: read
env: env:
DEFAULT_PYTHON: "3.10" DEFAULT_PYTHON: "3.9"
PYUPGRADE_TARGET: "--py310-plus" PYUPGRADE_TARGET: "--py39-plus"
concurrency: concurrency:
# yamllint disable-line rule:line-length # yamllint disable-line rule:line-length
@@ -36,7 +36,7 @@ jobs:
cache-key: ${{ steps.cache-key.outputs.key }} cache-key: ${{ steps.cache-key.outputs.key }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.1.7
- name: Generate cache-key - name: Generate cache-key
id: cache-key id: cache-key
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
@@ -68,7 +68,7 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.1.7
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
@@ -89,7 +89,7 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.1.7
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
@@ -110,7 +110,7 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.1.7
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
@@ -131,7 +131,7 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.1.7
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
@@ -152,7 +152,7 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.1.7
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
@@ -173,10 +173,10 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: python-version:
- "3.9"
- "3.10" - "3.10"
- "3.11" - "3.11"
- "3.12" - "3.12"
- "3.13"
os: os:
- ubuntu-latest - ubuntu-latest
- macOS-latest - macOS-latest
@@ -185,24 +185,24 @@ jobs:
# Minimize CI resource usage # Minimize CI resource usage
# by only running the Python version # by only running the Python version
# version used for docker images on Windows and macOS # version used for docker images on Windows and macOS
- python-version: "3.13"
os: windows-latest
- python-version: "3.12" - python-version: "3.12"
os: windows-latest os: windows-latest
- python-version: "3.10" - python-version: "3.10"
os: windows-latest os: windows-latest
- python-version: "3.13" - python-version: "3.9"
os: macOS-latest os: windows-latest
- python-version: "3.12" - python-version: "3.12"
os: macOS-latest os: macOS-latest
- python-version: "3.10" - python-version: "3.10"
os: macOS-latest os: macOS-latest
- python-version: "3.9"
os: macOS-latest
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
needs: needs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.1.7
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
@@ -214,14 +214,14 @@ jobs:
if: matrix.os == 'windows-latest' if: matrix.os == 'windows-latest'
run: | run: |
./venv/Scripts/activate ./venv/Scripts/activate
pytest -vv --cov-report=xml --tb=native -n auto tests pytest -vv --cov-report=xml --tb=native tests
- name: Run pytest - name: Run pytest
if: matrix.os == 'ubuntu-latest' || matrix.os == 'macOS-latest' if: matrix.os == 'ubuntu-latest' || matrix.os == 'macOS-latest'
run: | run: |
. venv/bin/activate . venv/bin/activate
pytest -vv --cov-report=xml --tb=native -n auto tests pytest -vv --cov-report=xml --tb=native tests
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@v5.4.3 uses: codecov/codecov-action@v5.4.2
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
@@ -232,7 +232,7 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.1.7
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
@@ -296,11 +296,11 @@ jobs:
name: Run script/clang-tidy for ZEPHYR name: Run script/clang-tidy for ZEPHYR
options: --environment nrf52-tidy --grep USE_ZEPHYR options: --environment nrf52-tidy --grep USE_ZEPHYR
pio_cache_key: tidy-zephyr pio_cache_key: tidy-zephyr
ignore_errors: false ignore_errors: true
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.1.7
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
@@ -356,7 +356,7 @@ jobs:
count: ${{ steps.list-components.outputs.count }} count: ${{ steps.list-components.outputs.count }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.1.7
with: with:
# Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works. # Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works.
fetch-depth: 500 fetch-depth: 500
@@ -406,7 +406,7 @@ jobs:
sudo apt-get install libsdl2-dev sudo apt-get install libsdl2-dev
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.1.7
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
@@ -432,7 +432,7 @@ jobs:
matrix: ${{ steps.split.outputs.components }} matrix: ${{ steps.split.outputs.components }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.1.7
- name: Split components into 20 groups - name: Split components into 20 groups
id: split id: split
run: | run: |
@@ -462,7 +462,7 @@ jobs:
sudo apt-get install libsdl2-dev sudo apt-get install libsdl2-dev
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.1.7
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:

View File

@@ -1,11 +1,28 @@
--- ---
name: Lock closed issues and PRs name: Lock
on: on:
schedule: schedule:
- cron: "30 0 * * *" # Run daily at 00:30 UTC - cron: "30 0 * * *"
workflow_dispatch: workflow_dispatch:
permissions:
issues: write
pull-requests: write
concurrency:
group: lock
jobs: jobs:
lock: 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: outputs:
tag: ${{ steps.tag.outputs.tag }} tag: ${{ steps.tag.outputs.tag }}
branch_build: ${{ steps.tag.outputs.branch_build }} branch_build: ${{ steps.tag.outputs.branch_build }}
deploy_env: ${{ steps.tag.outputs.deploy_env }}
steps: steps:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.1.7
- name: Get tag - name: Get tag
id: tag id: tag
# yamllint disable rule:line-length # yamllint disable rule:line-length
@@ -28,11 +27,6 @@ jobs:
if [[ "${{ github.event_name }}" = "release" ]]; then if [[ "${{ github.event_name }}" = "release" ]]; then
TAG="${{ github.event.release.tag_name}}" TAG="${{ github.event.release.tag_name}}"
BRANCH_BUILD="false" BRANCH_BUILD="false"
if [[ "${{ github.event.release.prerelease }}" = "true" ]]; then
ENVIRONMENT="beta"
else
ENVIRONMENT="production"
fi
else else
TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p") TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p")
today="$(date --utc '+%Y%m%d')" today="$(date --utc '+%Y%m%d')"
@@ -41,15 +35,12 @@ jobs:
if [[ "$BRANCH" != "dev" ]]; then if [[ "$BRANCH" != "dev" ]]; then
TAG="${TAG}-${BRANCH}" TAG="${TAG}-${BRANCH}"
BRANCH_BUILD="true" BRANCH_BUILD="true"
ENVIRONMENT=""
else else
BRANCH_BUILD="false" BRANCH_BUILD="false"
ENVIRONMENT="dev"
fi fi
fi fi
echo "tag=${TAG}" >> $GITHUB_OUTPUT echo "tag=${TAG}" >> $GITHUB_OUTPUT
echo "branch_build=${BRANCH_BUILD}" >> $GITHUB_OUTPUT echo "branch_build=${BRANCH_BUILD}" >> $GITHUB_OUTPUT
echo "deploy_env=${ENVIRONMENT}" >> $GITHUB_OUTPUT
# yamllint enable rule:line-length # yamllint enable rule:line-length
deploy-pypi: deploy-pypi:
@@ -60,19 +51,21 @@ jobs:
contents: read contents: read
id-token: write id-token: write
steps: steps:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.1.7
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5.6.0 uses: actions/setup-python@v5.6.0
with: with:
python-version: "3.x" python-version: "3.x"
- name: Set up python environment
env:
ESPHOME_NO_VENV: 1
run: script/setup
- name: Build - name: Build
run: |- run: |-
pip3 install build pip3 install build
python3 -m build python3 -m build
- name: Publish - name: Publish
uses: pypa/gh-action-pypi-publish@v1.12.4 uses: pypa/gh-action-pypi-publish@v1.12.4
with:
skip-existing: true
deploy-docker: deploy-docker:
name: Build ESPHome ${{ matrix.platform.arch }} name: Build ESPHome ${{ matrix.platform.arch }}
@@ -92,14 +85,14 @@ jobs:
os: "ubuntu-24.04-arm" os: "ubuntu-24.04-arm"
steps: steps:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.1.7
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5.6.0 uses: actions/setup-python@v5.6.0
with: with:
python-version: "3.10" python-version: "3.9"
- name: Set up Docker Buildx - 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 - name: Log in to docker hub
uses: docker/login-action@v3.4.0 uses: docker/login-action@v3.4.0
@@ -168,7 +161,7 @@ jobs:
- ghcr - ghcr
- dockerhub - dockerhub
steps: steps:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.1.7
- name: Download digests - name: Download digests
uses: actions/download-artifact@v4.3.0 uses: actions/download-artifact@v4.3.0
@@ -178,7 +171,7 @@ jobs:
merge-multiple: true merge-multiple: true
- name: Set up Docker Buildx - 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 - name: Log in to docker hub
if: matrix.registry == 'dockerhub' if: matrix.registry == 'dockerhub'
@@ -242,8 +235,9 @@ jobs:
deploy-esphome-schema: deploy-esphome-schema:
if: github.repository == 'esphome/esphome' && needs.init.outputs.branch_build == 'false' if: github.repository == 'esphome/esphome' && needs.init.outputs.branch_build == 'false'
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [init] needs:
environment: ${{ needs.init.outputs.deploy_env }} - init
- deploy-manifest
steps: steps:
- name: Trigger Workflow - name: Trigger Workflow
uses: actions/github-script@v7.0.1 uses: actions/github-script@v7.0.1

View File

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

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.1.7
- name: Run yamllint - name: Run yamllint
uses: frenck/action-yamllint@v1.5.0 uses: frenck/action-yamllint@v1.5.0
with: with:

1
.gitignore vendored
View File

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

View File

@@ -4,7 +4,7 @@
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version. # Ruff version.
rev: v0.12.1 rev: v0.11.9
hooks: hooks:
# Run the linter. # Run the linter.
- id: ruff - id: ruff
@@ -12,7 +12,7 @@ repos:
# Run the formatter. # Run the formatter.
- id: ruff-format - id: ruff-format
- repo: https://github.com/PyCQA/flake8 - repo: https://github.com/PyCQA/flake8
rev: 7.3.0 rev: 7.2.0
hooks: hooks:
- id: flake8 - id: flake8
additional_dependencies: additional_dependencies:
@@ -28,10 +28,10 @@ repos:
- --branch=release - --branch=release
- --branch=beta - --branch=beta
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v3.20.0 rev: v3.15.2
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py310-plus] args: [--py39-plus]
- repo: https://github.com/adrienverge/yamllint.git - repo: https://github.com/adrienverge/yamllint.git
rev: v1.37.1 rev: v1.37.1
hooks: hooks:

View File

@@ -96,7 +96,6 @@ esphome/components/ch422g/* @clydebarrow @jesterret
esphome/components/chsc6x/* @kkosik20 esphome/components/chsc6x/* @kkosik20
esphome/components/climate/* @esphome/core esphome/components/climate/* @esphome/core
esphome/components/climate_ir/* @glmnet esphome/components/climate_ir/* @glmnet
esphome/components/cm1106/* @andrewjswan
esphome/components/color_temperature/* @jesserockz esphome/components/color_temperature/* @jesserockz
esphome/components/combination/* @Cat-Ion @kahrendt esphome/components/combination/* @Cat-Ion @kahrendt
esphome/components/const/* @esphome/core esphome/components/const/* @esphome/core
@@ -139,19 +138,16 @@ esphome/components/es7210/* @kahrendt
esphome/components/es7243e/* @kbx81 esphome/components/es7243e/* @kbx81
esphome/components/es8156/* @kbx81 esphome/components/es8156/* @kbx81
esphome/components/es8311/* @kahrendt @kroimon esphome/components/es8311/* @kahrendt @kroimon
esphome/components/es8388/* @P4uLT
esphome/components/esp32/* @esphome/core esphome/components/esp32/* @esphome/core
esphome/components/esp32_ble/* @Rapsssito @jesserockz esphome/components/esp32_ble/* @Rapsssito @jesserockz
esphome/components/esp32_ble_client/* @jesserockz esphome/components/esp32_ble_client/* @jesserockz
esphome/components/esp32_ble_server/* @Rapsssito @clydebarrow @jesserockz esphome/components/esp32_ble_server/* @Rapsssito @clydebarrow @jesserockz
esphome/components/esp32_camera_web_server/* @ayufan esphome/components/esp32_camera_web_server/* @ayufan
esphome/components/esp32_can/* @Sympatron esphome/components/esp32_can/* @Sympatron
esphome/components/esp32_hosted/* @swoboda1337
esphome/components/esp32_improv/* @jesserockz esphome/components/esp32_improv/* @jesserockz
esphome/components/esp32_rmt/* @jesserockz esphome/components/esp32_rmt/* @jesserockz
esphome/components/esp32_rmt_led_strip/* @jesserockz esphome/components/esp32_rmt_led_strip/* @jesserockz
esphome/components/esp8266/* @esphome/core esphome/components/esp8266/* @esphome/core
esphome/components/esp_ldo/* @clydebarrow
esphome/components/ethernet_info/* @gtjadsonsantos esphome/components/ethernet_info/* @gtjadsonsantos
esphome/components/event/* @nohat esphome/components/event/* @nohat
esphome/components/event_emitter/* @Rapsssito esphome/components/event_emitter/* @Rapsssito
@@ -173,7 +169,7 @@ esphome/components/gp2y1010au0f/* @zry98
esphome/components/gp8403/* @jesserockz esphome/components/gp8403/* @jesserockz
esphome/components/gpio/* @esphome/core esphome/components/gpio/* @esphome/core
esphome/components/gpio/one_wire/* @ssieb esphome/components/gpio/one_wire/* @ssieb
esphome/components/gps/* @coogle @ximex esphome/components/gps/* @coogle
esphome/components/graph/* @synco esphome/components/graph/* @synco
esphome/components/graphical_display_menu/* @MrMDavidson esphome/components/graphical_display_menu/* @MrMDavidson
esphome/components/gree/* @orestismers esphome/components/gree/* @orestismers
@@ -237,7 +233,6 @@ esphome/components/kamstrup_kmp/* @cfeenstra1024
esphome/components/key_collector/* @ssieb esphome/components/key_collector/* @ssieb
esphome/components/key_provider/* @ssieb esphome/components/key_provider/* @ssieb
esphome/components/kuntze/* @ssieb esphome/components/kuntze/* @ssieb
esphome/components/lc709203f/* @ilikecake
esphome/components/lcd_menu/* @numo68 esphome/components/lcd_menu/* @numo68
esphome/components/ld2410/* @regevbr @sebcaps esphome/components/ld2410/* @regevbr @sebcaps
esphome/components/ld2420/* @descipher esphome/components/ld2420/* @descipher
@@ -248,7 +243,6 @@ esphome/components/libretiny_pwm/* @kuba2k2
esphome/components/light/* @esphome/core esphome/components/light/* @esphome/core
esphome/components/lightwaverf/* @max246 esphome/components/lightwaverf/* @max246
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
esphome/components/ln882x/* @lamauny
esphome/components/lock/* @esphome/core esphome/components/lock/* @esphome/core
esphome/components/logger/* @esphome/core esphome/components/logger/* @esphome/core
esphome/components/logger/select/* @clydebarrow esphome/components/logger/select/* @clydebarrow
@@ -288,7 +282,6 @@ esphome/components/microphone/* @jesserockz @kahrendt
esphome/components/mics_4514/* @jesserockz esphome/components/mics_4514/* @jesserockz
esphome/components/midea/* @dudanov esphome/components/midea/* @dudanov
esphome/components/midea_ir/* @dudanov esphome/components/midea_ir/* @dudanov
esphome/components/mipi_spi/* @clydebarrow
esphome/components/mitsubishi/* @RubyBailey esphome/components/mitsubishi/* @RubyBailey
esphome/components/mixer/speaker/* @kahrendt esphome/components/mixer/speaker/* @kahrendt
esphome/components/mlx90393/* @functionpointer esphome/components/mlx90393/* @functionpointer
@@ -324,17 +317,13 @@ esphome/components/number/* @esphome/core
esphome/components/one_wire/* @ssieb esphome/components/one_wire/* @ssieb
esphome/components/online_image/* @clydebarrow @guillempages esphome/components/online_image/* @clydebarrow @guillempages
esphome/components/opentherm/* @olegtarasov esphome/components/opentherm/* @olegtarasov
esphome/components/openthread/* @mrene
esphome/components/opt3001/* @ccutrer
esphome/components/ota/* @esphome/core esphome/components/ota/* @esphome/core
esphome/components/ota_base/* @esphome/core
esphome/components/output/* @esphome/core esphome/components/output/* @esphome/core
esphome/components/packet_transport/* @clydebarrow esphome/components/packet_transport/* @clydebarrow
esphome/components/pca6416a/* @Mat931 esphome/components/pca6416a/* @Mat931
esphome/components/pca9554/* @clydebarrow @hwstar esphome/components/pca9554/* @clydebarrow @hwstar
esphome/components/pcf85063/* @brogon esphome/components/pcf85063/* @brogon
esphome/components/pcf8563/* @KoenBreeman esphome/components/pcf8563/* @KoenBreeman
esphome/components/pi4ioe5v6408/* @jesserockz
esphome/components/pid/* @OttoWinter esphome/components/pid/* @OttoWinter
esphome/components/pipsolar/* @andreashergert1984 esphome/components/pipsolar/* @andreashergert1984
esphome/components/pm1006/* @habbie esphome/components/pm1006/* @habbie
@@ -488,14 +477,12 @@ esphome/components/ufire_ise/* @pvizeli
esphome/components/ultrasonic/* @OttoWinter esphome/components/ultrasonic/* @OttoWinter
esphome/components/update/* @jesserockz esphome/components/update/* @jesserockz
esphome/components/uponor_smatrix/* @kroimon esphome/components/uponor_smatrix/* @kroimon
esphome/components/usb_host/* @clydebarrow
esphome/components/usb_uart/* @clydebarrow
esphome/components/valve/* @esphome/core esphome/components/valve/* @esphome/core
esphome/components/vbus/* @ssieb esphome/components/vbus/* @ssieb
esphome/components/veml3235/* @kbx81 esphome/components/veml3235/* @kbx81
esphome/components/veml7700/* @latonita esphome/components/veml7700/* @latonita
esphome/components/version/* @esphome/core 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/wake_on_lan/* @clydebarrow @willwill2will54
esphome/components/watchdog/* @oarcher esphome/components/watchdog/* @oarcher
esphome/components/waveshare_epaper/* @clydebarrow esphome/components/waveshare_epaper/* @clydebarrow
@@ -525,7 +512,6 @@ esphome/components/xiaomi_lywsd03mmc/* @ahpohl
esphome/components/xiaomi_mhoc303/* @drug123 esphome/components/xiaomi_mhoc303/* @drug123
esphome/components/xiaomi_mhoc401/* @vevsvevs esphome/components/xiaomi_mhoc401/* @vevsvevs
esphome/components/xiaomi_rtcgq02lm/* @jesserockz esphome/components/xiaomi_rtcgq02lm/* @jesserockz
esphome/components/xiaomi_xmwsdj04mmc/* @medusalix
esphome/components/xl9535/* @mreditor97 esphome/components/xl9535/* @mreditor97
esphome/components/xpt2046/touchscreen/* @nielsnl68 @numo68 esphome/components/xpt2046/touchscreen/* @nielsnl68 @numo68
esphome/components/xxtea/* @clydebarrow esphome/components/xxtea/* @clydebarrow

2877
Doxyfile

File diff suppressed because it is too large Load Diff

View File

@@ -11,9 +11,7 @@ FROM base-source-${BUILD_TYPE} AS base
RUN git config --system --add safe.directory "*" RUN git config --system --add safe.directory "*"
ENV PIP_DISABLE_PIP_VERSION_CHECK=1 RUN pip install uv==0.6.14
RUN pip install --no-cache-dir -U pip uv==0.6.14
COPY requirements.txt / COPY requirements.txt /

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,7 @@ namespace absolute_humidity {
static const char *const TAG = "absolute_humidity.sensor"; static const char *const TAG = "absolute_humidity.sensor";
void AbsoluteHumidityComponent::setup() { 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()); 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); }); this->temperature_sensor_->add_on_state_callback([this](float state) { this->temperature_callback_(state); });
@@ -40,11 +40,9 @@ void AbsoluteHumidityComponent::dump_config() {
break; break;
} }
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG, "Sources");
"Sources\n" ESP_LOGCONFIG(TAG, " Temperature: '%s'", this->temperature_sensor_->get_name().c_str());
" Temperature: '%s'\n" ESP_LOGCONFIG(TAG, " Relative Humidity: '%s'", this->humidity_sensor_->get_name().c_str());
" Relative Humidity: '%s'",
this->temperature_sensor_->get_name().c_str(), this->humidity_sensor_->get_name().c_str());
} }
float AbsoluteHumidityComponent::get_setup_priority() const { return setup_priority::DATA; } float AbsoluteHumidityComponent::get_setup_priority() const { return setup_priority::DATA; }

View File

@@ -4,7 +4,6 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include <cmath> #include <cmath>
#include <numbers>
#ifdef USE_ESP8266 #ifdef USE_ESP8266
#include <core_esp8266_waveform.h> #include <core_esp8266_waveform.h>
@@ -194,17 +193,18 @@ void AcDimmer::setup() {
setTimer1Callback(&timer_interrupt); setTimer1Callback(&timer_interrupt);
#endif #endif
#ifdef USE_ESP32 #ifdef USE_ESP32
// timer frequency of 1mhz // 80 Divider -> 1 count=1µs
dimmer_timer = timerBegin(1000000); dimmer_timer = timerBegin(0, 80, true);
timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr); timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr, true);
// For ESP32, we can't use dynamic interval calculation because the timerX functions // For ESP32, we can't use dynamic interval calculation because the timerX functions
// are not callable from ISR (placed in flash storage). // are not callable from ISR (placed in flash storage).
// Here we just use an interrupt firing every 50 µs. // 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 #endif
} }
void AcDimmer::write_state(float state) { 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)); auto new_value = static_cast<uint16_t>(roundf(state * 65535));
if (new_value != 0 && this->store_.value == 0) if (new_value != 0 && this->store_.value == 0)
this->store_.init_cycle = this->init_with_half_cycle_; this->store_.init_cycle = this->init_with_half_cycle_;
@@ -214,10 +214,8 @@ void AcDimmer::dump_config() {
ESP_LOGCONFIG(TAG, "AcDimmer:"); ESP_LOGCONFIG(TAG, "AcDimmer:");
LOG_PIN(" Output Pin: ", this->gate_pin_); LOG_PIN(" Output Pin: ", this->gate_pin_);
LOG_PIN(" Zero-Cross Pin: ", this->zero_cross_pin_); LOG_PIN(" Zero-Cross Pin: ", this->zero_cross_pin_);
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG, " Min Power: %.1f%%", this->store_.min_power / 10.0f);
" Min Power: %.1f%%\n" ESP_LOGCONFIG(TAG, " Init with half cycle: %s", YESNO(this->init_with_half_cycle_));
" Init with half cycle: %s",
this->store_.min_power / 10.0f, YESNO(this->init_with_half_cycle_));
if (method_ == DIM_METHOD_LEADING_PULSE) { if (method_ == DIM_METHOD_LEADING_PULSE) {
ESP_LOGCONFIG(TAG, " Method: leading pulse"); ESP_LOGCONFIG(TAG, " Method: leading pulse");
} else if (method_ == DIM_METHOD_LEADING) { } else if (method_ == DIM_METHOD_LEADING) {

View File

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

View File

@@ -61,7 +61,7 @@ uint32_t Aggregator::aggregate() {
void ADCSensor::update() { void ADCSensor::update() {
float value_v = this->sample(); 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); 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; static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1;
void ADCSensor::setup() { 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) { if (this->channel1_ != ADC1_CHANNEL_MAX) {
adc1_config_width(ADC_WIDTH_MAX_SOC_BITS); adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);
@@ -55,40 +55,30 @@ void ADCSensor::setup() {
} }
void ADCSensor::dump_config() { 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_SENSOR("", "ADC Sensor", this);
LOG_PIN(" Pin: ", this->pin_); LOG_PIN(" Pin: ", this->pin_);
if (this->autorange_) {
if (!this->autorange_) { ESP_LOGCONFIG(TAG, " Attenuation: auto");
} else {
switch (this->attenuation_) { switch (this->attenuation_) {
case ADC_ATTEN_DB_0: case ADC_ATTEN_DB_0:
atten_str = ATTEN_0DB_STR; ESP_LOGCONFIG(TAG, " Attenuation: 0db");
break; break;
case ADC_ATTEN_DB_2_5: case ADC_ATTEN_DB_2_5:
atten_str = ATTEN_2_5DB_STR; ESP_LOGCONFIG(TAG, " Attenuation: 2.5db");
break; break;
case ADC_ATTEN_DB_6: case ADC_ATTEN_DB_6:
atten_str = ATTEN_6DB_STR; ESP_LOGCONFIG(TAG, " Attenuation: 6db");
break; break;
case ADC_ATTEN_DB_12_COMPAT: case ADC_ATTEN_DB_12_COMPAT:
atten_str = ATTEN_12DB_STR; ESP_LOGCONFIG(TAG, " Attenuation: 12db");
break; break;
default: // This is to satisfy the unused ADC_ATTEN_MAX default: // This is to satisfy the unused ADC_ATTEN_MAX
break; break;
} }
} }
ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_);
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG, " Sampling mode: %s", LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
" Attenuation: %s\n"
" Samples: %i\n"
" Sampling mode: %s",
atten_str, this->sample_count_, LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
} }

View File

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

View File

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

View File

@@ -14,7 +14,7 @@ namespace adc {
static const char *const TAG = "adc.rp2040"; static const char *const TAG = "adc.rp2040";
void ADCSensor::setup() { 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; static bool initialized = false;
if (!initialized) { if (!initialized) {
adc_init(); adc_init();
@@ -33,10 +33,8 @@ void ADCSensor::dump_config() {
LOG_PIN(" Pin: ", this->pin_); LOG_PIN(" Pin: ", this->pin_);
#endif // USE_ADC_SENSOR_VCC #endif // USE_ADC_SENSOR_VCC
} }
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_);
" Samples: %i\n" ESP_LOGCONFIG(TAG, " Sampling mode: %s", LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
" Sampling mode: %s",
this->sample_count_, LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
LOG_UPDATE_INTERVAL(this); 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; } float ADC128S102::get_setup_priority() const { return setup_priority::HARDWARE; }
void ADC128S102::setup() { void ADC128S102::setup() {
ESP_LOGCONFIG(TAG, "Running setup"); ESP_LOGCONFIG(TAG, "Setting up adc128s102");
this->spi_setup(); this->spi_setup();
} }

View File

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

View File

@@ -85,6 +85,8 @@ class ADE7880 : public i2c::I2CDevice, public PollingComponent {
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
protected: protected:
ADE7880Store store_{}; ADE7880Store store_{};
InternalGPIOPin *irq0_pin_{nullptr}; 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(" ", "Active Power B Sensor", this->active_power_b_sensor_);
LOG_SENSOR(" ", "Rective Power A Sensor", this->reactive_power_a_sensor_); LOG_SENSOR(" ", "Rective Power A Sensor", this->reactive_power_a_sensor_);
LOG_SENSOR(" ", "Reactive Power B Sensor", this->reactive_power_b_sensor_); LOG_SENSOR(" ", "Reactive Power B Sensor", this->reactive_power_b_sensor_);
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG, " USE_ACC_ENERGY_REGS: %d", this->use_acc_energy_regs_);
" USE_ACC_ENERGY_REGS: %d\n" ESP_LOGCONFIG(TAG, " PGA_V_8: 0x%X", pga_v_);
" PGA_V_8: 0x%X\n" ESP_LOGCONFIG(TAG, " PGA_IA_8: 0x%X", pga_ia_);
" PGA_IA_8: 0x%X\n" ESP_LOGCONFIG(TAG, " PGA_IB_8: 0x%X", pga_ib_);
" PGA_IB_8: 0x%X\n" ESP_LOGCONFIG(TAG, " VGAIN_32: 0x%08jX", (uintmax_t) vgain_);
" VGAIN_32: 0x%08jX\n" ESP_LOGCONFIG(TAG, " AIGAIN_32: 0x%08jX", (uintmax_t) aigain_);
" AIGAIN_32: 0x%08jX\n" ESP_LOGCONFIG(TAG, " BIGAIN_32: 0x%08jX", (uintmax_t) bigain_);
" BIGAIN_32: 0x%08jX\n" ESP_LOGCONFIG(TAG, " AWGAIN_32: 0x%08jX", (uintmax_t) awgain_);
" AWGAIN_32: 0x%08jX\n" ESP_LOGCONFIG(TAG, " BWGAIN_32: 0x%08jX", (uintmax_t) bwgain_);
" 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_);
} }
#define ADE_PUBLISH_(name, val, factor) \ #define ADE_PUBLISH_(name, val, factor) \

View File

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

View File

@@ -1,6 +1,6 @@
#include "ade7953_spi.h" #include "ade7953_spi.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome { namespace esphome {
namespace ade7953_spi { 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; static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
void ADS1115Component::setup() { void ADS1115Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup"); ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
uint16_t value; uint16_t value;
if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &value)) { if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &value)) {
this->mark_failed(); this->mark_failed();
return; return;
} }
ESP_LOGCONFIG(TAG, "Configuring ADS1115...");
uint16_t config = 0; uint16_t config = 0;
// Clear single-shot bit // Clear single-shot bit
// 0b0xxxxxxxxxxxxxxx // 0b0xxxxxxxxxxxxxxx
@@ -66,10 +68,10 @@ void ADS1115Component::setup() {
this->prev_config_ = config; this->prev_config_ = config;
} }
void ADS1115Component::dump_config() { void ADS1115Component::dump_config() {
ESP_LOGCONFIG(TAG, "ADS1115:"); ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
LOG_I2C_DEVICE(this); LOG_I2C_DEVICE(this);
if (this->is_failed()) { 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, 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 setup() override;
void dump_config() override; void dump_config() override;
/// HARDWARE_LATE setup priority /// 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; } void set_continuous_mode(bool continuous_mode) { continuous_mode_ = continuous_mode; }
/// Helper method to request a measurement from a sensor. /// Helper method to request a measurement from a sensor.

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
#include "ags10.h" #include "ags10.h"
#include "esphome/core/helpers.h"
#include <cinttypes> #include <cinttypes>
@@ -24,7 +23,7 @@ static const uint16_t ZP_CURRENT = 0x0000;
static const uint16_t ZP_DEFAULT = 0xFFFF; static const uint16_t ZP_DEFAULT = 0xFFFF;
void AGS10Component::setup() { void AGS10Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup"); ESP_LOGCONFIG(TAG, "Setting up ags10...");
auto version = this->read_version_(); auto version = this->read_version_();
if (version) { if (version) {
@@ -66,7 +65,7 @@ void AGS10Component::dump_config() {
case NONE: case NONE:
break; break;
case COMMUNICATION_FAILED: case COMMUNICATION_FAILED:
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); ESP_LOGE(TAG, "Communication with AGS10 failed!");
break; break;
case CRC_CHECK_FAILED: case CRC_CHECK_FAILED:
ESP_LOGE(TAG, "The 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; void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
/** /**
* Modifies target address of AGS10. * 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. // results making successive requests; the current implementation makes 3 attempts with a delay of 30ms each time.
#include "aht10.h" #include "aht10.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome { namespace esphome {
namespace aht10 { namespace aht10 {
@@ -35,59 +34,57 @@ static const uint8_t AHT10_INIT_ATTEMPTS = 10;
static const uint8_t AHT10_STATUS_BUSY = 0x80; 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() { void AHT10Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
if (this->write(AHT10_SOFTRESET_CMD, sizeof(AHT10_SOFTRESET_CMD)) != i2c::ERROR_OK) { 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); delay(AHT10_SOFTRESET_DELAY);
i2c::ErrorCode error_code = i2c::ERROR_INVALID_ARGUMENT; i2c::ErrorCode error_code = i2c::ERROR_INVALID_ARGUMENT;
switch (this->variant_) { switch (this->variant_) {
case AHT10Variant::AHT20: case AHT10Variant::AHT20:
ESP_LOGCONFIG(TAG, "Setting up AHT20");
error_code = this->write(AHT20_INITIALIZE_CMD, sizeof(AHT20_INITIALIZE_CMD)); error_code = this->write(AHT20_INITIALIZE_CMD, sizeof(AHT20_INITIALIZE_CMD));
break; break;
case AHT10Variant::AHT10: case AHT10Variant::AHT10:
ESP_LOGCONFIG(TAG, "Setting up AHT10");
error_code = this->write(AHT10_INITIALIZE_CMD, sizeof(AHT10_INITIALIZE_CMD)); error_code = this->write(AHT10_INITIALIZE_CMD, sizeof(AHT10_INITIALIZE_CMD));
break; break;
} }
if (error_code != i2c::ERROR_OK) { if (error_code != i2c::ERROR_OK) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); ESP_LOGE(TAG, "Communication with AHT10 failed!");
this->mark_failed(); this->mark_failed();
return; return;
} }
uint8_t cal_attempts = 0;
uint8_t data = AHT10_STATUS_BUSY; uint8_t data = AHT10_STATUS_BUSY;
int cal_attempts = 0;
while (data & AHT10_STATUS_BUSY) { while (data & AHT10_STATUS_BUSY) {
delay(AHT10_DEFAULT_DELAY); delay(AHT10_DEFAULT_DELAY);
if (this->read(&data, 1) != i2c::ERROR_OK) { 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(); this->mark_failed();
return; return;
} }
++cal_attempts; ++cal_attempts;
if (cal_attempts > AHT10_INIT_ATTEMPTS) { if (cal_attempts > AHT10_INIT_ATTEMPTS) {
ESP_LOGE(TAG, "Initialization timed out"); ESP_LOGE(TAG, "AHT10 initialization timed out!");
this->mark_failed(); this->mark_failed();
return; return;
} }
} }
if ((data & 0x68) != 0x08) { // Bit[6:5] = 0b00, NORMAL mode and Bit[3] = 0b1, CALIBRATED 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(); this->mark_failed();
return; return;
} }
ESP_LOGV(TAG, "Initialization complete"); ESP_LOGV(TAG, "AHT10 initialization");
} }
void AHT10Component::restart_read_() { void AHT10Component::restart_read_() {
if (this->read_count_ == AHT10_ATTEMPTS) { if (this->read_count_ == AHT10_ATTEMPTS) {
this->read_count_ = 0; this->read_count_ = 0;
this->status_set_error("Reading timed out"); this->status_set_error("Measurements reading timed-out!");
return; return;
} }
this->read_count_++; 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_)); ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_));
} }
if (this->read(data, 6) != i2c::ERROR_OK) { 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_(); this->restart_read_();
return; return;
} }
if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy 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_(); this->restart_read_();
return; return;
} }
if (data[1] == 0x0 && data[2] == 0x0 && (data[3] >> 4) == 0x0) { if (data[1] == 0x0 && data[2] == 0x0 && (data[3] >> 4) == 0x0) {
// Invalid humidity (0x0) // Unrealistic humidity (0x0)
if (this->humidity_sensor_ == nullptr) { 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 { } 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) { 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_(); this->restart_read_();
return; return;
@@ -126,17 +123,22 @@ void AHT10Component::read_data_() {
if (this->read_count_ > 1) { if (this->read_count_ > 1) {
ESP_LOGD(TAG, "Success at %ums", (unsigned) (millis() - this->start_time_)); 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_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
uint32_t raw_humidity = encode_uint24(data[1], data[2], data[3]) >> 4; uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4;
if (this->temperature_sensor_ != nullptr) { 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); this->temperature_sensor_->publish_state(temperature);
} }
if (this->humidity_sensor_ != nullptr) { 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)) { 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); this->humidity_sensor_->publish_state(humidity);
} }
@@ -148,7 +150,7 @@ void AHT10Component::update() {
return; return;
this->start_time_ = millis(); this->start_time_ = millis();
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) { 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; return;
} }
this->restart_read_(); this->restart_read_();
@@ -160,7 +162,7 @@ void AHT10Component::dump_config() {
ESP_LOGCONFIG(TAG, "AHT10:"); ESP_LOGCONFIG(TAG, "AHT10:");
LOG_I2C_DEVICE(this); LOG_I2C_DEVICE(this);
if (this->is_failed()) { 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(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);

View File

@@ -17,7 +17,7 @@ static const char *const TAG = "aic3204";
} }
void AIC3204::setup() { void AIC3204::setup() {
ESP_LOGCONFIG(TAG, "Running setup"); ESP_LOGCONFIG(TAG, "Setting up AIC3204...");
// Set register page to 0 // Set register page to 0
ERROR_CHECK(this->write_byte(AIC3204_PAGE_CTRL, 0x00), "Set page 0 failed"); 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); LOG_I2C_DEVICE(this);
if (this->is_failed()) { 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: public:
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
bool set_mute_off() override; bool set_mute_off() override;
bool set_mute_on() override; bool set_mute_on() override;

View File

@@ -14,8 +14,8 @@ from esphome.const import (
CONF_WEB_SERVER, CONF_WEB_SERVER,
) )
from esphome.core import CORE, coroutine_with_priority 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_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@grahambrown11", "@hwstar"] CODEOWNERS = ["@grahambrown11", "@hwstar"]
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
@@ -149,9 +149,6 @@ _ALARM_CONTROL_PANEL_SCHEMA = (
) )
_ALARM_CONTROL_PANEL_SCHEMA.add_extra(entity_duplicate_validator("alarm_control_panel"))
def alarm_control_panel_schema( def alarm_control_panel_schema(
class_: MockObjClass, class_: MockObjClass,
*, *,
@@ -193,7 +190,7 @@ ALARM_CONTROL_PANEL_CONDITION_SCHEMA = maybe_simple_id(
async def setup_alarm_control_panel_core_(var, config): 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, []): for conf in config.get(CONF_ON_STATE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) await automation.build_automation(trigger, [], conf)
@@ -238,7 +235,6 @@ async def register_alarm_control_panel(var, config):
if not CORE.has_id(config[CONF_ID]): if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var) var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_alarm_control_panel(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) await setup_alarm_control_panel_core_(var, config)

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, void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override; esp_ble_gattc_cb_param_t *param) override;
void dump_config() 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_flow_sensor(sensor::Sensor *sensor) { this->flow_sensor_ = sensor; }
void set_head_sensor(sensor::Sensor *sensor) { this->head_sensor_ = sensor; } void set_head_sensor(sensor::Sensor *sensor) { this->head_sensor_ = sensor; }
void set_power_sensor(sensor::Sensor *sensor) { this->power_sensor_ = sensor; } void set_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() { void AM2315C::setup() {
ESP_LOGCONFIG(TAG, "Running setup"); ESP_LOGCONFIG(TAG, "Setting up AM2315C...");
// get status // get status
uint8_t status = 0; uint8_t status = 0;
@@ -188,7 +188,7 @@ void AM2315C::dump_config() {
ESP_LOGCONFIG(TAG, "AM2315C:"); ESP_LOGCONFIG(TAG, "AM2315C:");
LOG_I2C_DEVICE(this); LOG_I2C_DEVICE(this);
if (this->is_failed()) { 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(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);

View File

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

View File

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

View File

@@ -12,10 +12,8 @@ using namespace esphome::cover;
void Am43Component::dump_config() { void Am43Component::dump_config() {
LOG_COVER("", "AM43 Cover", this); LOG_COVER("", "AM43 Cover", this);
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG, " Device Pin: %d", this->pin_);
" Device Pin: %d\n" ESP_LOGCONFIG(TAG, " Invert Position: %d", (int) this->invert_position_);
" Invert Position: %d",
this->pin_, (int) this->invert_position_);
} }
void Am43Component::setup() { 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, void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override; esp_ble_gattc_cb_param_t *param) override;
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
cover::CoverTraits get_traits() override; cover::CoverTraits get_traits() override;
void set_pin(uint16_t pin) { this->pin_ = pin; } void set_pin(uint16_t pin) { this->pin_ = pin; }
void set_invert_position(bool invert_position) { this->invert_position_ = invert_position; } 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, void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override; esp_ble_gattc_cb_param_t *param) override;
void dump_config() 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_battery(sensor::Sensor *battery) { battery_ = battery; }
void set_illuminance(sensor::Sensor *illuminance) { illuminance_ = illuminance; } void set_illuminance(sensor::Sensor *illuminance) { illuminance_ = illuminance; }

View File

@@ -34,10 +34,8 @@ void AnalogThresholdBinarySensor::set_sensor(sensor::Sensor *analog_sensor) {
void AnalogThresholdBinarySensor::dump_config() { void AnalogThresholdBinarySensor::dump_config() {
LOG_BINARY_SENSOR("", "Analog Threshold Binary Sensor", this); LOG_BINARY_SENSOR("", "Analog Threshold Binary Sensor", this);
LOG_SENSOR(" ", "Sensor", this->sensor_); LOG_SENSOR(" ", "Sensor", this->sensor_);
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG, " Upper threshold: %.11f", this->upper_threshold_.value());
" Upper threshold: %.11f\n" ESP_LOGCONFIG(TAG, " Lower threshold: %.11f", this->lower_threshold_.value());
" Lower threshold: %.11f",
this->upper_threshold_.value(), this->lower_threshold_.value());
} }
} // namespace analog_threshold } // namespace analog_threshold

View File

@@ -12,6 +12,8 @@ class AnalogThresholdBinarySensor : public Component, public binary_sensor::Bina
void dump_config() override; void dump_config() override;
void setup() override; void setup() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_sensor(sensor::Sensor *analog_sensor); 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_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; } template<typename T> void set_lower_threshold(T lower_threshold) { this->lower_threshold_ = lower_threshold; }

View File

@@ -17,11 +17,7 @@ void Anova::setup() {
this->current_request_ = 0; this->current_request_ = 0;
} }
void Anova::loop() { 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::control(const ClimateCall &call) { void Anova::control(const ClimateCall &call) {
if (call.get_mode().has_value()) { 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, void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override; esp_ble_gattc_cb_param_t *param) override;
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
climate::ClimateTraits traits() override { climate::ClimateTraits traits() override {
auto traits = climate::ClimateTraits(); auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(true); traits.set_supports_current_temperature(true);

View File

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

View File

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

View File

@@ -49,7 +49,6 @@ SERVICE_ARG_NATIVE_TYPES = {
"string[]": cg.std_vector.template(cg.std_string), "string[]": cg.std_vector.template(cg.std_string),
} }
CONF_ENCRYPTION = "encryption" CONF_ENCRYPTION = "encryption"
CONF_BATCH_DELAY = "batch_delay"
def validate_encryption_key(value): def validate_encryption_key(value):
@@ -110,10 +109,6 @@ CONFIG_SCHEMA = cv.All(
): ACTIONS_SCHEMA, ): ACTIONS_SCHEMA,
cv.Exclusive(CONF_ACTIONS, 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_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_ON_CLIENT_CONNECTED): automation.validate_automation( cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
single=True single=True
), ),
@@ -134,28 +129,24 @@ async def to_code(config):
cg.add(var.set_port(config[CONF_PORT])) cg.add(var.set_port(config[CONF_PORT]))
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_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
if actions := config.get(CONF_ACTIONS, []): for conf in config.get(CONF_ACTIONS, []):
cg.add_define("USE_API_YAML_SERVICES") template_args = []
for conf in actions: func_args = []
template_args = [] service_arg_names = []
func_args = [] for name, var_ in conf[CONF_VARIABLES].items():
service_arg_names = [] native = SERVICE_ARG_NATIVE_TYPES[var_]
for name, var_ in conf[CONF_VARIABLES].items(): template_args.append(native)
native = SERVICE_ARG_NATIVE_TYPES[var_] func_args.append((native, name))
template_args.append(native) service_arg_names.append(name)
func_args.append((native, name)) templ = cg.TemplateArguments(*template_args)
service_arg_names.append(name) trigger = cg.new_Pvariable(
templ = cg.TemplateArguments(*template_args) conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names
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)
cg.add(var.register_user_service(trigger))
await automation.build_automation(trigger, func_args, conf)
if CONF_ON_CLIENT_CONNECTED in config: if CONF_ON_CLIENT_CONNECTED in config:
cg.add_define("USE_API_CLIENT_CONNECTED_TRIGGER")
await automation.build_automation( await automation.build_automation(
var.get_client_connected_trigger(), var.get_client_connected_trigger(),
[(cg.std_string, "client_info"), (cg.std_string, "client_address")], [(cg.std_string, "client_info"), (cg.std_string, "client_address")],
@@ -163,7 +154,6 @@ async def to_code(config):
) )
if CONF_ON_CLIENT_DISCONNECTED in config: if CONF_ON_CLIENT_DISCONNECTED in config:
cg.add_define("USE_API_CLIENT_DISCONNECTED_TRIGGER")
await automation.build_automation( await automation.build_automation(
var.get_client_disconnected_trigger(), var.get_client_disconnected_trigger(),
[(cg.std_string, "client_info"), (cg.std_string, "client_address")], [(cg.std_string, "client_info"), (cg.std_string, "client_address")],
@@ -182,7 +172,7 @@ async def to_code(config):
# and plaintext disabled. Only a factory reset can remove it. # and plaintext disabled. Only a factory reset can remove it.
cg.add_define("USE_API_PLAINTEXT") cg.add_define("USE_API_PLAINTEXT")
cg.add_define("USE_API_NOISE") 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: else:
cg.add_define("USE_API_PLAINTEXT") cg.add_define("USE_API_PLAINTEXT")

View File

@@ -188,17 +188,6 @@ message DeviceInfoRequest {
// Empty // Empty
} }
message AreaInfo {
uint32 area_id = 1;
string name = 2;
}
message DeviceInfo {
uint32 device_id = 1;
string name = 2;
uint32 area_id = 3;
}
message DeviceInfoResponse { message DeviceInfoResponse {
option (id) = 10; option (id) = 10;
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
@@ -247,12 +236,6 @@ message DeviceInfoResponse {
// Supports receiving and saving api encryption key // Supports receiving and saving api encryption key
bool api_encryption_supported = 19; bool api_encryption_supported = 19;
repeated DeviceInfo devices = 20;
repeated AreaInfo areas = 21;
// Top-level area info to phase out suggested_area
AreaInfo area = 22;
} }
message ListEntitiesRequest { message ListEntitiesRequest {
@@ -283,7 +266,6 @@ enum EntityCategory {
// ==================== BINARY SENSOR ==================== // ==================== BINARY SENSOR ====================
message ListEntitiesBinarySensorResponse { message ListEntitiesBinarySensorResponse {
option (id) = 12; option (id) = 12;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BINARY_SENSOR"; option (ifdef) = "USE_BINARY_SENSOR";
@@ -297,11 +279,9 @@ message ListEntitiesBinarySensorResponse {
bool disabled_by_default = 7; bool disabled_by_default = 7;
string icon = 8; string icon = 8;
EntityCategory entity_category = 9; EntityCategory entity_category = 9;
uint32 device_id = 10;
} }
message BinarySensorStateResponse { message BinarySensorStateResponse {
option (id) = 21; option (id) = 21;
option (base_class) = "StateResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BINARY_SENSOR"; option (ifdef) = "USE_BINARY_SENSOR";
option (no_delay) = true; option (no_delay) = true;
@@ -316,7 +296,6 @@ message BinarySensorStateResponse {
// ==================== COVER ==================== // ==================== COVER ====================
message ListEntitiesCoverResponse { message ListEntitiesCoverResponse {
option (id) = 13; option (id) = 13;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_COVER"; option (ifdef) = "USE_COVER";
@@ -333,7 +312,6 @@ message ListEntitiesCoverResponse {
string icon = 10; string icon = 10;
EntityCategory entity_category = 11; EntityCategory entity_category = 11;
bool supports_stop = 12; bool supports_stop = 12;
uint32 device_id = 13;
} }
enum LegacyCoverState { enum LegacyCoverState {
@@ -347,7 +325,6 @@ enum CoverOperation {
} }
message CoverStateResponse { message CoverStateResponse {
option (id) = 22; option (id) = 22;
option (base_class) = "StateResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_COVER"; option (ifdef) = "USE_COVER";
option (no_delay) = true; option (no_delay) = true;
@@ -390,7 +367,6 @@ message CoverCommandRequest {
// ==================== FAN ==================== // ==================== FAN ====================
message ListEntitiesFanResponse { message ListEntitiesFanResponse {
option (id) = 14; option (id) = 14;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_FAN"; option (ifdef) = "USE_FAN";
@@ -407,7 +383,6 @@ message ListEntitiesFanResponse {
string icon = 10; string icon = 10;
EntityCategory entity_category = 11; EntityCategory entity_category = 11;
repeated string supported_preset_modes = 12; repeated string supported_preset_modes = 12;
uint32 device_id = 13;
} }
enum FanSpeed { enum FanSpeed {
FAN_SPEED_LOW = 0; FAN_SPEED_LOW = 0;
@@ -420,7 +395,6 @@ enum FanDirection {
} }
message FanStateResponse { message FanStateResponse {
option (id) = 23; option (id) = 23;
option (base_class) = "StateResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_FAN"; option (ifdef) = "USE_FAN";
option (no_delay) = true; option (no_delay) = true;
@@ -458,8 +432,7 @@ message FanCommandRequest {
enum ColorMode { enum ColorMode {
COLOR_MODE_UNKNOWN = 0; COLOR_MODE_UNKNOWN = 0;
COLOR_MODE_ON_OFF = 1; COLOR_MODE_ON_OFF = 1;
COLOR_MODE_LEGACY_BRIGHTNESS = 2; COLOR_MODE_BRIGHTNESS = 2;
COLOR_MODE_BRIGHTNESS = 3;
COLOR_MODE_WHITE = 7; COLOR_MODE_WHITE = 7;
COLOR_MODE_COLOR_TEMPERATURE = 11; COLOR_MODE_COLOR_TEMPERATURE = 11;
COLOR_MODE_COLD_WARM_WHITE = 19; COLOR_MODE_COLD_WARM_WHITE = 19;
@@ -470,7 +443,6 @@ enum ColorMode {
} }
message ListEntitiesLightResponse { message ListEntitiesLightResponse {
option (id) = 15; option (id) = 15;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_LIGHT"; option (ifdef) = "USE_LIGHT";
@@ -491,11 +463,9 @@ message ListEntitiesLightResponse {
bool disabled_by_default = 13; bool disabled_by_default = 13;
string icon = 14; string icon = 14;
EntityCategory entity_category = 15; EntityCategory entity_category = 15;
uint32 device_id = 16;
} }
message LightStateResponse { message LightStateResponse {
option (id) = 24; option (id) = 24;
option (base_class) = "StateResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_LIGHT"; option (ifdef) = "USE_LIGHT";
option (no_delay) = true; option (no_delay) = true;
@@ -565,7 +535,6 @@ enum SensorLastResetType {
message ListEntitiesSensorResponse { message ListEntitiesSensorResponse {
option (id) = 16; option (id) = 16;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_SENSOR"; option (ifdef) = "USE_SENSOR";
@@ -584,11 +553,9 @@ message ListEntitiesSensorResponse {
SensorLastResetType legacy_last_reset_type = 11; SensorLastResetType legacy_last_reset_type = 11;
bool disabled_by_default = 12; bool disabled_by_default = 12;
EntityCategory entity_category = 13; EntityCategory entity_category = 13;
uint32 device_id = 14;
} }
message SensorStateResponse { message SensorStateResponse {
option (id) = 25; option (id) = 25;
option (base_class) = "StateResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_SENSOR"; option (ifdef) = "USE_SENSOR";
option (no_delay) = true; option (no_delay) = true;
@@ -603,7 +570,6 @@ message SensorStateResponse {
// ==================== SWITCH ==================== // ==================== SWITCH ====================
message ListEntitiesSwitchResponse { message ListEntitiesSwitchResponse {
option (id) = 17; option (id) = 17;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_SWITCH"; option (ifdef) = "USE_SWITCH";
@@ -617,11 +583,9 @@ message ListEntitiesSwitchResponse {
bool disabled_by_default = 7; bool disabled_by_default = 7;
EntityCategory entity_category = 8; EntityCategory entity_category = 8;
string device_class = 9; string device_class = 9;
uint32 device_id = 10;
} }
message SwitchStateResponse { message SwitchStateResponse {
option (id) = 26; option (id) = 26;
option (base_class) = "StateResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_SWITCH"; option (ifdef) = "USE_SWITCH";
option (no_delay) = true; option (no_delay) = true;
@@ -642,7 +606,6 @@ message SwitchCommandRequest {
// ==================== TEXT SENSOR ==================== // ==================== TEXT SENSOR ====================
message ListEntitiesTextSensorResponse { message ListEntitiesTextSensorResponse {
option (id) = 18; option (id) = 18;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_TEXT_SENSOR"; option (ifdef) = "USE_TEXT_SENSOR";
@@ -655,11 +618,9 @@ message ListEntitiesTextSensorResponse {
bool disabled_by_default = 6; bool disabled_by_default = 6;
EntityCategory entity_category = 7; EntityCategory entity_category = 7;
string device_class = 8; string device_class = 8;
uint32 device_id = 9;
} }
message TextSensorStateResponse { message TextSensorStateResponse {
option (id) = 27; option (id) = 27;
option (base_class) = "StateResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_TEXT_SENSOR"; option (ifdef) = "USE_TEXT_SENSOR";
option (no_delay) = true; option (no_delay) = true;
@@ -827,7 +788,6 @@ message ExecuteServiceRequest {
// ==================== CAMERA ==================== // ==================== CAMERA ====================
message ListEntitiesCameraResponse { message ListEntitiesCameraResponse {
option (id) = 43; option (id) = 43;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_ESP32_CAMERA"; option (ifdef) = "USE_ESP32_CAMERA";
@@ -838,7 +798,6 @@ message ListEntitiesCameraResponse {
bool disabled_by_default = 5; bool disabled_by_default = 5;
string icon = 6; string icon = 6;
EntityCategory entity_category = 7; EntityCategory entity_category = 7;
uint32 device_id = 8;
} }
message CameraImageResponse { message CameraImageResponse {
@@ -909,7 +868,6 @@ enum ClimatePreset {
} }
message ListEntitiesClimateResponse { message ListEntitiesClimateResponse {
option (id) = 46; option (id) = 46;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_CLIMATE"; option (ifdef) = "USE_CLIMATE";
@@ -941,11 +899,9 @@ message ListEntitiesClimateResponse {
bool supports_target_humidity = 23; bool supports_target_humidity = 23;
float visual_min_humidity = 24; float visual_min_humidity = 24;
float visual_max_humidity = 25; float visual_max_humidity = 25;
uint32 device_id = 26;
} }
message ClimateStateResponse { message ClimateStateResponse {
option (id) = 47; option (id) = 47;
option (base_class) = "StateResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_CLIMATE"; option (ifdef) = "USE_CLIMATE";
option (no_delay) = true; option (no_delay) = true;
@@ -1007,7 +963,6 @@ enum NumberMode {
} }
message ListEntitiesNumberResponse { message ListEntitiesNumberResponse {
option (id) = 49; option (id) = 49;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_NUMBER"; option (ifdef) = "USE_NUMBER";
@@ -1025,11 +980,9 @@ message ListEntitiesNumberResponse {
string unit_of_measurement = 11; string unit_of_measurement = 11;
NumberMode mode = 12; NumberMode mode = 12;
string device_class = 13; string device_class = 13;
uint32 device_id = 14;
} }
message NumberStateResponse { message NumberStateResponse {
option (id) = 50; option (id) = 50;
option (base_class) = "StateResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_NUMBER"; option (ifdef) = "USE_NUMBER";
option (no_delay) = true; option (no_delay) = true;
@@ -1053,7 +1006,6 @@ message NumberCommandRequest {
// ==================== SELECT ==================== // ==================== SELECT ====================
message ListEntitiesSelectResponse { message ListEntitiesSelectResponse {
option (id) = 52; option (id) = 52;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_SELECT"; option (ifdef) = "USE_SELECT";
@@ -1066,11 +1018,9 @@ message ListEntitiesSelectResponse {
repeated string options = 6; repeated string options = 6;
bool disabled_by_default = 7; bool disabled_by_default = 7;
EntityCategory entity_category = 8; EntityCategory entity_category = 8;
uint32 device_id = 9;
} }
message SelectStateResponse { message SelectStateResponse {
option (id) = 53; option (id) = 53;
option (base_class) = "StateResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_SELECT"; option (ifdef) = "USE_SELECT";
option (no_delay) = true; option (no_delay) = true;
@@ -1094,7 +1044,6 @@ message SelectCommandRequest {
// ==================== SIREN ==================== // ==================== SIREN ====================
message ListEntitiesSirenResponse { message ListEntitiesSirenResponse {
option (id) = 55; option (id) = 55;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_SIREN"; option (ifdef) = "USE_SIREN";
@@ -1109,11 +1058,9 @@ message ListEntitiesSirenResponse {
bool supports_duration = 8; bool supports_duration = 8;
bool supports_volume = 9; bool supports_volume = 9;
EntityCategory entity_category = 10; EntityCategory entity_category = 10;
uint32 device_id = 11;
} }
message SirenStateResponse { message SirenStateResponse {
option (id) = 56; option (id) = 56;
option (base_class) = "StateResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_SIREN"; option (ifdef) = "USE_SIREN";
option (no_delay) = true; option (no_delay) = true;
@@ -1154,7 +1101,6 @@ enum LockCommand {
} }
message ListEntitiesLockResponse { message ListEntitiesLockResponse {
option (id) = 58; option (id) = 58;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_LOCK"; option (ifdef) = "USE_LOCK";
@@ -1173,11 +1119,9 @@ message ListEntitiesLockResponse {
// Not yet implemented: // Not yet implemented:
string code_format = 11; string code_format = 11;
uint32 device_id = 12;
} }
message LockStateResponse { message LockStateResponse {
option (id) = 59; option (id) = 59;
option (base_class) = "StateResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_LOCK"; option (ifdef) = "USE_LOCK";
option (no_delay) = true; option (no_delay) = true;
@@ -1200,7 +1144,6 @@ message LockCommandRequest {
// ==================== BUTTON ==================== // ==================== BUTTON ====================
message ListEntitiesButtonResponse { message ListEntitiesButtonResponse {
option (id) = 61; option (id) = 61;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BUTTON"; option (ifdef) = "USE_BUTTON";
@@ -1213,7 +1156,6 @@ message ListEntitiesButtonResponse {
bool disabled_by_default = 6; bool disabled_by_default = 6;
EntityCategory entity_category = 7; EntityCategory entity_category = 7;
string device_class = 8; string device_class = 8;
uint32 device_id = 9;
} }
message ButtonCommandRequest { message ButtonCommandRequest {
option (id) = 62; option (id) = 62;
@@ -1253,7 +1195,6 @@ message MediaPlayerSupportedFormat {
} }
message ListEntitiesMediaPlayerResponse { message ListEntitiesMediaPlayerResponse {
option (id) = 63; option (id) = 63;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_MEDIA_PLAYER"; option (ifdef) = "USE_MEDIA_PLAYER";
@@ -1269,12 +1210,9 @@ message ListEntitiesMediaPlayerResponse {
bool supports_pause = 8; bool supports_pause = 8;
repeated MediaPlayerSupportedFormat supported_formats = 9; repeated MediaPlayerSupportedFormat supported_formats = 9;
uint32 device_id = 10;
} }
message MediaPlayerStateResponse { message MediaPlayerStateResponse {
option (id) = 64; option (id) = 64;
option (base_class) = "StateResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_MEDIA_PLAYER"; option (ifdef) = "USE_MEDIA_PLAYER";
option (no_delay) = true; option (no_delay) = true;
@@ -1676,7 +1614,6 @@ enum VoiceAssistantEvent {
VOICE_ASSISTANT_STT_VAD_END = 12; VOICE_ASSISTANT_STT_VAD_END = 12;
VOICE_ASSISTANT_TTS_STREAM_START = 98; VOICE_ASSISTANT_TTS_STREAM_START = 98;
VOICE_ASSISTANT_TTS_STREAM_END = 99; VOICE_ASSISTANT_TTS_STREAM_END = 99;
VOICE_ASSISTANT_INTENT_PROGRESS = 100;
} }
message VoiceAssistantEventData { message VoiceAssistantEventData {
@@ -1797,7 +1734,6 @@ enum AlarmControlPanelStateCommand {
message ListEntitiesAlarmControlPanelResponse { message ListEntitiesAlarmControlPanelResponse {
option (id) = 94; option (id) = 94;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_ALARM_CONTROL_PANEL"; option (ifdef) = "USE_ALARM_CONTROL_PANEL";
@@ -1811,12 +1747,10 @@ message ListEntitiesAlarmControlPanelResponse {
uint32 supported_features = 8; uint32 supported_features = 8;
bool requires_code = 9; bool requires_code = 9;
bool requires_code_to_arm = 10; bool requires_code_to_arm = 10;
uint32 device_id = 11;
} }
message AlarmControlPanelStateResponse { message AlarmControlPanelStateResponse {
option (id) = 95; option (id) = 95;
option (base_class) = "StateResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_ALARM_CONTROL_PANEL"; option (ifdef) = "USE_ALARM_CONTROL_PANEL";
option (no_delay) = true; option (no_delay) = true;
@@ -1841,7 +1775,6 @@ enum TextMode {
} }
message ListEntitiesTextResponse { message ListEntitiesTextResponse {
option (id) = 97; option (id) = 97;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_TEXT"; option (ifdef) = "USE_TEXT";
@@ -1857,11 +1790,9 @@ message ListEntitiesTextResponse {
uint32 max_length = 9; uint32 max_length = 9;
string pattern = 10; string pattern = 10;
TextMode mode = 11; TextMode mode = 11;
uint32 device_id = 12;
} }
message TextStateResponse { message TextStateResponse {
option (id) = 98; option (id) = 98;
option (base_class) = "StateResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_TEXT"; option (ifdef) = "USE_TEXT";
option (no_delay) = true; option (no_delay) = true;
@@ -1886,7 +1817,6 @@ message TextCommandRequest {
// ==================== DATETIME DATE ==================== // ==================== DATETIME DATE ====================
message ListEntitiesDateResponse { message ListEntitiesDateResponse {
option (id) = 100; option (id) = 100;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_DATETIME_DATE"; option (ifdef) = "USE_DATETIME_DATE";
@@ -1898,11 +1828,9 @@ message ListEntitiesDateResponse {
string icon = 5; string icon = 5;
bool disabled_by_default = 6; bool disabled_by_default = 6;
EntityCategory entity_category = 7; EntityCategory entity_category = 7;
uint32 device_id = 8;
} }
message DateStateResponse { message DateStateResponse {
option (id) = 101; option (id) = 101;
option (base_class) = "StateResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_DATETIME_DATE"; option (ifdef) = "USE_DATETIME_DATE";
option (no_delay) = true; option (no_delay) = true;
@@ -1930,7 +1858,6 @@ message DateCommandRequest {
// ==================== DATETIME TIME ==================== // ==================== DATETIME TIME ====================
message ListEntitiesTimeResponse { message ListEntitiesTimeResponse {
option (id) = 103; option (id) = 103;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_DATETIME_TIME"; option (ifdef) = "USE_DATETIME_TIME";
@@ -1942,11 +1869,9 @@ message ListEntitiesTimeResponse {
string icon = 5; string icon = 5;
bool disabled_by_default = 6; bool disabled_by_default = 6;
EntityCategory entity_category = 7; EntityCategory entity_category = 7;
uint32 device_id = 8;
} }
message TimeStateResponse { message TimeStateResponse {
option (id) = 104; option (id) = 104;
option (base_class) = "StateResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_DATETIME_TIME"; option (ifdef) = "USE_DATETIME_TIME";
option (no_delay) = true; option (no_delay) = true;
@@ -1974,7 +1899,6 @@ message TimeCommandRequest {
// ==================== EVENT ==================== // ==================== EVENT ====================
message ListEntitiesEventResponse { message ListEntitiesEventResponse {
option (id) = 107; option (id) = 107;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_EVENT"; option (ifdef) = "USE_EVENT";
@@ -1989,11 +1913,9 @@ message ListEntitiesEventResponse {
string device_class = 8; string device_class = 8;
repeated string event_types = 9; repeated string event_types = 9;
uint32 device_id = 10;
} }
message EventResponse { message EventResponse {
option (id) = 108; option (id) = 108;
option (base_class) = "StateResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_EVENT"; option (ifdef) = "USE_EVENT";
@@ -2004,7 +1926,6 @@ message EventResponse {
// ==================== VALVE ==================== // ==================== VALVE ====================
message ListEntitiesValveResponse { message ListEntitiesValveResponse {
option (id) = 109; option (id) = 109;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_VALVE"; option (ifdef) = "USE_VALVE";
@@ -2021,7 +1942,6 @@ message ListEntitiesValveResponse {
bool assumed_state = 9; bool assumed_state = 9;
bool supports_position = 10; bool supports_position = 10;
bool supports_stop = 11; bool supports_stop = 11;
uint32 device_id = 12;
} }
enum ValveOperation { enum ValveOperation {
@@ -2031,7 +1951,6 @@ enum ValveOperation {
} }
message ValveStateResponse { message ValveStateResponse {
option (id) = 110; option (id) = 110;
option (base_class) = "StateResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_VALVE"; option (ifdef) = "USE_VALVE";
option (no_delay) = true; option (no_delay) = true;
@@ -2056,7 +1975,6 @@ message ValveCommandRequest {
// ==================== DATETIME DATETIME ==================== // ==================== DATETIME DATETIME ====================
message ListEntitiesDateTimeResponse { message ListEntitiesDateTimeResponse {
option (id) = 112; option (id) = 112;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_DATETIME_DATETIME"; option (ifdef) = "USE_DATETIME_DATETIME";
@@ -2068,11 +1986,9 @@ message ListEntitiesDateTimeResponse {
string icon = 5; string icon = 5;
bool disabled_by_default = 6; bool disabled_by_default = 6;
EntityCategory entity_category = 7; EntityCategory entity_category = 7;
uint32 device_id = 8;
} }
message DateTimeStateResponse { message DateTimeStateResponse {
option (id) = 113; option (id) = 113;
option (base_class) = "StateResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_DATETIME_DATETIME"; option (ifdef) = "USE_DATETIME_DATETIME";
option (no_delay) = true; option (no_delay) = true;
@@ -2096,7 +2012,6 @@ message DateTimeCommandRequest {
// ==================== UPDATE ==================== // ==================== UPDATE ====================
message ListEntitiesUpdateResponse { message ListEntitiesUpdateResponse {
option (id) = 116; option (id) = 116;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_UPDATE"; option (ifdef) = "USE_UPDATE";
@@ -2109,11 +2024,9 @@ message ListEntitiesUpdateResponse {
bool disabled_by_default = 6; bool disabled_by_default = 6;
EntityCategory entity_category = 7; EntityCategory entity_category = 7;
string device_class = 8; string device_class = 8;
uint32 device_id = 9;
} }
message UpdateStateResponse { message UpdateStateResponse {
option (id) = 117; option (id) = 117;
option (base_class) = "StateResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_UPDATE"; option (ifdef) = "USE_UPDATE";
option (no_delay) = true; option (no_delay) = true;

File diff suppressed because it is too large Load Diff

View File

@@ -8,21 +8,54 @@
#include "api_server.h" #include "api_server.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
#include <vector> #include <vector>
#include <functional>
namespace esphome { namespace esphome {
namespace api { namespace api {
// Keepalive timeout in milliseconds using send_message_t = bool(APIConnection *, void *);
static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000;
/*
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 { class APIConnection : public APIServerConnection {
public: public:
friend class APIServer;
friend class ListEntitiesIterator;
APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent); APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent);
virtual ~APIConnection(); virtual ~APIConnection();
@@ -30,86 +63,149 @@ class APIConnection : public APIServerConnection {
void loop(); void loop();
bool send_list_info_done() { bool send_list_info_done() {
return this->schedule_message_(nullptr, &APIConnection::try_send_list_info_done, ListEntitiesDoneResponse resp;
ListEntitiesDoneResponse::MESSAGE_TYPE); return this->send_list_entities_done_response(resp);
} }
#ifdef USE_BINARY_SENSOR #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 #endif
#ifdef USE_COVER #ifdef USE_COVER
bool send_cover_state(cover::Cover *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; void cover_command(const CoverCommandRequest &msg) override;
#endif #endif
#ifdef USE_FAN #ifdef USE_FAN
bool send_fan_state(fan::Fan *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; void fan_command(const FanCommandRequest &msg) override;
#endif #endif
#ifdef USE_LIGHT #ifdef USE_LIGHT
bool send_light_state(light::LightState *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; void light_command(const LightCommandRequest &msg) override;
#endif #endif
#ifdef USE_SENSOR #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 #endif
#ifdef USE_SWITCH #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; void switch_command(const SwitchCommandRequest &msg) override;
#endif #endif
#ifdef USE_TEXT_SENSOR #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 #endif
#ifdef USE_ESP32_CAMERA #ifdef USE_ESP32_CAMERA
void set_camera_state(std::shared_ptr<esp32_camera::CameraImage> image); 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; void camera_image(const CameraImageRequest &msg) override;
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
bool send_climate_state(climate::Climate *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; void climate_command(const ClimateCommandRequest &msg) override;
#endif #endif
#ifdef USE_NUMBER #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; void number_command(const NumberCommandRequest &msg) override;
#endif #endif
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
bool send_date_state(datetime::DateEntity *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; void date_command(const DateCommandRequest &msg) override;
#endif #endif
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
bool send_time_state(datetime::TimeEntity *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; void time_command(const TimeCommandRequest &msg) override;
#endif #endif
#ifdef USE_DATETIME_DATETIME #ifdef USE_DATETIME_DATETIME
bool send_datetime_state(datetime::DateTimeEntity *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; void datetime_command(const DateTimeCommandRequest &msg) override;
#endif #endif
#ifdef USE_TEXT #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; void text_command(const TextCommandRequest &msg) override;
#endif #endif
#ifdef USE_SELECT #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; void select_command(const SelectCommandRequest &msg) override;
#endif #endif
#ifdef USE_BUTTON #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; void button_command(const ButtonCommandRequest &msg) override;
#endif #endif
#ifdef USE_LOCK #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; void lock_command(const LockCommandRequest &msg) override;
#endif #endif
#ifdef USE_VALVE #ifdef USE_VALVE
bool send_valve_state(valve::Valve *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; void valve_command(const ValveCommandRequest &msg) override;
#endif #endif
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
bool send_media_player_state(media_player::MediaPlayer *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; void media_player_command(const MediaPlayerCommandRequest &msg) override;
#endif #endif
bool try_send_log_message(int level, const char *tag, const char *line); bool try_send_log_message(int level, const char *tag, const char *line);
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
if (!this->flags_.service_call_subscription) if (!this->service_call_subscription_)
return; return;
this->send_message(call); this->send_homeassistant_service_response(call);
} }
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
@@ -131,7 +227,7 @@ class APIConnection : public APIServerConnection {
#ifdef USE_HOMEASSISTANT_TIME #ifdef USE_HOMEASSISTANT_TIME
void send_time_request() { void send_time_request() {
GetTimeRequest req; GetTimeRequest req;
this->send_message(req); this->send_get_time_request(req);
} }
#endif #endif
@@ -149,22 +245,33 @@ class APIConnection : public APIServerConnection {
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_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; void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
#endif #endif
#ifdef USE_EVENT #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 #endif
#ifdef USE_UPDATE #ifdef USE_UPDATE
bool send_update_state(update::UpdateEntity *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; void update_command(const UpdateCommandRequest &msg) override;
#endif #endif
void on_disconnect_response(const DisconnectResponse &value) override; void on_disconnect_response(const DisconnectResponse &value) override;
void on_ping_response(const PingResponse &value) override { void on_ping_response(const PingResponse &value) override {
// we initiated ping // 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; void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override;
#ifdef USE_HOMEASSISTANT_TIME #ifdef USE_HOMEASSISTANT_TIME
@@ -177,16 +284,16 @@ class APIConnection : public APIServerConnection {
DeviceInfoResponse device_info(const DeviceInfoRequest &msg) override; DeviceInfoResponse device_info(const DeviceInfoRequest &msg) override;
void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); } void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); }
void subscribe_states(const SubscribeStatesRequest &msg) override { void subscribe_states(const SubscribeStatesRequest &msg) override {
this->flags_.state_subscription = true; this->state_subscription_ = true;
this->initial_state_iterator_.begin(); this->initial_state_iterator_.begin();
} }
void subscribe_logs(const SubscribeLogsRequest &msg) override { void subscribe_logs(const SubscribeLogsRequest &msg) override {
this->flags_.log_subscription = msg.level; this->log_subscription_ = msg.level;
if (msg.dump_config) if (msg.dump_config)
App.schedule_dump_config(); App.schedule_dump_config();
} }
void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) override { 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; void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override;
GetTimeResponse get_time(const GetTimeRequest &msg) override { GetTimeResponse get_time(const GetTimeRequest &msg) override {
@@ -198,438 +305,63 @@ class APIConnection : public APIServerConnection {
NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) override; NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) override;
#endif #endif
bool is_authenticated() override { bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; }
return static_cast<ConnectionState>(this->flags_.connection_state) == ConnectionState::AUTHENTICATED;
}
bool is_connection_setup() override { bool is_connection_setup() override {
return static_cast<ConnectionState>(this->flags_.connection_state) == ConnectionState::CONNECTED || return this->connection_state_ == ConnectionState ::CONNECTED || this->is_authenticated();
this->is_authenticated();
} }
void on_fatal_error() override; void on_fatal_error() override;
void on_unauthenticated_access() override; void on_unauthenticated_access() override;
void on_no_setup_connection() override; void on_no_setup_connection() override;
ProtoWriteBuffer create_buffer(uint32_t reserve_size) override { ProtoWriteBuffer create_buffer(uint32_t reserve_size) override {
// FIXME: ensure no recursive writes can happen // FIXME: ensure no recursive writes can happen
this->proto_write_buffer_.clear();
// Get header padding size - used for both reserve and insert this->proto_write_buffer_.reserve(reserve_size);
uint8_t header_padding = this->helper_->frame_header_padding(); return {&this->proto_write_buffer_};
// 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};
} }
bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) override;
// Prepare buffer for next message in batch std::string get_client_combined_info() const { return this->client_combined_info_; }
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, uint16_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);
protected: protected:
// Helper function to fill common entity info fields friend APIServer;
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();
if (entity->has_own_name()) bool send_(const void *buf, size_t len, bool force);
response.name = entity->get_name();
// Set common EntityBase properties enum class ConnectionState {
response.icon = entity->get_icon(); WAITING_FOR_HELLO,
response.disabled_by_default = entity->is_disabled_by_default(); CONNECTED,
response.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category()); AUTHENTICATED,
#ifdef USE_DEVICES } connection_state_{ConnectionState::WAITING_FOR_HELLO};
response.device_id = entity->get_device_id();
#endif
}
// Helper function to fill common entity state fields bool remove_{false};
static void fill_entity_state_base(esphome::EntityBase *entity, StateResponseProtoMessage &response) {
response.key = entity->get_object_id_hash();
}
// Non-template helper to encode any ProtoMessage // Buffer used to encode proto messages
static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint16_t message_type, APIConnection *conn, // Re-use to prevent allocations
uint32_t remaining_size, bool is_single); std::vector<uint8_t> proto_write_buffer_;
#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_ESP32_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);
// Helper function to get estimated message size for buffer pre-allocation
static uint16_t get_estimated_message_size(uint16_t message_type);
// 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)
std::unique_ptr<APIFrameHelper> helper_; std::unique_ptr<APIFrameHelper> helper_;
APIServer *parent_;
// Group 2: Larger objects (must be 4-byte aligned) std::string client_info_;
// These contain vectors/pointers internally, so putting them early ensures good alignment std::string client_peername_;
InitialStateIterator initial_state_iterator_; std::string client_combined_info_;
ListEntitiesIterator list_entities_iterator_; uint32_t client_api_version_major_{0};
uint32_t client_api_version_minor_{0};
#ifdef USE_ESP32_CAMERA #ifdef USE_ESP32_CAMERA
esp32_camera::CameraImageReader image_reader_; esp32_camera::CameraImageReader image_reader_;
#endif #endif
// Group 3: Strings (12 bytes each on 32-bit, 4-byte aligned) bool state_subscription_{false};
std::string client_info_; int log_subscription_{ESPHOME_LOG_LEVEL_NONE};
std::string client_peername_;
// Group 4: 4-byte types
uint32_t last_traffic_; 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; 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,
uint16_t message_type) const;
// Manual cleanup method - must be called before destruction for string types
void cleanup(uint16_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
uint16_t message_type; // Message type for overhead calculation
// Constructor for creating BatchItem
BatchItem(EntityBase *entity, MessageCreator creator, uint16_t message_type)
: entity(entity), creator(std::move(creator)), message_type(message_type) {}
};
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, uint16_t message_type);
// Add item to the front of the batch (for high priority messages like ping)
void add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type);
// 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
#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_PACKET_SIZE = 1390; // MTU
bool schedule_batch_();
void process_batch_();
#ifdef HAS_PROTO_MESSAGE_DUMP
void log_batch_item_(const DeferredBatch::BatchItem &item);
#endif
// Helper function to schedule a deferred message with known message type
bool schedule_message_(EntityBase *entity, MessageCreator creator, uint16_t message_type) {
this->deferred_batch_.add_item(entity, std::move(creator), message_type);
return this->schedule_batch_();
}
// Overload for function pointers (for info messages and current state reads)
bool schedule_message_(EntityBase *entity, MessageCreatorPtr function_ptr, uint16_t message_type) {
return schedule_message_(entity, MessageCreator(function_ptr), message_type);
}
// Helper function to schedule a high priority message at the front of the batch
bool schedule_message_front_(EntityBase *entity, MessageCreatorPtr function_ptr, uint16_t message_type) {
this->deferred_batch_.add_item_front(entity, MessageCreator(function_ptr), message_type);
return this->schedule_batch_();
}
}; };
} // namespace api } // namespace api

View File

@@ -1,19 +1,26 @@
#include "api_frame_helper.h" #include "api_frame_helper.h"
#ifdef USE_API #ifdef USE_API
#include "esphome/core/application.h" #include "esphome/core/log.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/application.h"
#include "proto.h" #include "proto.h"
#include "api_pb2_size.h" #include "api_pb2_size.h"
#include <cstring> #include <cstring>
#include <cinttypes>
namespace esphome { namespace esphome {
namespace api { namespace api {
static const char *const TAG = "api.socket"; static const char *const TAG = "api.socket";
/// Is the given return value (from write syscalls) a wouldblock error?
bool is_would_block(ssize_t ret) {
if (ret == -1) {
return errno == EWOULDBLOCK || errno == EAGAIN;
}
return ret == 0;
}
const char *api_error_to_str(APIError err) { const char *api_error_to_str(APIError err) {
// not using switch to ensure compiler doesn't try to build a big table out of it // not using switch to ensure compiler doesn't try to build a big table out of it
if (err == APIError::OK) { if (err == APIError::OK) {
@@ -66,165 +73,92 @@ const char *api_error_to_str(APIError err) {
return "UNKNOWN"; return "UNKNOWN";
} }
// Default implementation for loop - handles sending buffered data // Common implementation for writing raw data to socket
APIError APIFrameHelper::loop() { template<typename StateEnum>
if (!this->tx_buf_.empty()) { APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket,
APIError err = try_send_tx_buf_(); std::vector<uint8_t> &tx_buf, const std::string &info, StateEnum &state,
if (err != APIError::OK && err != APIError::WOULD_BLOCK) { StateEnum failed_state) {
return err; // This method writes data to socket or buffers it
}
}
return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
}
// Helper method to buffer data from IOVs
void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) {
SendBuffer buffer;
buffer.data.reserve(total_write_len);
for (int i = 0; i < iovcnt; i++) {
const uint8_t *data = reinterpret_cast<uint8_t *>(iov[i].iov_base);
buffer.data.insert(buffer.data.end(), data, data + iov[i].iov_len);
}
this->tx_buf_.push_back(std::move(buffer));
}
// This method writes data to socket or buffers it
APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
// Returns APIError::OK if successful (or would block, but data has been buffered) // Returns APIError::OK if successful (or would block, but data has been buffered)
// Returns APIError::SOCKET_WRITE_FAILED if socket write failed, and sets state to FAILED // Returns APIError::SOCKET_WRITE_FAILED if socket write failed, and sets state to failed_state
if (iovcnt == 0) if (iovcnt == 0)
return APIError::OK; // Nothing to do, success return APIError::OK; // Nothing to do, success
uint16_t total_write_len = 0; size_t total_write_len = 0;
for (int i = 0; i < iovcnt; i++) { for (int i = 0; i < iovcnt; i++) {
#ifdef HELPER_LOG_PACKETS #ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Sending raw: %s", ESP_LOGVV(TAG, "Sending raw: %s",
format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str()); format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
#endif #endif
total_write_len += static_cast<uint16_t>(iov[i].iov_len); total_write_len += iov[i].iov_len;
} }
// Try to send any existing buffered data first if there is any if (!tx_buf.empty()) {
if (!this->tx_buf_.empty()) { // try to empty tx_buf first
APIError send_result = try_send_tx_buf_(); while (!tx_buf.empty()) {
// If real error occurred (not just WOULD_BLOCK), return it ssize_t sent = socket->write(tx_buf.data(), tx_buf.size());
if (send_result != APIError::OK && send_result != APIError::WOULD_BLOCK) { if (is_would_block(sent)) {
return send_result; break;
} } else if (sent == -1) {
ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", info.c_str(), errno);
// If there is still data in the buffer, we can't send, buffer state = failed_state;
// the new data and return return APIError::SOCKET_WRITE_FAILED; // Socket write failed
if (!this->tx_buf_.empty()) { }
this->buffer_data_from_iov_(iov, iovcnt, total_write_len); // TODO: inefficient if multiple packets in txbuf
return APIError::OK; // Success, data buffered // replace with deque of buffers
tx_buf.erase(tx_buf.begin(), tx_buf.begin() + sent);
} }
} }
// Try to send directly if no buffered data if (!tx_buf.empty()) {
ssize_t sent = this->socket_->writev(iov, iovcnt); // tx buf not empty, can't write now because then stream would be inconsistent
// Reserve space upfront to avoid multiple reallocations
if (sent == -1) { tx_buf.reserve(tx_buf.size() + total_write_len);
if (errno == EWOULDBLOCK || errno == EAGAIN) { for (int i = 0; i < iovcnt; i++) {
// Socket would block, buffer the data tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
this->buffer_data_from_iov_(iov, iovcnt, total_write_len); reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
return APIError::OK; // Success, data buffered
} }
// Socket error return APIError::OK; // Success, data buffered
ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", this->info_.c_str(), errno); }
this->state_ = State::FAILED;
ssize_t sent = socket->writev(iov, iovcnt);
if (is_would_block(sent)) {
// operation would block, add buffer to tx_buf
// Reserve space upfront to avoid multiple reallocations
tx_buf.reserve(tx_buf.size() + total_write_len);
for (int i = 0; i < iovcnt; i++) {
tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
}
return APIError::OK; // Success, data buffered
} else if (sent == -1) {
// an error occurred
ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", info.c_str(), errno);
state = failed_state;
return APIError::SOCKET_WRITE_FAILED; // Socket write failed return APIError::SOCKET_WRITE_FAILED; // Socket write failed
} else if (static_cast<uint16_t>(sent) < total_write_len) { } else if ((size_t) sent != total_write_len) {
// Partially sent, buffer the remaining data // partially sent, add end to tx_buf
SendBuffer buffer; size_t remaining = total_write_len - sent;
uint16_t to_consume = static_cast<uint16_t>(sent); // Reserve space upfront to avoid multiple reallocations
uint16_t remaining = total_write_len - static_cast<uint16_t>(sent); tx_buf.reserve(tx_buf.size() + remaining);
buffer.data.reserve(remaining);
size_t to_consume = sent;
for (int i = 0; i < iovcnt; i++) { for (int i = 0; i < iovcnt; i++) {
if (to_consume >= iov[i].iov_len) { if (to_consume >= iov[i].iov_len) {
// This segment was fully sent to_consume -= iov[i].iov_len;
to_consume -= static_cast<uint16_t>(iov[i].iov_len);
} else { } else {
// This segment was partially sent or not sent at all tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
const uint8_t *data = reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume; reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
uint16_t len = static_cast<uint16_t>(iov[i].iov_len) - to_consume;
buffer.data.insert(buffer.data.end(), data, data + len);
to_consume = 0; to_consume = 0;
} }
} }
return APIError::OK; // Success, data buffered
this->tx_buf_.push_back(std::move(buffer));
} }
return APIError::OK; // Success, all data sent
return APIError::OK; // Success, all data sent or buffered
} }
// Common implementation for trying to send buffered data #define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__)
// IMPORTANT: Caller MUST ensure tx_buf_ is not empty before calling this method
APIError APIFrameHelper::try_send_tx_buf_() {
// Try to send from tx_buf - we assume it's not empty as it's the caller's responsibility to check
bool tx_buf_empty = false;
while (!tx_buf_empty) {
// Get the first buffer in the queue
SendBuffer &front_buffer = this->tx_buf_.front();
// Try to send the remaining data in this buffer
ssize_t sent = this->socket_->write(front_buffer.current_data(), front_buffer.remaining());
if (sent == -1) {
if (errno != EWOULDBLOCK && errno != EAGAIN) {
// Real socket error (not just would block)
ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", this->info_.c_str(), errno);
this->state_ = State::FAILED;
return APIError::SOCKET_WRITE_FAILED; // Socket write failed
}
// Socket would block, we'll try again later
return APIError::WOULD_BLOCK;
} else if (sent == 0) {
// Nothing sent but not an error
return APIError::WOULD_BLOCK;
} else if (static_cast<uint16_t>(sent) < front_buffer.remaining()) {
// Partially sent, update offset
// Cast to ensure no overflow issues with uint16_t
front_buffer.offset += static_cast<uint16_t>(sent);
return APIError::WOULD_BLOCK; // Stop processing more buffers if we couldn't send a complete buffer
} else {
// Buffer completely sent, remove it from the queue
this->tx_buf_.pop_front();
// Update empty status for the loop condition
tx_buf_empty = this->tx_buf_.empty();
// Continue loop to try sending the next buffer
}
}
return APIError::OK; // All buffers sent successfully
}
APIError APIFrameHelper::init_common_() {
if (state_ != State::INITIALIZE || this->socket_ == nullptr) {
ESP_LOGVV(TAG, "%s: Bad state for init %d", this->info_.c_str(), (int) state_);
return APIError::BAD_STATE;
}
int err = this->socket_->setblocking(false);
if (err != 0) {
state_ = State::FAILED;
ESP_LOGVV(TAG, "%s: Setting nonblocking failed with errno %d", this->info_.c_str(), errno);
return APIError::TCP_NONBLOCKING_FAILED;
}
int enable = 1;
err = this->socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
if (err != 0) {
state_ = State::FAILED;
ESP_LOGVV(TAG, "%s: Setting nodelay failed with errno %d", this->info_.c_str(), errno);
return APIError::TCP_NODELAY_FAILED;
}
return APIError::OK;
}
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->info_.c_str(), ##__VA_ARGS__)
// uncomment to log raw packets // uncomment to log raw packets
//#define HELPER_LOG_PACKETS //#define HELPER_LOG_PACKETS
@@ -272,9 +206,23 @@ std::string noise_err_to_str(int err) {
/// Initialize the frame helper, returns OK if successful. /// Initialize the frame helper, returns OK if successful.
APIError APINoiseFrameHelper::init() { APIError APINoiseFrameHelper::init() {
APIError err = init_common_(); if (state_ != State::INITIALIZE || socket_ == nullptr) {
if (err != APIError::OK) { HELPER_LOG("Bad state for init %d", (int) state_);
return err; return APIError::BAD_STATE;
}
int err = socket_->setblocking(false);
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("Setting nonblocking failed with errno %d", errno);
return APIError::TCP_NONBLOCKING_FAILED;
}
int enable = 1;
err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("Setting nodelay failed with errno %d", errno);
return APIError::TCP_NODELAY_FAILED;
} }
// init prologue // init prologue
@@ -285,21 +233,18 @@ APIError APINoiseFrameHelper::init() {
} }
/// Run through handshake messages (if in that phase) /// Run through handshake messages (if in that phase)
APIError APINoiseFrameHelper::loop() { APIError APINoiseFrameHelper::loop() {
// During handshake phase, process as many actions as possible until we can't progress APIError err = state_action_();
// socket_->ready() stays true until next main loop, but state_action() will return if (err == APIError::WOULD_BLOCK)
// WOULD_BLOCK when no more data is available to read return APIError::OK;
while (state_ != State::DATA && this->socket_->ready()) { if (err != APIError::OK)
APIError err = state_action_(); return err;
if (err != APIError::OK && err != APIError::WOULD_BLOCK) { if (!tx_buf_.empty()) {
err = try_send_tx_buf_();
if (err != APIError::OK) {
return err; return err;
} }
if (err == APIError::WOULD_BLOCK) {
break;
}
} }
return APIError::OK;
// Use base class implementation for buffer sending
return APIFrameHelper::loop();
} }
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter /** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
@@ -325,8 +270,8 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
// read header // read header
if (rx_header_buf_len_ < 3) { if (rx_header_buf_len_ < 3) {
// no header information yet // no header information yet
uint8_t to_read = 3 - rx_header_buf_len_; size_t to_read = 3 - rx_header_buf_len_;
ssize_t received = this->socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read); ssize_t received = socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
if (received == -1) { if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) { if (errno == EWOULDBLOCK || errno == EAGAIN) {
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
@@ -339,21 +284,23 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
HELPER_LOG("Connection closed"); HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED; return APIError::CONNECTION_CLOSED;
} }
rx_header_buf_len_ += static_cast<uint8_t>(received); rx_header_buf_len_ += received;
if (static_cast<uint8_t>(received) != to_read) { if ((size_t) received != to_read) {
// not a full read // not a full read
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
} }
if (rx_header_buf_[0] != 0x01) {
state_ = State::FAILED;
HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
return APIError::BAD_INDICATOR;
}
// header reading done // header reading done
} }
// read body // read body
uint8_t indicator = rx_header_buf_[0];
if (indicator != 0x01) {
state_ = State::FAILED;
HELPER_LOG("Bad indicator byte %u", indicator);
return APIError::BAD_INDICATOR;
}
uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2]; uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2];
if (state_ != State::DATA && msg_size > 128) { if (state_ != State::DATA && msg_size > 128) {
@@ -370,8 +317,8 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
if (rx_buf_len_ < msg_size) { if (rx_buf_len_ < msg_size) {
// more data to read // more data to read
uint16_t to_read = msg_size - rx_buf_len_; size_t to_read = msg_size - rx_buf_len_;
ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read); ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read);
if (received == -1) { if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) { if (errno == EWOULDBLOCK || errno == EAGAIN) {
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
@@ -384,8 +331,8 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
HELPER_LOG("Connection closed"); HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED; return APIError::CONNECTION_CLOSED;
} }
rx_buf_len_ += static_cast<uint16_t>(received); rx_buf_len_ += received;
if (static_cast<uint16_t>(received) != to_read) { if ((size_t) received != to_read) {
// not all read // not all read
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
} }
@@ -434,8 +381,6 @@ APIError APINoiseFrameHelper::state_action_() {
if (aerr != APIError::OK) if (aerr != APIError::OK)
return aerr; return aerr;
// ignore contents, may be used in future for flags // ignore contents, may be used in future for flags
// Reserve space for: existing prologue + 2 size bytes + frame data
prologue_.reserve(prologue_.size() + 2 + frame.msg.size());
prologue_.push_back((uint8_t) (frame.msg.size() >> 8)); prologue_.push_back((uint8_t) (frame.msg.size() >> 8));
prologue_.push_back((uint8_t) frame.msg.size()); prologue_.push_back((uint8_t) frame.msg.size());
prologue_.insert(prologue_.end(), frame.msg.begin(), frame.msg.end()); prologue_.insert(prologue_.end(), frame.msg.begin(), frame.msg.end());
@@ -444,20 +389,16 @@ APIError APINoiseFrameHelper::state_action_() {
} }
if (state_ == State::SERVER_HELLO) { if (state_ == State::SERVER_HELLO) {
// send server hello // send server hello
const std::string &name = App.get_name();
const std::string &mac = get_mac_address();
std::vector<uint8_t> msg; std::vector<uint8_t> msg;
// Reserve space for: 1 byte proto + name + null + mac + null
msg.reserve(1 + name.size() + 1 + mac.size() + 1);
// chosen proto // chosen proto
msg.push_back(0x01); msg.push_back(0x01);
// node name, terminated by null byte // node name, terminated by null byte
const std::string &name = App.get_name();
const uint8_t *name_ptr = reinterpret_cast<const uint8_t *>(name.c_str()); const uint8_t *name_ptr = reinterpret_cast<const uint8_t *>(name.c_str());
msg.insert(msg.end(), name_ptr, name_ptr + name.size() + 1); msg.insert(msg.end(), name_ptr, name_ptr + name.size() + 1);
// node mac, terminated by null byte // node mac, terminated by null byte
const std::string &mac = get_mac_address();
const uint8_t *mac_ptr = reinterpret_cast<const uint8_t *>(mac.c_str()); const uint8_t *mac_ptr = reinterpret_cast<const uint8_t *>(mac.c_str());
msg.insert(msg.end(), mac_ptr, mac_ptr + mac.size() + 1); msg.insert(msg.end(), mac_ptr, mac_ptr + mac.size() + 1);
@@ -552,18 +493,16 @@ void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &rea
std::vector<uint8_t> data; std::vector<uint8_t> data;
data.resize(reason.length() + 1); data.resize(reason.length() + 1);
data[0] = 0x01; // failure data[0] = 0x01; // failure
for (size_t i = 0; i < reason.length(); i++) {
// Copy error message in bulk data[i + 1] = (uint8_t) reason[i];
if (!reason.empty()) {
std::memcpy(data.data() + 1, reason.c_str(), reason.length());
} }
// temporarily remove failed state // temporarily remove failed state
auto orig_state = state_; auto orig_state = state_;
state_ = State::EXPLICIT_REJECT; state_ = State::EXPLICIT_REJECT;
write_frame_(data.data(), data.size()); write_frame_(data.data(), data.size());
state_ = orig_state; state_ = orig_state;
} }
APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) { APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
int err; int err;
APIError aerr; APIError aerr;
@@ -591,7 +530,7 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
return APIError::CIPHERSTATE_DECRYPT_FAILED; return APIError::CIPHERSTATE_DECRYPT_FAILED;
} }
uint16_t msg_size = mbuf.size; size_t msg_size = mbuf.size;
uint8_t *msg_data = frame.msg.data(); uint8_t *msg_data = frame.msg.data();
if (msg_size < 4) { if (msg_size < 4) {
state_ = State::FAILED; state_ = State::FAILED;
@@ -599,6 +538,10 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
return APIError::BAD_DATA_PACKET; return APIError::BAD_DATA_PACKET;
} }
// uint16_t type;
// uint16_t data_len;
// uint8_t *data;
// uint8_t *padding; zero or more bytes to fill up the rest of the packet
uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1]; uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1];
uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3]; uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3];
if (data_len > msg_size - 4) { if (data_len > msg_size - 4) {
@@ -613,22 +556,11 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
buffer->type = type; buffer->type = type;
return APIError::OK; return APIError::OK;
} }
APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) { bool APINoiseFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
std::vector<uint8_t> *raw_buffer = buffer.get_buffer(); APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
uint16_t payload_len = static_cast<uint16_t>(raw_buffer->size() - frame_header_padding_); int err;
APIError aerr;
// Resize to include MAC space (required for Noise encryption) aerr = state_action_();
raw_buffer->resize(raw_buffer->size() + frame_footer_size_);
// Use write_protobuf_packets with a single packet
std::vector<PacketInfo> packets;
packets.emplace_back(type, 0, payload_len);
return write_protobuf_packets(buffer, packets);
}
APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector<PacketInfo> &packets) {
APIError aerr = state_action_();
if (aerr != APIError::OK) { if (aerr != APIError::OK) {
return aerr; return aerr;
} }
@@ -637,67 +569,70 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, co
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
} }
if (packets.empty()) { size_t padding = 0;
return APIError::OK; size_t msg_len = 4 + payload_len + padding;
size_t frame_len = 3 + msg_len + noise_cipherstate_get_mac_length(send_cipher_);
auto tmpbuf = std::unique_ptr<uint8_t[]>{new (std::nothrow) uint8_t[frame_len]};
if (tmpbuf == nullptr) {
HELPER_LOG("Could not allocate for writing packet");
return APIError::OUT_OF_MEMORY;
} }
std::vector<uint8_t> *raw_buffer = buffer.get_buffer(); tmpbuf[0] = 0x01; // indicator
this->reusable_iovs_.clear(); // tmpbuf[1], tmpbuf[2] to be set later
this->reusable_iovs_.reserve(packets.size()); const uint8_t msg_offset = 3;
const uint8_t payload_offset = msg_offset + 4;
tmpbuf[msg_offset + 0] = (uint8_t) (type >> 8); // type
tmpbuf[msg_offset + 1] = (uint8_t) type;
tmpbuf[msg_offset + 2] = (uint8_t) (payload_len >> 8); // data_len
tmpbuf[msg_offset + 3] = (uint8_t) payload_len;
// copy data
std::copy(payload, payload + payload_len, &tmpbuf[payload_offset]);
// fill padding with zeros
std::fill(&tmpbuf[payload_offset + payload_len], &tmpbuf[frame_len], 0);
// We need to encrypt each packet in place NoiseBuffer mbuf;
for (const auto &packet : packets) { noise_buffer_init(mbuf);
uint16_t type = packet.message_type; noise_buffer_set_inout(mbuf, &tmpbuf[msg_offset], msg_len, frame_len - msg_offset);
uint16_t offset = packet.offset; err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
uint16_t payload_len = packet.payload_size; if (err != 0) {
uint16_t msg_len = 4 + payload_len; // type(2) + data_len(2) + payload state_ = State::FAILED;
HELPER_LOG("noise_cipherstate_encrypt failed: %s", noise_err_to_str(err).c_str());
// The buffer already has padding at offset return APIError::CIPHERSTATE_ENCRYPT_FAILED;
uint8_t *buf_start = raw_buffer->data() + offset;
// Write noise header
buf_start[0] = 0x01; // indicator
// buf_start[1], buf_start[2] to be set after encryption
// Write message header (to be encrypted)
const uint8_t msg_offset = 3;
buf_start[msg_offset + 0] = (uint8_t) (type >> 8); // type high byte
buf_start[msg_offset + 1] = (uint8_t) type; // type low byte
buf_start[msg_offset + 2] = (uint8_t) (payload_len >> 8); // data_len high byte
buf_start[msg_offset + 3] = (uint8_t) payload_len; // data_len low byte
// payload data is already in the buffer starting at offset + 7
// Make sure we have space for MAC
// The buffer should already have been sized appropriately
// Encrypt the message in place
NoiseBuffer mbuf;
noise_buffer_init(mbuf);
noise_buffer_set_inout(mbuf, buf_start + msg_offset, msg_len, msg_len + frame_footer_size_);
int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("noise_cipherstate_encrypt failed: %s", noise_err_to_str(err).c_str());
return APIError::CIPHERSTATE_ENCRYPT_FAILED;
}
// Fill in the encrypted size
buf_start[1] = (uint8_t) (mbuf.size >> 8);
buf_start[2] = (uint8_t) mbuf.size;
// Add iovec for this encrypted packet
struct iovec iov;
iov.iov_base = buf_start;
iov.iov_len = 3 + mbuf.size; // indicator + size + encrypted data
this->reusable_iovs_.push_back(iov);
} }
// Send all encrypted packets in one writev call size_t total_len = 3 + mbuf.size;
return this->write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size()); tmpbuf[1] = (uint8_t) (mbuf.size >> 8);
tmpbuf[2] = (uint8_t) mbuf.size;
struct iovec iov;
iov.iov_base = &tmpbuf[0];
iov.iov_len = total_len;
// write raw to not have two packets sent if NAGLE disabled
return write_raw_(&iov, 1);
} }
APIError APINoiseFrameHelper::try_send_tx_buf_() {
// try send from tx_buf
while (state_ != State::CLOSED && !tx_buf_.empty()) {
ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size());
if (sent == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN)
break;
state_ = State::FAILED;
HELPER_LOG("Socket write failed with errno %d", errno);
return APIError::SOCKET_WRITE_FAILED;
} else if (sent == 0) {
break;
}
// TODO: inefficient if multiple packets in txbuf
// replace with deque of buffers
tx_buf_.erase(tx_buf_.begin(), tx_buf_.begin() + sent);
}
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) { return APIError::OK;
}
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
uint8_t header[3]; uint8_t header[3];
header[0] = 0x01; // indicator header[0] = 0x01; // indicator
header[1] = (uint8_t) (len >> 8); header[1] = (uint8_t) (len >> 8);
@@ -707,12 +642,12 @@ APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) {
iov[0].iov_base = header; iov[0].iov_base = header;
iov[0].iov_len = 3; iov[0].iov_len = 3;
if (len == 0) { if (len == 0) {
return this->write_raw_(iov, 1); return write_raw_(iov, 1);
} }
iov[1].iov_base = const_cast<uint8_t *>(data); iov[1].iov_base = const_cast<uint8_t *>(data);
iov[1].iov_len = len; iov[1].iov_len = len;
return this->write_raw_(iov, 2); return write_raw_(iov, 2);
} }
/** Initiate the data structures for the handshake. /** Initiate the data structures for the handshake.
@@ -783,8 +718,6 @@ APIError APINoiseFrameHelper::check_handshake_finished_() {
return APIError::HANDSHAKESTATE_SPLIT_FAILED; return APIError::HANDSHAKESTATE_SPLIT_FAILED;
} }
frame_footer_size_ = noise_cipherstate_get_mac_length(send_cipher_);
HELPER_LOG("Handshake complete!"); HELPER_LOG("Handshake complete!");
noise_handshakestate_free(handshake_); noise_handshakestate_free(handshake_);
handshake_ = nullptr; handshake_ = nullptr;
@@ -807,36 +740,76 @@ APINoiseFrameHelper::~APINoiseFrameHelper() {
} }
} }
APIError APINoiseFrameHelper::close() {
state_ = State::CLOSED;
int err = socket_->close();
if (err == -1)
return APIError::CLOSE_FAILED;
return APIError::OK;
}
APIError APINoiseFrameHelper::shutdown(int how) {
int err = socket_->shutdown(how);
if (err == -1)
return APIError::SHUTDOWN_FAILED;
if (how == SHUT_RDWR) {
state_ = State::CLOSED;
}
return APIError::OK;
}
extern "C" { extern "C" {
// declare how noise generates random bytes (here with a good HWRNG based on the RF system) // declare how noise generates random bytes (here with a good HWRNG based on the RF system)
void noise_rand_bytes(void *output, size_t len) { void noise_rand_bytes(void *output, size_t len) {
if (!esphome::random_bytes(reinterpret_cast<uint8_t *>(output), len)) { if (!esphome::random_bytes(reinterpret_cast<uint8_t *>(output), len)) {
ESP_LOGE(TAG, "Acquiring random bytes failed; rebooting"); ESP_LOGE(TAG, "Failed to acquire random bytes, rebooting!");
arch_restart(); arch_restart();
} }
} }
} }
// Explicit template instantiation for Noise
template APIError APIFrameHelper::write_raw_<APINoiseFrameHelper::State>(
const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf_, const std::string &info,
APINoiseFrameHelper::State &state, APINoiseFrameHelper::State failed_state);
#endif // USE_API_NOISE #endif // USE_API_NOISE
#ifdef USE_API_PLAINTEXT #ifdef USE_API_PLAINTEXT
/// Initialize the frame helper, returns OK if successful. /// Initialize the frame helper, returns OK if successful.
APIError APIPlaintextFrameHelper::init() { APIError APIPlaintextFrameHelper::init() {
APIError err = init_common_(); if (state_ != State::INITIALIZE || socket_ == nullptr) {
if (err != APIError::OK) { HELPER_LOG("Bad state for init %d", (int) state_);
return err; return APIError::BAD_STATE;
}
int err = socket_->setblocking(false);
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("Setting nonblocking failed with errno %d", errno);
return APIError::TCP_NONBLOCKING_FAILED;
}
int enable = 1;
err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
if (err != 0) {
state_ = State::FAILED;
HELPER_LOG("Setting nodelay failed with errno %d", errno);
return APIError::TCP_NODELAY_FAILED;
} }
state_ = State::DATA; state_ = State::DATA;
return APIError::OK; return APIError::OK;
} }
/// Not used for plaintext
APIError APIPlaintextFrameHelper::loop() { APIError APIPlaintextFrameHelper::loop() {
if (state_ != State::DATA) { if (state_ != State::DATA) {
return APIError::BAD_STATE; return APIError::BAD_STATE;
} }
// Use base class implementation for buffer sending // try send pending TX data
return APIFrameHelper::loop(); if (!tx_buf_.empty()) {
APIError err = try_send_tx_buf_();
if (err != APIError::OK) {
return err;
}
}
return APIError::OK;
} }
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter /** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
@@ -856,15 +829,8 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
// read header // read header
while (!rx_header_parsed_) { while (!rx_header_parsed_) {
// Now that we know when the socket is ready, we can read up to 3 bytes uint8_t data;
// into the rx_header_buf_ before we have to switch back to reading ssize_t received = socket_->read(&data, 1);
// one byte at a time to ensure we don't read past the message and
// into the next one.
// Read directly into rx_header_buf_ at the current position
// Try to get to at least 3 bytes total (indicator + 2 varint bytes), then read one byte at a time
ssize_t received =
this->socket_->read(&rx_header_buf_[rx_header_buf_pos_], rx_header_buf_pos_ < 3 ? 3 - rx_header_buf_pos_ : 1);
if (received == -1) { if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) { if (errno == EWOULDBLOCK || errno == EAGAIN) {
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
@@ -877,75 +843,32 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
HELPER_LOG("Connection closed"); HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED; return APIError::CONNECTION_CLOSED;
} }
rx_header_buf_.push_back(data);
// If this was the first read, validate the indicator byte // try parse header
if (rx_header_buf_pos_ == 0 && received > 0) { if (rx_header_buf_[0] != 0x00) {
if (rx_header_buf_[0] != 0x00) {
state_ = State::FAILED;
HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
return APIError::BAD_INDICATOR;
}
}
rx_header_buf_pos_ += received;
// Check for buffer overflow
if (rx_header_buf_pos_ >= sizeof(rx_header_buf_)) {
state_ = State::FAILED; state_ = State::FAILED;
HELPER_LOG("Header buffer overflow"); HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
return APIError::BAD_DATA_PACKET; return APIError::BAD_INDICATOR;
} }
// Need at least 3 bytes total (indicator + 2 varint bytes) before trying to parse size_t i = 1;
if (rx_header_buf_pos_ < 3) {
continue;
}
// At this point, we have at least 3 bytes total:
// - Validated indicator byte (0x00) stored at position 0
// - At least 2 bytes in the buffer for the varints
// Buffer layout:
// [0]: indicator byte (0x00)
// [1-3]: Message size varint (variable length)
// - 2 bytes would only allow up to 16383, which is less than noise's UINT16_MAX (65535)
// - 3 bytes allows up to 2097151, ensuring we support at least as much as noise
// [2-5]: Message type varint (variable length)
// We now attempt to parse both varints. If either is incomplete,
// we'll continue reading more bytes.
// Skip indicator byte at position 0
uint8_t varint_pos = 1;
uint32_t consumed = 0; uint32_t consumed = 0;
auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed);
auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[varint_pos], rx_header_buf_pos_ - varint_pos, &consumed);
if (!msg_size_varint.has_value()) { if (!msg_size_varint.has_value()) {
// not enough data there yet // not enough data there yet
continue; continue;
} }
if (msg_size_varint->as_uint32() > std::numeric_limits<uint16_t>::max()) { i += consumed;
state_ = State::FAILED; rx_header_parsed_len_ = msg_size_varint->as_uint32();
HELPER_LOG("Bad packet: message size %" PRIu32 " exceeds maximum %u", msg_size_varint->as_uint32(),
std::numeric_limits<uint16_t>::max());
return APIError::BAD_DATA_PACKET;
}
rx_header_parsed_len_ = msg_size_varint->as_uint16();
// Move to next varint position auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[i], rx_header_buf_.size() - i, &consumed);
varint_pos += consumed;
auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[varint_pos], rx_header_buf_pos_ - varint_pos, &consumed);
if (!msg_type_varint.has_value()) { if (!msg_type_varint.has_value()) {
// not enough data there yet // not enough data there yet
continue; continue;
} }
if (msg_type_varint->as_uint32() > std::numeric_limits<uint16_t>::max()) { rx_header_parsed_type_ = msg_type_varint->as_uint32();
state_ = State::FAILED;
HELPER_LOG("Bad packet: message type %" PRIu32 " exceeds maximum %u", msg_type_varint->as_uint32(),
std::numeric_limits<uint16_t>::max());
return APIError::BAD_DATA_PACKET;
}
rx_header_parsed_type_ = msg_type_varint->as_uint16();
rx_header_parsed_ = true; rx_header_parsed_ = true;
} }
// header reading done // header reading done
@@ -957,8 +880,8 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
if (rx_buf_len_ < rx_header_parsed_len_) { if (rx_buf_len_ < rx_header_parsed_len_) {
// more data to read // more data to read
uint16_t to_read = rx_header_parsed_len_ - rx_buf_len_; size_t to_read = rx_header_parsed_len_ - rx_buf_len_;
ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read); ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read);
if (received == -1) { if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) { if (errno == EWOULDBLOCK || errno == EAGAIN) {
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
@@ -971,8 +894,8 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
HELPER_LOG("Connection closed"); HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED; return APIError::CONNECTION_CLOSED;
} }
rx_buf_len_ += static_cast<uint16_t>(received); rx_buf_len_ += received;
if (static_cast<uint16_t>(received) != to_read) { if ((size_t) received != to_read) {
// not all read // not all read
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
} }
@@ -986,10 +909,11 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
// consume msg // consume msg
rx_buf_ = {}; rx_buf_ = {};
rx_buf_len_ = 0; rx_buf_len_ = 0;
rx_header_buf_pos_ = 0; rx_header_buf_.clear();
rx_header_parsed_ = false; rx_header_parsed_ = false;
return APIError::OK; return APIError::OK;
} }
APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
APIError aerr; APIError aerr;
@@ -1017,7 +941,7 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
"Bad indicator byte"; "Bad indicator byte";
iov[0].iov_base = (void *) msg; iov[0].iov_base = (void *) msg;
iov[0].iov_len = 19; iov[0].iov_len = 19;
this->write_raw_(iov, 1); write_raw_(iov, 1);
} }
return aerr; return aerr;
} }
@@ -1028,89 +952,70 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
buffer->type = rx_header_parsed_type_; buffer->type = rx_header_parsed_type_;
return APIError::OK; return APIError::OK;
} }
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) { bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
std::vector<uint8_t> *raw_buffer = buffer.get_buffer(); APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
uint16_t payload_len = static_cast<uint16_t>(raw_buffer->size() - frame_header_padding_);
// Use write_protobuf_packets with a single packet
std::vector<PacketInfo> packets;
packets.emplace_back(type, 0, payload_len);
return write_protobuf_packets(buffer, packets);
}
APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer,
const std::vector<PacketInfo> &packets) {
if (state_ != State::DATA) { if (state_ != State::DATA) {
return APIError::BAD_STATE; return APIError::BAD_STATE;
} }
if (packets.empty()) { std::vector<uint8_t> header;
return APIError::OK; header.reserve(1 + api::ProtoSize::varint(static_cast<uint32_t>(payload_len)) +
api::ProtoSize::varint(static_cast<uint32_t>(type)));
header.push_back(0x00);
ProtoVarInt(payload_len).encode(header);
ProtoVarInt(type).encode(header);
struct iovec iov[2];
iov[0].iov_base = &header[0];
iov[0].iov_len = header.size();
if (payload_len == 0) {
return write_raw_(iov, 1);
}
iov[1].iov_base = const_cast<uint8_t *>(payload);
iov[1].iov_len = payload_len;
return write_raw_(iov, 2);
}
APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
// try send from tx_buf
while (state_ != State::CLOSED && !tx_buf_.empty()) {
ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size());
if (is_would_block(sent)) {
break;
} else if (sent == -1) {
state_ = State::FAILED;
HELPER_LOG("Socket write failed with errno %d", errno);
return APIError::SOCKET_WRITE_FAILED;
}
// TODO: inefficient if multiple packets in txbuf
// replace with deque of buffers
tx_buf_.erase(tx_buf_.begin(), tx_buf_.begin() + sent);
} }
std::vector<uint8_t> *raw_buffer = buffer.get_buffer(); return APIError::OK;
this->reusable_iovs_.clear();
this->reusable_iovs_.reserve(packets.size());
for (const auto &packet : packets) {
uint16_t type = packet.message_type;
uint16_t offset = packet.offset;
uint16_t payload_len = packet.payload_size;
// Calculate varint sizes for header layout
uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(payload_len));
uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(type));
uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
// Calculate where to start writing the header
// The header starts at the latest possible position to minimize unused padding
//
// Example 1 (small values): total_header_len = 3, header_offset = 6 - 3 = 3
// [0-2] - Unused padding
// [3] - 0x00 indicator byte
// [4] - Payload size varint (1 byte, for sizes 0-127)
// [5] - Message type varint (1 byte, for types 0-127)
// [6...] - Actual payload data
//
// Example 2 (medium values): total_header_len = 4, header_offset = 6 - 4 = 2
// [0-1] - Unused padding
// [2] - 0x00 indicator byte
// [3-4] - Payload size varint (2 bytes, for sizes 128-16383)
// [5] - Message type varint (1 byte, for types 0-127)
// [6...] - Actual payload data
//
// Example 3 (large values): total_header_len = 6, header_offset = 6 - 6 = 0
// [0] - 0x00 indicator byte
// [1-3] - Payload size varint (3 bytes, for sizes 16384-2097151)
// [4-5] - Message type varint (2 bytes, for types 128-32767)
// [6...] - Actual payload data
//
// The message starts at offset + frame_header_padding_
// So we write the header starting at offset + frame_header_padding_ - total_header_len
uint8_t *buf_start = raw_buffer->data() + offset;
uint32_t header_offset = frame_header_padding_ - total_header_len;
// Write the plaintext header
buf_start[header_offset] = 0x00; // indicator
// Encode size varint directly into buffer
ProtoVarInt(payload_len).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
// Encode type varint directly into buffer
ProtoVarInt(type).encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
// Add iovec for this packet (header + payload)
struct iovec iov;
iov.iov_base = buf_start + header_offset;
iov.iov_len = total_header_len + payload_len;
this->reusable_iovs_.push_back(iov);
}
// Send all packets in one writev call
return write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size());
} }
APIError APIPlaintextFrameHelper::close() {
state_ = State::CLOSED;
int err = socket_->close();
if (err == -1)
return APIError::CLOSE_FAILED;
return APIError::OK;
}
APIError APIPlaintextFrameHelper::shutdown(int how) {
int err = socket_->shutdown(how);
if (err == -1)
return APIError::SHUTDOWN_FAILED;
if (how == SHUT_RDWR) {
state_ = State::CLOSED;
}
return APIError::OK;
}
// Explicit template instantiation for Plaintext
template APIError APIFrameHelper::write_raw_<APIPlaintextFrameHelper::State>(
const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf_, const std::string &info,
APIPlaintextFrameHelper::State &state, APIPlaintextFrameHelper::State failed_state);
#endif // USE_API_PLAINTEXT #endif // USE_API_PLAINTEXT
} // namespace api } // namespace api

View File

@@ -1,7 +1,6 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <deque> #include <deque>
#include <limits>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -13,32 +12,25 @@
#include "api_noise_context.h" #include "api_noise_context.h"
#include "esphome/components/socket/socket.h" #include "esphome/components/socket/socket.h"
#include "esphome/core/application.h"
namespace esphome { namespace esphome {
namespace api { namespace api {
class ProtoWriteBuffer;
struct ReadPacketBuffer { struct ReadPacketBuffer {
std::vector<uint8_t> container; std::vector<uint8_t> container;
uint16_t type; uint16_t type;
uint16_t data_offset; size_t data_offset;
uint16_t data_len; size_t data_len;
}; };
// Packed packet info structure to minimize memory usage struct PacketBuffer {
struct PacketInfo { const std::vector<uint8_t> container;
uint16_t message_type; // 2 bytes uint16_t type;
uint16_t offset; // 2 bytes (sufficient for packet size ~1460 bytes) uint8_t data_offset;
uint16_t payload_size; // 2 bytes (up to 65535 bytes) uint8_t data_len;
uint16_t padding; // 2 byte (for alignment)
PacketInfo(uint16_t type, uint16_t off, uint16_t size)
: message_type(type), offset(off), payload_size(size), padding(0) {}
}; };
enum class APIError : uint16_t { enum class APIError : int {
OK = 0, OK = 0,
WOULD_BLOCK = 1001, WOULD_BLOCK = 1001,
BAD_HANDSHAKE_PACKET_LEN = 1002, BAD_HANDSHAKE_PACKET_LEN = 1002,
@@ -68,211 +60,141 @@ const char *api_error_to_str(APIError err);
class APIFrameHelper { class APIFrameHelper {
public: public:
APIFrameHelper() = default;
explicit APIFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_owned_(std::move(socket)) {
socket_ = socket_owned_.get();
}
virtual ~APIFrameHelper() = default; virtual ~APIFrameHelper() = default;
virtual APIError init() = 0; virtual APIError init() = 0;
virtual APIError loop(); virtual APIError loop() = 0;
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0; virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
bool can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); } virtual bool can_write_without_blocking() = 0;
std::string getpeername() { return socket_->getpeername(); } virtual APIError write_packet(uint16_t type, const uint8_t *data, size_t len) = 0;
int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); } virtual std::string getpeername() = 0;
APIError close() { virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0;
state_ = State::CLOSED; virtual APIError close() = 0;
int err = this->socket_->close(); virtual APIError shutdown(int how) = 0;
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;
}
// Give this helper a name for logging // Give this helper a name for logging
void set_log_info(std::string info) { info_ = std::move(info); } virtual void set_log_info(std::string info) = 0;
virtual APIError write_protobuf_packet(uint16_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, const std::vector<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: 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 // 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> template<typename StateEnum>
APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf, 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); 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_();
}; };
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
class APINoiseFrameHelper : public APIFrameHelper { class APINoiseFrameHelper : public APIFrameHelper {
public: public:
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx) APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx)
: APIFrameHelper(std::move(socket)), ctx_(std::move(ctx)) { : socket_(std::move(socket)), ctx_(std::move(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;
}
~APINoiseFrameHelper() override; ~APINoiseFrameHelper() override;
APIError init() override; APIError init() override;
APIError loop() override; APIError loop() override;
APIError read_packet(ReadPacketBuffer *buffer) override; APIError read_packet(ReadPacketBuffer *buffer) override;
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override; bool can_write_without_blocking() override;
APIError write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector<PacketInfo> &packets) override; APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
// Get the frame header padding required by this protocol std::string getpeername() override { return this->socket_->getpeername(); }
uint8_t frame_header_padding() override { return frame_header_padding_; } int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
// Get the frame footer size required by this protocol return this->socket_->getpeername(addr, addrlen);
uint8_t frame_footer_size() override { return frame_footer_size_; } }
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: protected:
struct ParsedFrame {
std::vector<uint8_t> msg;
};
APIError state_action_(); APIError state_action_();
APIError try_read_frame_(ParsedFrame *frame); 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);
inline APIError write_raw_(const struct iovec *iov, int iovcnt) {
return APIFrameHelper::write_raw_(iov, iovcnt, socket_.get(), tx_buf_, info_, state_, State::FAILED);
}
APIError init_handshake_(); APIError init_handshake_();
APIError check_handshake_finished_(); APIError check_handshake_finished_();
void send_explicit_handshake_reject_(const std::string &reason); 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}; NoiseHandshakeState *handshake_{nullptr};
NoiseCipherState *send_cipher_{nullptr}; NoiseCipherState *send_cipher_{nullptr};
NoiseCipherState *recv_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_; NoiseProtocolId nid_;
// Group small types together enum class State {
// Fixed-size header buffer for noise protocol: INITIALIZE = 1,
// 1 byte for indicator + 2 bytes for message size (16-bit value, not varint) CLIENT_HELLO = 2,
// Note: Maximum message size is UINT16_MAX (65535), with a limit of 128 bytes during handshake phase SERVER_HELLO = 3,
uint8_t rx_header_buf_[3]; HANDSHAKE = 4,
uint8_t rx_header_buf_len_ = 0; DATA = 5,
// 4 bytes total, no padding CLOSED = 6,
FAILED = 7,
EXPLICIT_REJECT = 8,
} state_ = State::INITIALIZE;
}; };
#endif // USE_API_NOISE #endif // USE_API_NOISE
#ifdef USE_API_PLAINTEXT #ifdef USE_API_PLAINTEXT
class APIPlaintextFrameHelper : public APIFrameHelper { class APIPlaintextFrameHelper : public APIFrameHelper {
public: public:
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : APIFrameHelper(std::move(socket)) { APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_(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() override = default; ~APIPlaintextFrameHelper() override = default;
APIError init() override; APIError init() override;
APIError loop() override; APIError loop() override;
APIError read_packet(ReadPacketBuffer *buffer) override; APIError read_packet(ReadPacketBuffer *buffer) override;
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override; bool can_write_without_blocking() override;
APIError write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector<PacketInfo> &packets) override; APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
uint8_t frame_header_padding() override { return frame_header_padding_; } std::string getpeername() override { return this->socket_->getpeername(); }
// Get the frame footer size required by this protocol int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
uint8_t frame_footer_size() override { return frame_footer_size_; } 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: protected:
struct ParsedFrame {
std::vector<uint8_t> msg;
};
APIError try_read_frame_(ParsedFrame *frame); APIError try_read_frame_(ParsedFrame *frame);
APIError try_send_tx_buf_();
inline APIError write_raw_(const struct iovec *iov, int iovcnt) {
return APIFrameHelper::write_raw_(iov, iovcnt, socket_.get(), tx_buf_, info_, state_, State::FAILED);
}
// Group 2-byte aligned types std::unique_ptr<socket::Socket> socket_;
uint16_t rx_header_parsed_type_ = 0;
uint16_t rx_header_parsed_len_ = 0;
// Group 1-byte types together std::string info_;
// Fixed-size header buffer for plaintext protocol: std::vector<uint8_t> rx_header_buf_;
// 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;
bool rx_header_parsed_ = false; 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 #endif

View File

@@ -21,5 +21,4 @@ extend google.protobuf.MessageOptions {
optional string ifdef = 1038; optional string ifdef = 1038;
optional bool log = 1039 [default=true]; optional bool log = 1039 [default=true];
optional bool no_delay = 1040 [default=false]; 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

View File

@@ -10,94 +10,162 @@ namespace api {
class APIServerConnectionBase : public ProtoService { class APIServerConnectionBase : public ProtoService {
public: 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){}; virtual void on_hello_request(const HelloRequest &value){};
bool send_hello_response(const HelloResponse &msg);
virtual void on_connect_request(const ConnectRequest &value){}; 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){}; virtual void on_disconnect_request(const DisconnectRequest &value){};
bool send_disconnect_response(const DisconnectResponse &msg);
virtual void on_disconnect_response(const DisconnectResponse &value){}; virtual void on_disconnect_response(const DisconnectResponse &value){};
bool send_ping_request(const PingRequest &msg);
virtual void on_ping_request(const PingRequest &value){}; 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_ping_response(const PingResponse &value){};
virtual void on_device_info_request(const DeviceInfoRequest &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){}; 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){}; 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 #ifdef USE_COVER
virtual void on_cover_command_request(const CoverCommandRequest &value){}; virtual void on_cover_command_request(const CoverCommandRequest &value){};
#endif #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 #ifdef USE_FAN
virtual void on_fan_command_request(const FanCommandRequest &value){}; virtual void on_fan_command_request(const FanCommandRequest &value){};
#endif #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 #ifdef USE_LIGHT
virtual void on_light_command_request(const LightCommandRequest &value){}; virtual void on_light_command_request(const LightCommandRequest &value){};
#endif #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 #ifdef USE_SWITCH
virtual void on_switch_command_request(const SwitchCommandRequest &value){}; virtual void on_switch_command_request(const SwitchCommandRequest &value){};
#endif #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){}; virtual void on_subscribe_logs_request(const SubscribeLogsRequest &value){};
bool send_subscribe_logs_response(const SubscribeLogsResponse &msg);
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
virtual void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){}; virtual void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){};
#endif #endif
#ifdef USE_API_NOISE
bool send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyResponse &msg);
#endif
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){}; 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){}; 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){}; 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){}; 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){}; virtual void on_get_time_response(const GetTimeResponse &value){};
bool send_list_entities_services_response(const ListEntitiesServicesResponse &msg);
virtual void on_execute_service_request(const ExecuteServiceRequest &value){}; 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_ESP32_CAMERA
bool send_camera_image_response(const CameraImageResponse &msg);
#endif
#ifdef USE_ESP32_CAMERA #ifdef USE_ESP32_CAMERA
virtual void on_camera_image_request(const CameraImageRequest &value){}; virtual void on_camera_image_request(const CameraImageRequest &value){};
#endif #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 #ifdef USE_CLIMATE
virtual void on_climate_command_request(const ClimateCommandRequest &value){}; virtual void on_climate_command_request(const ClimateCommandRequest &value){};
#endif #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 #ifdef USE_NUMBER
virtual void on_number_command_request(const NumberCommandRequest &value){}; virtual void on_number_command_request(const NumberCommandRequest &value){};
#endif #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 #ifdef USE_SELECT
virtual void on_select_command_request(const SelectCommandRequest &value){}; virtual void on_select_command_request(const SelectCommandRequest &value){};
#endif #endif
#ifdef USE_SIREN
bool send_list_entities_siren_response(const ListEntitiesSirenResponse &msg);
#endif
#ifdef USE_SIREN
bool send_siren_state_response(const SirenStateResponse &msg);
#endif
#ifdef USE_SIREN #ifdef USE_SIREN
virtual void on_siren_command_request(const SirenCommandRequest &value){}; virtual void on_siren_command_request(const SirenCommandRequest &value){};
#endif #endif
#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 #ifdef USE_LOCK
virtual void on_lock_command_request(const LockCommandRequest &value){}; virtual void on_lock_command_request(const LockCommandRequest &value){};
#endif #endif
#ifdef USE_BUTTON
bool send_list_entities_button_response(const ListEntitiesButtonResponse &msg);
#endif
#ifdef USE_BUTTON #ifdef USE_BUTTON
virtual void on_button_command_request(const ButtonCommandRequest &value){}; virtual void on_button_command_request(const ButtonCommandRequest &value){};
#endif #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 #ifdef USE_MEDIA_PLAYER
virtual void on_media_player_command_request(const MediaPlayerCommandRequest &value){}; virtual void on_media_player_command_request(const MediaPlayerCommandRequest &value){};
#endif #endif
@@ -105,19 +173,33 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_subscribe_bluetooth_le_advertisements_request( virtual void on_subscribe_bluetooth_le_advertisements_request(
const SubscribeBluetoothLEAdvertisementsRequest &value){}; const SubscribeBluetoothLEAdvertisementsRequest &value){};
#endif #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 #ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_device_request(const BluetoothDeviceRequest &value){}; virtual void on_bluetooth_device_request(const BluetoothDeviceRequest &value){};
#endif #endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_device_connection_response(const BluetoothDeviceConnectionResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){}; virtual void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){};
#endif #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 #ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){}; virtual void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){};
#endif #endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value){}; virtual void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value){};
#endif #endif
@@ -130,23 +212,49 @@ class APIServerConnectionBase : public ProtoService {
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value){}; virtual void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value){};
#endif #endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
virtual void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &value){}; virtual void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &value){};
#endif #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 #ifdef USE_BLUETOOTH_PROXY
virtual void on_unsubscribe_bluetooth_le_advertisements_request( virtual void on_unsubscribe_bluetooth_le_advertisements_request(
const UnsubscribeBluetoothLEAdvertisementsRequest &value){}; const UnsubscribeBluetoothLEAdvertisementsRequest &value){};
#endif #endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_scanner_state_response(const BluetoothScannerStateResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &value){}; virtual void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &value){};
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
virtual void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){}; virtual void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){};
#endif #endif
#ifdef USE_VOICE_ASSISTANT
bool send_voice_assistant_request(const VoiceAssistantRequest &msg);
#endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_response(const VoiceAssistantResponse &value){}; virtual void on_voice_assistant_response(const VoiceAssistantResponse &value){};
#endif #endif
@@ -154,6 +262,7 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){}; virtual void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){};
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
bool send_voice_assistant_audio(const VoiceAssistantAudio &msg);
virtual void on_voice_assistant_audio(const VoiceAssistantAudio &value){}; virtual void on_voice_assistant_audio(const VoiceAssistantAudio &value){};
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
@@ -162,44 +271,89 @@ class APIServerConnectionBase : public ProtoService {
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &value){}; virtual void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &value){};
#endif #endif
#ifdef USE_VOICE_ASSISTANT
bool send_voice_assistant_announce_finished(const VoiceAssistantAnnounceFinished &msg);
#endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &value){}; virtual void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &value){};
#endif #endif
#ifdef USE_VOICE_ASSISTANT
bool send_voice_assistant_configuration_response(const VoiceAssistantConfigurationResponse &msg);
#endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value){}; virtual void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value){};
#endif #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 #ifdef USE_ALARM_CONTROL_PANEL
virtual void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &value){}; virtual void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &value){};
#endif #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 #ifdef USE_TEXT
virtual void on_text_command_request(const TextCommandRequest &value){}; virtual void on_text_command_request(const TextCommandRequest &value){};
#endif #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 #ifdef USE_DATETIME_DATE
virtual void on_date_command_request(const DateCommandRequest &value){}; virtual void on_date_command_request(const DateCommandRequest &value){};
#endif #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 #ifdef USE_DATETIME_TIME
virtual void on_time_command_request(const TimeCommandRequest &value){}; virtual void on_time_command_request(const TimeCommandRequest &value){};
#endif #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 #ifdef USE_VALVE
virtual void on_valve_command_request(const ValveCommandRequest &value){}; virtual void on_valve_command_request(const ValveCommandRequest &value){};
#endif #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 #ifdef USE_DATETIME_DATETIME
virtual void on_date_time_command_request(const DateTimeCommandRequest &value){}; virtual void on_date_time_command_request(const DateTimeCommandRequest &value){};
#endif #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 #ifdef USE_UPDATE
virtual void on_update_command_request(const UpdateCommandRequest &value){}; virtual void on_update_command_request(const UpdateCommandRequest &value){};
#endif #endif
protected: 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 { class APIServerConnection : public APIServerConnectionBase {

View File

@@ -316,13 +316,15 @@ class ProtoSize {
/** /**
* @brief Calculates and adds the size of a nested message field to the total message 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, * This templated version directly takes a message object, calculates its size internally,
* and updates the total_size reference. This eliminates the need for a temporary variable * and updates the total_size reference. This eliminates the need for a temporary variable
* at the call site. * at the call site.
* *
* @tparam MessageType The type of the nested message (inferred from parameter)
* @param message The nested message object * @param message The nested message object
*/ */
static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const ProtoMessage &message, template<typename MessageType>
static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const MessageType &message,
bool force = false) { bool force = false) {
uint32_t nested_size = 0; uint32_t nested_size = 0;
message.calculate_size(nested_size); message.calculate_size(nested_size);

View File

@@ -24,14 +24,10 @@ static const char *const TAG = "api";
// APIServer // APIServer
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
APIServer::APIServer() { APIServer::APIServer() { global_api_server = this; }
global_api_server = this;
// Pre-allocate shared write buffer
shared_write_buffer_.reserve(64);
}
void APIServer::setup() { void APIServer::setup() {
ESP_LOGCONFIG(TAG, "Running setup"); ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server...");
this->setup_controller(); this->setup_controller();
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
@@ -47,12 +43,7 @@ void APIServer::setup() {
} }
#endif #endif
// Schedule reboot if no clients connect within timeout this->socket_ = socket::socket_ip(SOCK_STREAM, 0);
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) { if (this->socket_ == nullptr) {
ESP_LOGW(TAG, "Could not create socket"); ESP_LOGW(TAG, "Could not create socket");
this->mark_failed(); this->mark_failed();
@@ -97,26 +88,22 @@ void APIServer::setup() {
#ifdef USE_LOGGER #ifdef USE_LOGGER
if (logger::global_logger != nullptr) { if (logger::global_logger != nullptr) {
logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) { logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) {
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_) { for (auto &c : this->clients_) {
if (!c->flags_.remove) if (!c->remove_)
c->try_send_log_message(level, tag, message); c->try_send_log_message(level, tag, message);
} }
}); });
} }
#endif #endif
this->last_connected_ = millis();
#ifdef USE_ESP32_CAMERA #ifdef USE_ESP32_CAMERA
if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) { if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) {
esp32_camera::global_esp32_camera->add_image_callback( esp32_camera::global_esp32_camera->add_image_callback(
[this](const std::shared_ptr<esp32_camera::CameraImage> &image) { [this](const std::shared_ptr<esp32_camera::CameraImage> &image) {
for (auto &c : this->clients_) { for (auto &c : this->clients_) {
if (!c->flags_.remove) if (!c->remove_)
c->set_camera_state(image); c->set_camera_state(image);
} }
}); });
@@ -124,90 +111,64 @@ void APIServer::setup() {
#endif #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();
}
});
}
void APIServer::loop() { void APIServer::loop() {
// Accept new clients only if the socket exists and has incoming connections // Accept new clients
if (this->socket_ && this->socket_->ready()) { while (true) {
while (true) { struct sockaddr_storage source_addr;
struct sockaddr_storage source_addr; socklen_t addr_len = sizeof(source_addr);
socklen_t addr_len = sizeof(source_addr); auto sock = this->socket_->accept((struct sockaddr *) &source_addr, &addr_len);
auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len); if (!sock)
if (!sock) break;
break; ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str());
ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str());
auto *conn = new APIConnection(std::move(sock), this); auto *conn = new APIConnection(std::move(sock), this);
this->clients_.emplace_back(conn); this->clients_.emplace_back(conn);
conn->start(); conn->start();
}
// Clear warning status and cancel reboot when first client connects // Process clients and remove disconnected ones in a single pass
if (this->clients_.size() == 1 && this->reboot_timeout_ != 0) { if (!this->clients_.empty()) {
this->status_clear_warning(); size_t client_index = 0;
this->cancel_timeout("api_reboot"); while (client_index < this->clients_.size()) {
auto &client = this->clients_[client_index];
if (client->remove_) {
// Handle disconnection
this->client_disconnected_trigger_->trigger(client->client_info_, client->client_peername_);
ESP_LOGV(TAG, "Removing connection to %s", client->client_info_.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();
// Don't increment client_index since we need to process the swapped element
} else {
// Process active client
client->loop();
client_index++; // Move to next client
} }
} }
} }
if (this->clients_.empty()) { if (this->reboot_timeout_ != 0) {
return; const uint32_t now = millis();
} if (!this->is_connected()) {
if (now - this->last_connected_ > this->reboot_timeout_) {
// Process clients and remove disconnected ones in a single pass ESP_LOGE(TAG, "No client connected to API. Rebooting...");
// Check network connectivity once for all clients App.reboot();
if (!network::is_connected()) { }
// Network is down - disconnect all clients this->status_set_warning();
for (auto &client : this->clients_) { } else {
client->on_fatal_error(); this->last_connected_ = now;
ESP_LOGW(TAG, "%s: Network down; disconnect", client->get_client_combined_info().c_str()); this->status_clear_warning();
} }
// 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());
// 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();
// Schedule reboot when last client disconnects
if (this->clients_.empty() && this->reboot_timeout_ != 0) {
this->schedule_reboot_timeout_();
}
// Don't increment client_index since we need to process the swapped element
} }
} }
void APIServer::dump_config() { void APIServer::dump_config() {
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG, "API Server:");
"API Server:\n" ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->port_);
" Address: %s:%u",
network::get_use_address().c_str(), this->port_);
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
ESP_LOGCONFIG(TAG, " Using noise encryption: %s", YESNO(this->noise_ctx_->has_psk())); ESP_LOGCONFIG(TAG, " Using noise encryption: %s", YESNO(this->noise_ctx_->has_psk()));
if (!this->noise_ctx_->has_psk()) { if (!this->noise_ctx_->has_psk()) {
@@ -252,11 +213,11 @@ bool APIServer::check_password(const std::string &password) const {
void APIServer::handle_disconnect(APIConnection *conn) {} void APIServer::handle_disconnect(APIConnection *conn) {}
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj) { void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) {
if (obj->is_internal()) if (obj->is_internal())
return; return;
for (auto &c : this->clients_) for (auto &c : this->clients_)
c->send_binary_sensor_state(obj); c->send_binary_sensor_state(obj, state);
} }
#endif #endif
@@ -292,7 +253,7 @@ void APIServer::on_sensor_update(sensor::Sensor *obj, float state) {
if (obj->is_internal()) if (obj->is_internal())
return; return;
for (auto &c : this->clients_) for (auto &c : this->clients_)
c->send_sensor_state(obj); c->send_sensor_state(obj, state);
} }
#endif #endif
@@ -301,7 +262,7 @@ void APIServer::on_switch_update(switch_::Switch *obj, bool state) {
if (obj->is_internal()) if (obj->is_internal())
return; return;
for (auto &c : this->clients_) for (auto &c : this->clients_)
c->send_switch_state(obj); c->send_switch_state(obj, state);
} }
#endif #endif
@@ -310,7 +271,7 @@ void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::s
if (obj->is_internal()) if (obj->is_internal())
return; return;
for (auto &c : this->clients_) for (auto &c : this->clients_)
c->send_text_sensor_state(obj); c->send_text_sensor_state(obj, state);
} }
#endif #endif
@@ -328,7 +289,7 @@ void APIServer::on_number_update(number::Number *obj, float state) {
if (obj->is_internal()) if (obj->is_internal())
return; return;
for (auto &c : this->clients_) for (auto &c : this->clients_)
c->send_number_state(obj); c->send_number_state(obj, state);
} }
#endif #endif
@@ -364,7 +325,7 @@ void APIServer::on_text_update(text::Text *obj, const std::string &state) {
if (obj->is_internal()) if (obj->is_internal())
return; return;
for (auto &c : this->clients_) for (auto &c : this->clients_)
c->send_text_state(obj); c->send_text_state(obj, state);
} }
#endif #endif
@@ -373,7 +334,7 @@ void APIServer::on_select_update(select::Select *obj, const std::string &state,
if (obj->is_internal()) if (obj->is_internal())
return; return;
for (auto &c : this->clients_) for (auto &c : this->clients_)
c->send_select_state(obj); c->send_select_state(obj, state);
} }
#endif #endif
@@ -382,7 +343,7 @@ void APIServer::on_lock_update(lock::Lock *obj) {
if (obj->is_internal()) if (obj->is_internal())
return; return;
for (auto &c : this->clients_) for (auto &c : this->clients_)
c->send_lock_state(obj); c->send_lock_state(obj, obj->state);
} }
#endif #endif
@@ -433,8 +394,6 @@ void APIServer::set_port(uint16_t port) { this->port_ = port; }
void APIServer::set_password(const std::string &password) { this->password_ = password; } void APIServer::set_password(const std::string &password) { this->password_ = password; }
void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; }
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) { void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
for (auto &client : this->clients_) { for (auto &client : this->clients_) {
client->send_homeassistant_service_call(call); client->send_homeassistant_service_call(call);
@@ -493,7 +452,7 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
ESP_LOGW(TAG, "Disconnecting all clients to reset connections"); ESP_LOGW(TAG, "Disconnecting all clients to reset connections");
this->set_noise_psk(psk); this->set_noise_psk(psk);
for (auto &c : this->clients_) { for (auto &c : this->clients_) {
c->send_message(DisconnectRequest()); c->send_disconnect_request(DisconnectRequest());
} }
}); });
} }
@@ -504,7 +463,7 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
#ifdef USE_HOMEASSISTANT_TIME #ifdef USE_HOMEASSISTANT_TIME
void APIServer::request_time() { void APIServer::request_time() {
for (auto &client : this->clients_) { for (auto &client : this->clients_) {
if (!client->flags_.remove && client->is_authenticated()) if (!client->remove_ && client->is_authenticated())
client->send_time_request(); client->send_time_request();
} }
} }
@@ -513,36 +472,10 @@ void APIServer::request_time() {
bool APIServer::is_connected() const { return !this->clients_.empty(); } bool APIServer::is_connected() const { return !this->clients_.empty(); }
void APIServer::on_shutdown() { void APIServer::on_shutdown() {
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_) { for (auto &c : this->clients_) {
if (!c->send_message(DisconnectRequest())) { c->send_disconnect_request(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);
}
} }
} 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();
} }
} // namespace api } // namespace api

View File

@@ -34,17 +34,11 @@ class APIServer : public Component, public Controller {
void loop() override; void loop() override;
void dump_config() override; void dump_config() override;
void on_shutdown() override; void on_shutdown() override;
bool teardown() override;
bool check_password(const std::string &password) const; bool check_password(const std::string &password) const;
bool uses_password() const; bool uses_password() const;
void set_port(uint16_t port); void set_port(uint16_t port);
void set_password(const std::string &password); void set_password(const std::string &password);
void set_reboot_timeout(uint32_t reboot_timeout); 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 #ifdef USE_API_NOISE
bool save_noise_psk(psk_t psk, bool make_active = true); bool save_noise_psk(psk_t psk, bool make_active = true);
@@ -54,7 +48,7 @@ class APIServer : public Component, public Controller {
void handle_disconnect(APIConnection *conn); void handle_disconnect(APIConnection *conn);
#ifdef USE_BINARY_SENSOR #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 #endif
#ifdef USE_COVER #ifdef USE_COVER
void on_cover_update(cover::Cover *obj) override; void on_cover_update(cover::Cover *obj) override;
@@ -105,18 +99,7 @@ class APIServer : public Component, public Controller {
void on_media_player_update(media_player::MediaPlayer *obj) override; void on_media_player_update(media_player::MediaPlayer *obj) override;
#endif #endif
void send_homeassistant_service_call(const HomeassistantServiceResponse &call); void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
void register_user_service(UserServiceDescriptor *descriptor) { void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
#ifdef USE_API_YAML_SERVICES
// Vector is pre-allocated when services are defined in YAML
this->user_services_.push_back(descriptor);
#else
// Lazy allocate vector on first use for CustomAPIDevice
if (!this->user_services_) {
this->user_services_ = std::make_unique<std::vector<UserServiceDescriptor *>>();
}
this->user_services_->push_back(descriptor);
#endif
}
#ifdef USE_HOMEASSISTANT_TIME #ifdef USE_HOMEASSISTANT_TIME
void request_time(); void request_time();
#endif #endif
@@ -145,58 +128,24 @@ class APIServer : public Component, public Controller {
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute, void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f); std::function<void(std::string)> f);
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const; const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
const std::vector<UserServiceDescriptor *> &get_user_services() const { const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }
#ifdef USE_API_YAML_SERVICES
return this->user_services_;
#else
static const std::vector<UserServiceDescriptor *> EMPTY;
return this->user_services_ ? *this->user_services_ : EMPTY;
#endif
}
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
Trigger<std::string, std::string> *get_client_connected_trigger() const { return this->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 { Trigger<std::string, std::string> *get_client_disconnected_trigger() const {
return this->client_disconnected_trigger_; return this->client_disconnected_trigger_;
} }
#endif
protected: protected:
void schedule_reboot_timeout_();
// Pointers and pointer-like types first (4 bytes each)
std::unique_ptr<socket::Socket> socket_ = nullptr; std::unique_ptr<socket::Socket> socket_ = nullptr;
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER uint16_t port_{6053};
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}; uint32_t reboot_timeout_{300000};
uint32_t last_connected_{0};
// Vectors and strings (12 bytes each on 32-bit)
std::vector<std::unique_ptr<APIConnection>> clients_; std::vector<std::unique_ptr<APIConnection>> clients_;
std::string password_; std::string password_;
std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections
std::vector<HomeAssistantStateSubscription> state_subs_; std::vector<HomeAssistantStateSubscription> state_subs_;
#ifdef USE_API_YAML_SERVICES
// When services are defined in YAML, we know at compile time that services will be registered
std::vector<UserServiceDescriptor *> user_services_; std::vector<UserServiceDescriptor *> user_services_;
#else Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>();
// Services can still be registered at runtime by CustomAPIDevice components even when not Trigger<std::string, std::string> *client_disconnected_trigger_ = new Trigger<std::string, std::string>();
// defined in YAML. Using unique_ptr allows lazy allocation, saving 12 bytes in the common
// case where no services (YAML or custom) are used.
std::unique_ptr<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
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>(); std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>();

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
#include "list_entities.h" #include "list_entities.h"
#ifdef USE_API #ifdef USE_API
#include "api_connection.h" #include "api_connection.h"
#include "api_pb2.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/util.h" #include "esphome/core/util.h"
@@ -9,85 +8,155 @@
namespace esphome { namespace esphome {
namespace api { namespace api {
// Generate entity handler implementations using macros
#ifdef USE_BINARY_SENSOR #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 #endif
#ifdef USE_COVER #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 #endif
#ifdef USE_FAN #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 #endif
#ifdef USE_LIGHT #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 #endif
#ifdef USE_SENSOR #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 #endif
#ifdef USE_SWITCH #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 #endif
#ifdef USE_BUTTON #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 #endif
#ifdef USE_TEXT_SENSOR #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 #endif
#ifdef USE_LOCK #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 #endif
#ifdef USE_VALVE #ifdef USE_VALVE
LIST_ENTITIES_HANDLER(valve, valve::Valve, ListEntitiesValveResponse) bool ListEntitiesIterator::on_valve(valve::Valve *valve) {
#endif this->client_->send_valve_info(valve);
#ifdef USE_ESP32_CAMERA return true;
LIST_ENTITIES_HANDLER(camera, esp32_camera::ESP32Camera, 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)
#endif #endif
// Special cases that don't follow the pattern
bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); } bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); }
ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {} ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {}
bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) { bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
auto resp = service->encode_list_service_response(); 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
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome
#endif #endif

View File

@@ -9,83 +9,75 @@ namespace api {
class APIConnection; 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); \
}
class ListEntitiesIterator : public ComponentIterator { class ListEntitiesIterator : public ComponentIterator {
public: public:
ListEntitiesIterator(APIConnection *client); ListEntitiesIterator(APIConnection *client);
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
bool on_binary_sensor(binary_sensor::BinarySensor *entity) override; bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override;
#endif #endif
#ifdef USE_COVER #ifdef USE_COVER
bool on_cover(cover::Cover *entity) override; bool on_cover(cover::Cover *cover) override;
#endif #endif
#ifdef USE_FAN #ifdef USE_FAN
bool on_fan(fan::Fan *entity) override; bool on_fan(fan::Fan *fan) override;
#endif #endif
#ifdef USE_LIGHT #ifdef USE_LIGHT
bool on_light(light::LightState *entity) override; bool on_light(light::LightState *light) override;
#endif #endif
#ifdef USE_SENSOR #ifdef USE_SENSOR
bool on_sensor(sensor::Sensor *entity) override; bool on_sensor(sensor::Sensor *sensor) override;
#endif #endif
#ifdef USE_SWITCH #ifdef USE_SWITCH
bool on_switch(switch_::Switch *entity) override; bool on_switch(switch_::Switch *a_switch) override;
#endif #endif
#ifdef USE_BUTTON #ifdef USE_BUTTON
bool on_button(button::Button *entity) override; bool on_button(button::Button *button) override;
#endif #endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
bool on_text_sensor(text_sensor::TextSensor *entity) override; bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
#endif #endif
bool on_service(UserServiceDescriptor *service) override; bool on_service(UserServiceDescriptor *service) override;
#ifdef USE_ESP32_CAMERA #ifdef USE_ESP32_CAMERA
bool on_camera(esp32_camera::ESP32Camera *entity) override; bool on_camera(esp32_camera::ESP32Camera *camera) override;
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
bool on_climate(climate::Climate *entity) override; bool on_climate(climate::Climate *climate) override;
#endif #endif
#ifdef USE_NUMBER #ifdef USE_NUMBER
bool on_number(number::Number *entity) override; bool on_number(number::Number *number) override;
#endif #endif
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
bool on_date(datetime::DateEntity *entity) override; bool on_date(datetime::DateEntity *date) override;
#endif #endif
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
bool on_time(datetime::TimeEntity *entity) override; bool on_time(datetime::TimeEntity *time) override;
#endif #endif
#ifdef USE_DATETIME_DATETIME #ifdef USE_DATETIME_DATETIME
bool on_datetime(datetime::DateTimeEntity *entity) override; bool on_datetime(datetime::DateTimeEntity *datetime) override;
#endif #endif
#ifdef USE_TEXT #ifdef USE_TEXT
bool on_text(text::Text *entity) override; bool on_text(text::Text *text) override;
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
bool on_select(select::Select *entity) override; bool on_select(select::Select *select) override;
#endif #endif
#ifdef USE_LOCK #ifdef USE_LOCK
bool on_lock(lock::Lock *entity) override; bool on_lock(lock::Lock *a_lock) override;
#endif #endif
#ifdef USE_VALVE #ifdef USE_VALVE
bool on_valve(valve::Valve *entity) override; bool on_valve(valve::Valve *valve) override;
#endif #endif
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
bool on_media_player(media_player::MediaPlayer *entity) override; bool on_media_player(media_player::MediaPlayer *media_player) override;
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL #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 #endif
#ifdef USE_EVENT #ifdef USE_EVENT
bool on_event(event::Event *entity) override; bool on_event(event::Event *event) override;
#endif #endif
#ifdef USE_UPDATE #ifdef USE_UPDATE
bool on_update(update::UpdateEntity *entity) override; bool on_update(update::UpdateEntity *update) override;
#endif #endif
bool on_end() override; bool on_end() override;
bool completed() { return this->state_ == IteratorState::NONE; } bool completed() { return this->state_ == IteratorState::NONE; }

View File

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

View File

@@ -1,8 +1,8 @@
#pragma once #pragma once
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include <vector> #include <vector>
@@ -20,26 +20,16 @@ class ProtoVarInt {
explicit ProtoVarInt(uint64_t value) : value_(value) {} explicit ProtoVarInt(uint64_t value) : value_(value) {}
static optional<ProtoVarInt> parse(const uint8_t *buffer, uint32_t len, uint32_t *consumed) { static optional<ProtoVarInt> parse(const uint8_t *buffer, uint32_t len, uint32_t *consumed) {
if (len == 0) { if (consumed != nullptr)
if (consumed != nullptr) *consumed = 0;
*consumed = 0;
if (len == 0)
return {}; return {};
}
// Most common case: single-byte varint (values 0-127) uint64_t result = 0;
if ((buffer[0] & 0x80) == 0) { uint8_t bitpos = 0;
if (consumed != nullptr)
*consumed = 1;
return ProtoVarInt(buffer[0]);
}
// General case for multi-byte varints for (uint32_t i = 0; i < len; i++) {
// 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++) {
uint8_t val = buffer[i]; uint8_t val = buffer[i];
result |= uint64_t(val & 0x7F) << uint64_t(bitpos); result |= uint64_t(val & 0x7F) << uint64_t(bitpos);
bitpos += 7; bitpos += 7;
@@ -50,12 +40,9 @@ class ProtoVarInt {
} }
} }
if (consumed != nullptr) return {};
*consumed = 0;
return {}; // Incomplete or invalid varint
} }
uint16_t as_uint16() const { return this->value_; }
uint32_t as_uint32() const { return this->value_; } uint32_t as_uint32() const { return this->value_; }
uint64_t as_uint64() const { return this->value_; } uint64_t as_uint64() const { return this->value_; }
bool as_bool() const { return this->value_; } bool as_bool() const { return this->value_; }
@@ -84,34 +71,6 @@ class ProtoVarInt {
return static_cast<int64_t>(this->value_ >> 1); 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) { void encode(std::vector<uint8_t> &out) {
uint64_t val = this->value_; uint64_t val = this->value_;
if (val <= 0x7F) { if (val <= 0x7F) {
@@ -216,7 +175,7 @@ class ProtoWriteBuffer {
this->buffer_->insert(this->buffer_->end(), data, data + len); this->buffer_->insert(this->buffer_->end(), data, data + len);
} }
void encode_string(uint32_t field_id, const std::string &value, bool force = false) { 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) { 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); this->encode_string(field_id, reinterpret_cast<const char *>(data), len, force);
@@ -327,15 +286,12 @@ class ProtoWriteBuffer {
class ProtoMessage { class ProtoMessage {
public: public:
virtual ~ProtoMessage() = default; virtual ~ProtoMessage() = default;
// Default implementation for messages with no fields virtual void encode(ProtoWriteBuffer buffer) const = 0;
virtual void encode(ProtoWriteBuffer buffer) const {}
void decode(const uint8_t *buffer, size_t length); 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 = 0;
virtual void calculate_size(uint32_t &total_size) const {}
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
std::string dump() const; std::string dump() const;
virtual void dump_to(std::string &out) const = 0; virtual void dump_to(std::string &out) const = 0;
virtual const char *message_name() const { return "unknown"; }
#endif #endif
protected: protected:
@@ -363,11 +319,11 @@ class ProtoService {
* @return A ProtoWriteBuffer object with the reserved size. * @return A ProtoWriteBuffer object with the reserved size.
*/ */
virtual ProtoWriteBuffer create_buffer(uint32_t reserve_size) = 0; virtual ProtoWriteBuffer create_buffer(uint32_t reserve_size) = 0;
virtual bool send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) = 0; virtual bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) = 0;
virtual void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 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 // Optimized method that pre-allocates buffer based on message size
bool send_message_(const ProtoMessage &msg, uint16_t message_type) { template<class C> bool send_message_(const C &msg, uint32_t message_type) {
uint32_t msg_size = 0; uint32_t msg_size = 0;
msg.calculate_size(msg_size); msg.calculate_size(msg_size);
@@ -380,26 +336,6 @@ class ProtoService {
// Send the buffer // Send the buffer
return this->send_buffer(buffer, message_type); 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 } // namespace api

View File

@@ -6,67 +6,81 @@
namespace esphome { namespace esphome {
namespace api { namespace api {
// Generate entity handler implementations using macros
#ifdef USE_BINARY_SENSOR #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 #endif
#ifdef USE_COVER #ifdef USE_COVER
INITIAL_STATE_HANDLER(cover, cover::Cover) bool InitialStateIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_state(cover); }
#endif #endif
#ifdef USE_FAN #ifdef USE_FAN
INITIAL_STATE_HANDLER(fan, fan::Fan) bool InitialStateIterator::on_fan(fan::Fan *fan) { return this->client_->send_fan_state(fan); }
#endif #endif
#ifdef USE_LIGHT #ifdef USE_LIGHT
INITIAL_STATE_HANDLER(light, light::LightState) bool InitialStateIterator::on_light(light::LightState *light) { return this->client_->send_light_state(light); }
#endif #endif
#ifdef USE_SENSOR #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 #endif
#ifdef USE_SWITCH #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 #endif
#ifdef USE_TEXT_SENSOR #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 #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
INITIAL_STATE_HANDLER(climate, climate::Climate) bool InitialStateIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_state(climate); }
#endif #endif
#ifdef USE_NUMBER #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 #endif
#ifdef USE_DATETIME_DATE #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 #endif
#ifdef USE_DATETIME_TIME #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 #endif
#ifdef USE_DATETIME_DATETIME #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 #endif
#ifdef USE_TEXT #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 #endif
#ifdef USE_SELECT #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 #endif
#ifdef USE_LOCK #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 #endif
#ifdef USE_VALVE #ifdef USE_VALVE
INITIAL_STATE_HANDLER(valve, valve::Valve) bool InitialStateIterator::on_valve(valve::Valve *valve) { return this->client_->send_valve_state(valve); }
#endif #endif
#ifdef USE_MEDIA_PLAYER #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 #endif
#ifdef USE_ALARM_CONTROL_PANEL #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 #endif
#ifdef USE_UPDATE #ifdef USE_UPDATE
INITIAL_STATE_HANDLER(update, update::UpdateEntity) bool InitialStateIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_state(update); }
#endif #endif
// Special cases (button and event) are already defined inline in subscribe_state.h
InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {} InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {}
} // namespace api } // namespace api

View File

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

View File

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

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

View File

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

View File

@@ -8,7 +8,7 @@ namespace as7341 {
static const char *const TAG = "as7341"; static const char *const TAG = "as7341";
void AS7341Component::setup() { void AS7341Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup"); ESP_LOGCONFIG(TAG, "Setting up AS7341...");
LOG_I2C_DEVICE(this); LOG_I2C_DEVICE(this);
// Verify device ID // Verify device ID
@@ -38,14 +38,12 @@ void AS7341Component::dump_config() {
ESP_LOGCONFIG(TAG, "AS7341:"); ESP_LOGCONFIG(TAG, "AS7341:");
LOG_I2C_DEVICE(this); LOG_I2C_DEVICE(this);
if (this->is_failed()) { if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); ESP_LOGE(TAG, "Communication with AS7341 failed!");
} }
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG, " Gain: %u", get_gain());
" Gain: %u\n" ESP_LOGCONFIG(TAG, " ATIME: %u", get_atime());
" ATIME: %u\n" ESP_LOGCONFIG(TAG, " ASTEP: %u", get_astep());
" ASTEP: %u",
get_gain(), get_atime(), get_astep());
LOG_SENSOR(" ", "F1", this->f1_); LOG_SENSOR(" ", "F1", this->f1_);
LOG_SENSOR(" ", "F2", this->f2_); LOG_SENSOR(" ", "F2", this->f2_);

View File

@@ -5,7 +5,6 @@ from esphome.const import (
PLATFORM_BK72XX, PLATFORM_BK72XX,
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_ESP8266, PLATFORM_ESP8266,
PLATFORM_LN882X,
PLATFORM_RTL87XX, PLATFORM_RTL87XX,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
@@ -15,23 +14,15 @@ CODEOWNERS = ["@OttoWinter"]
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
cv.Schema({}), cv.Schema({}),
cv.only_with_arduino, cv.only_with_arduino,
cv.only_on( cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX]),
[
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_BK72XX,
PLATFORM_LN882X,
PLATFORM_RTL87XX,
]
),
) )
@coroutine_with_priority(200.0) @coroutine_with_priority(200.0)
async def to_code(config): async def to_code(config):
if CORE.is_esp32 or CORE.is_libretiny: if CORE.is_esp32 or CORE.is_libretiny:
# https://github.com/ESP32Async/AsyncTCP # https://github.com/esphome/AsyncTCP/blob/master/library.json
cg.add_library("ESP32Async/AsyncTCP", "3.4.4") cg.add_library("esphome/AsyncTCP-esphome", "2.1.4")
elif CORE.is_esp8266: elif CORE.is_esp8266:
# https://github.com/ESP32Async/ESPAsyncTCP # https://github.com/esphome/ESPAsyncTCP
cg.add_library("ESP32Async/ESPAsyncTCP", "2.0.0") cg.add_library("esphome/ESPAsyncTCP-esphome", "2.0.0")

View File

@@ -71,22 +71,19 @@ bool AT581XComponent::i2c_read_reg(uint8_t addr, uint8_t &data) {
return this->read_register(addr, &data, 1) == esphome::i2c::NO_ERROR; return this->read_register(addr, &data, 1) == esphome::i2c::NO_ERROR;
} }
void AT581XComponent::setup() { ESP_LOGCONFIG(TAG, "Running setup"); } void AT581XComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up AT581X..."); }
void AT581XComponent::dump_config() { LOG_I2C_DEVICE(this); } void AT581XComponent::dump_config() { LOG_I2C_DEVICE(this); }
#define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0])) #define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0]))
bool AT581XComponent::i2c_write_config() { bool AT581XComponent::i2c_write_config() {
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG, "Writing new config for AT581X...");
"Writing new config for AT581X\n" ESP_LOGCONFIG(TAG, "Frequency: %dMHz", this->freq_);
"Frequency: %dMHz\n" ESP_LOGCONFIG(TAG, "Sensing distance: %d", this->delta_);
"Sensing distance: %d\n" ESP_LOGCONFIG(TAG, "Power: %dµA", this->power_);
"Power: %dµA\n" ESP_LOGCONFIG(TAG, "Gain: %d", this->gain_);
"Gain: %d\n" ESP_LOGCONFIG(TAG, "Trigger base time: %dms", this->trigger_base_time_ms_);
"Trigger base time: %dms\n" ESP_LOGCONFIG(TAG, "Trigger keep time: %dms", this->trigger_keep_time_ms_);
"Trigger keep time: %dms\n" ESP_LOGCONFIG(TAG, "Protect time: %dms", this->protect_time_ms_);
"Protect time: %dms\n" ESP_LOGCONFIG(TAG, "Self check time: %dms", this->self_check_time_ms_);
"Self check time: %dms",
this->freq_, this->delta_, this->power_, this->gain_, this->trigger_base_time_ms_,
this->trigger_keep_time_ms_, this->protect_time_ms_, this->self_check_time_ms_);
// Set frequency point // Set frequency point
if (!this->i2c_write_reg(FREQ_ADDR, GAIN61_VALUE)) { if (!this->i2c_write_reg(FREQ_ADDR, GAIN61_VALUE)) {

View File

@@ -14,8 +14,11 @@ namespace esphome {
namespace at581x { namespace at581x {
class AT581XComponent : public Component, public i2c::I2CDevice { class AT581XComponent : public Component, public i2c::I2CDevice {
public:
#ifdef USE_SWITCH #ifdef USE_SWITCH
protected:
switch_::Switch *rf_power_switch_{nullptr};
public:
void set_rf_power_switch(switch_::Switch *s) { void set_rf_power_switch(switch_::Switch *s) {
this->rf_power_switch_ = s; this->rf_power_switch_ = s;
s->turn_on(); s->turn_on();
@@ -45,9 +48,6 @@ class AT581XComponent : public Component, public i2c::I2CDevice {
bool i2c_read_reg(uint8_t addr, uint8_t &data); bool i2c_read_reg(uint8_t addr, uint8_t &data);
protected: protected:
#ifdef USE_SWITCH
switch_::Switch *rf_power_switch_{nullptr};
#endif
int freq_; int freq_;
int self_check_time_ms_; /*!< Power-on self-test time, range: 0 ~ 65536 ms */ int self_check_time_ms_; /*!< Power-on self-test time, range: 0 ~ 65536 ms */
int protect_time_ms_; /*!< Protection time, recommended 1000 ms */ int protect_time_ms_; /*!< Protection time, recommended 1000 ms */

View File

@@ -25,6 +25,7 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; } void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; }

View File

@@ -41,7 +41,7 @@ void ATM90E26Component::update() {
} }
void ATM90E26Component::setup() { void ATM90E26Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup"); ESP_LOGCONFIG(TAG, "Setting up ATM90E26 Component...");
this->spi_setup(); this->spi_setup();
uint16_t mmode = 0x422; // default values for everything but L/N line current gains uint16_t mmode = 0x422; // default values for everything but L/N line current gains
@@ -135,7 +135,7 @@ void ATM90E26Component::dump_config() {
ESP_LOGCONFIG("", "ATM90E26:"); ESP_LOGCONFIG("", "ATM90E26:");
LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" CS Pin: ", this->cs_);
if (this->is_failed()) { if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); ESP_LOGE(TAG, "Communication with ATM90E26 failed!");
} }
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Voltage A", this->voltage_sensor_); LOG_SENSOR(" ", "Voltage A", this->voltage_sensor_);

View File

@@ -1,7 +1,6 @@
#include "atm90e32.h" #include "atm90e32.h"
#include <cinttypes> #include <cinttypes>
#include <cmath> #include <cmath>
#include <numbers>
#include "esphome/core/log.h" #include "esphome/core/log.h"
namespace esphome { namespace esphome {
@@ -109,7 +108,7 @@ void ATM90E32Component::update() {
} }
void ATM90E32Component::setup() { void ATM90E32Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup"); ESP_LOGCONFIG(TAG, "Setting up ATM90E32 Component...");
this->spi_setup(); this->spi_setup();
uint16_t mmode0 = 0x87; // 3P4W 50Hz uint16_t mmode0 = 0x87; // 3P4W 50Hz
@@ -218,7 +217,7 @@ void ATM90E32Component::dump_config() {
ESP_LOGCONFIG("", "ATM90E32:"); ESP_LOGCONFIG("", "ATM90E32:");
LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" CS Pin: ", this->cs_);
if (this->is_failed()) { if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); ESP_LOGE(TAG, "Communication with ATM90E32 failed!");
} }
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Voltage A", this->phase_[PHASEA].voltage_sensor_); LOG_SENSOR(" ", "Voltage A", this->phase_[PHASEA].voltage_sensor_);
@@ -687,7 +686,7 @@ void ATM90E32Component::restore_power_offset_calibrations_() {
} }
void ATM90E32Component::clear_gain_calibrations() { void ATM90E32Component::clear_gain_calibrations() {
ESP_LOGI(TAG, "[CALIBRATION] Clearing stored gain calibrations and restoring config-defined values"); ESP_LOGI(TAG, "[CALIBRATION] Clearing stored gain calibrations and restoring config-defined values...");
for (int phase = 0; phase < 3; phase++) { for (int phase = 0; phase < 3; phase++) {
gain_phase_[phase].voltage_gain = this->phase_[phase].voltage_gain_; gain_phase_[phase].voltage_gain = this->phase_[phase].voltage_gain_;
@@ -849,7 +848,7 @@ uint16_t ATM90E32Component::calculate_voltage_threshold(int line_freq, uint16_t
float nominal_voltage = (line_freq == 60) ? 120.0f : 220.0f; float nominal_voltage = (line_freq == 60) ? 120.0f : 220.0f;
float target_voltage = nominal_voltage * multiplier; float target_voltage = nominal_voltage * multiplier;
float peak_01v = target_voltage * 100.0f * std::numbers::sqrt2_v<float>; // convert RMS → peak, scale to 0.01V float peak_01v = target_voltage * 100.0f * std::sqrt(2.0f); // convert RMS → peak, scale to 0.01V
float divider = (2.0f * ugain) / 32768.0f; float divider = (2.0f * ugain) / 32768.0f;
float threshold = peak_01v / divider; float threshold = peak_01v / divider;

View File

@@ -312,7 +312,7 @@ FileDecoderState AudioDecoder::decode_mp3_() {
if (err) { if (err) {
switch (err) { switch (err) {
case esp_audio_libs::helix_decoder::ERR_MP3_OUT_OF_MEMORY: case esp_audio_libs::helix_decoder::ERR_MP3_OUT_OF_MEMORY:
[[fallthrough]]; // Intentional fallthrough
case esp_audio_libs::helix_decoder::ERR_MP3_NULL_POINTER: case esp_audio_libs::helix_decoder::ERR_MP3_NULL_POINTER:
return FileDecoderState::FAILED; return FileDecoderState::FAILED;
break; break;

View File

@@ -5,7 +5,6 @@
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE #if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
#include "esp_crt_bundle.h" #include "esp_crt_bundle.h"
@@ -17,13 +16,13 @@ namespace audio {
static const uint32_t READ_WRITE_TIMEOUT_MS = 20; static const uint32_t READ_WRITE_TIMEOUT_MS = 20;
static const uint32_t CONNECTION_TIMEOUT_MS = 5000; static const uint32_t CONNECTION_TIMEOUT_MS = 5000;
static const uint8_t MAX_FETCHING_HEADER_ATTEMPTS = 6;
// The number of times the http read times out with no data before throwing an error
static const uint32_t ERROR_COUNT_NO_DATA_READ_TIMEOUT = 100;
static const size_t HTTP_STREAM_BUFFER_SIZE = 2048; static const size_t HTTP_STREAM_BUFFER_SIZE = 2048;
static const uint8_t MAX_REDIRECTIONS = 5; static const uint8_t MAX_REDIRECTION = 5;
static const char *const TAG = "audio_reader";
// Some common HTTP status codes - borrowed from http_request component accessed 20241224 // Some common HTTP status codes - borrowed from http_request component accessed 20241224
enum HttpStatus { enum HttpStatus {
@@ -95,7 +94,7 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) {
client_config.url = uri.c_str(); client_config.url = uri.c_str();
client_config.cert_pem = nullptr; client_config.cert_pem = nullptr;
client_config.disable_auto_redirect = false; client_config.disable_auto_redirect = false;
client_config.max_redirection_count = MAX_REDIRECTIONS; client_config.max_redirection_count = 10;
client_config.event_handler = http_event_handler; client_config.event_handler = http_event_handler;
client_config.user_data = this; client_config.user_data = this;
client_config.buffer_size = HTTP_STREAM_BUFFER_SIZE; client_config.buffer_size = HTTP_STREAM_BUFFER_SIZE;
@@ -117,29 +116,12 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) {
esp_err_t err = esp_http_client_open(this->client_, 0); esp_err_t err = esp_http_client_open(this->client_, 0);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to open URL");
this->cleanup_connection_(); this->cleanup_connection_();
return err; return err;
} }
int64_t header_length = esp_http_client_fetch_headers(this->client_); int64_t header_length = esp_http_client_fetch_headers(this->client_);
uint8_t reattempt_count = 0;
while ((header_length < 0) && (reattempt_count < MAX_FETCHING_HEADER_ATTEMPTS)) {
this->cleanup_connection_();
if (header_length != -ESP_ERR_HTTP_EAGAIN) {
// Serious error, no recovery
return ESP_FAIL;
} else {
// Reconnect from a fresh state to avoid a bug where it never reads the headers even if made available
this->client_ = esp_http_client_init(&client_config);
esp_http_client_open(this->client_, 0);
header_length = esp_http_client_fetch_headers(this->client_);
++reattempt_count;
}
}
if (header_length < 0) { if (header_length < 0) {
ESP_LOGE(TAG, "Failed to fetch headers");
this->cleanup_connection_(); this->cleanup_connection_();
return ESP_FAIL; return ESP_FAIL;
} }
@@ -153,7 +135,7 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) {
ssize_t redirect_count = 0; ssize_t redirect_count = 0;
while ((esp_http_client_set_redirection(this->client_) == ESP_OK) && (redirect_count < MAX_REDIRECTIONS)) { while ((esp_http_client_set_redirection(this->client_) == ESP_OK) && (redirect_count < MAX_REDIRECTION)) {
err = esp_http_client_open(this->client_, 0); err = esp_http_client_open(this->client_, 0);
if (err != ESP_OK) { if (err != ESP_OK) {
this->cleanup_connection_(); this->cleanup_connection_();
@@ -285,29 +267,27 @@ AudioReaderState AudioReader::http_read_() {
return AudioReaderState::FINISHED; return AudioReaderState::FINISHED;
} }
} else if (this->output_transfer_buffer_->free() > 0) { } else if (this->output_transfer_buffer_->free() > 0) {
int received_len = esp_http_client_read(this->client_, (char *) this->output_transfer_buffer_->get_buffer_end(), size_t bytes_to_read = this->output_transfer_buffer_->free();
this->output_transfer_buffer_->free()); int received_len =
esp_http_client_read(this->client_, (char *) this->output_transfer_buffer_->get_buffer_end(), bytes_to_read);
if (received_len > 0) { if (received_len > 0) {
this->output_transfer_buffer_->increase_buffer_length(received_len); this->output_transfer_buffer_->increase_buffer_length(received_len);
this->last_data_read_ms_ = millis(); this->last_data_read_ms_ = millis();
return AudioReaderState::READING; } else if (received_len < 0) {
} else if (received_len <= 0) {
// HTTP read error // HTTP read error
if (received_len == -1) { this->cleanup_connection_();
// A true connection error occured, no chance at recovery return AudioReaderState::FAILED;
this->cleanup_connection_(); } else {
return AudioReaderState::FAILED; if (bytes_to_read > 0) {
} // Read timed out
if ((millis() - this->last_data_read_ms_) > CONNECTION_TIMEOUT_MS) {
this->cleanup_connection_();
return AudioReaderState::FAILED;
}
// Read timed out, manually verify if it has been too long since the last successful read delay(READ_WRITE_TIMEOUT_MS);
if ((millis() - this->last_data_read_ms_) > MAX_FETCHING_HEADER_ATTEMPTS * CONNECTION_TIMEOUT_MS) {
ESP_LOGE(TAG, "Timed out");
this->cleanup_connection_();
return AudioReaderState::FAILED;
} }
delay(READ_WRITE_TIMEOUT_MS);
} }
} }

View File

@@ -86,7 +86,7 @@ bool AudioTransferBuffer::reallocate(size_t new_buffer_size) {
bool AudioTransferBuffer::allocate_buffer_(size_t buffer_size) { bool AudioTransferBuffer::allocate_buffer_(size_t buffer_size) {
this->buffer_size_ = buffer_size; this->buffer_size_ = buffer_size;
RAMAllocator<uint8_t> allocator; RAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
this->buffer_ = allocator.allocate(this->buffer_size_); this->buffer_ = allocator.allocate(this->buffer_size_);
if (this->buffer_ == nullptr) { if (this->buffer_ == nullptr) {
@@ -101,7 +101,7 @@ bool AudioTransferBuffer::allocate_buffer_(size_t buffer_size) {
void AudioTransferBuffer::deallocate_buffer_() { void AudioTransferBuffer::deallocate_buffer_() {
if (this->buffer_ != nullptr) { if (this->buffer_ != nullptr) {
RAMAllocator<uint8_t> allocator; RAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
allocator.deallocate(this->buffer_, this->buffer_size_); allocator.deallocate(this->buffer_, this->buffer_size_);
this->buffer_ = nullptr; this->buffer_ = nullptr;
this->data_start_ = nullptr; this->data_start_ = nullptr;

View File

@@ -17,7 +17,7 @@ constexpr static const uint8_t AXS_READ_TOUCHPAD[11] = {0xb5, 0xab, 0xa5, 0x5a,
} }
void AXS15231Touchscreen::setup() { void AXS15231Touchscreen::setup() {
ESP_LOGCONFIG(TAG, "Running setup"); ESP_LOGCONFIG(TAG, "Setting up AXS15231 Touchscreen...");
if (this->reset_pin_ != nullptr) { if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup(); this->reset_pin_->setup();
this->reset_pin_->digital_write(false); this->reset_pin_->digital_write(false);
@@ -60,10 +60,8 @@ void AXS15231Touchscreen::dump_config() {
LOG_I2C_DEVICE(this); LOG_I2C_DEVICE(this);
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_);
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG, " Width: %d", this->x_raw_max_);
" Width: %d\n" ESP_LOGCONFIG(TAG, " Height: %d", this->y_raw_max_);
" Height: %d",
this->x_raw_max_, this->y_raw_max_);
} }
} // namespace axs15231 } // namespace axs15231

View File

@@ -16,6 +16,7 @@ class BParasite : public Component, public esp32_ble_tracker::ESPBTDeviceListene
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_battery_voltage(sensor::Sensor *battery_voltage) { battery_voltage_ = battery_voltage; } void set_battery_voltage(sensor::Sensor *battery_voltage) { battery_voltage_ = battery_voltage; }
void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }

View File

@@ -1,5 +1,7 @@
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import climate_ir from esphome.components import climate_ir
import esphome.config_validation as cv
from esphome.const import CONF_ID
AUTO_LOAD = ["climate_ir"] AUTO_LOAD = ["climate_ir"]
CODEOWNERS = ["@bazuchan"] CODEOWNERS = ["@bazuchan"]
@@ -7,8 +9,13 @@ CODEOWNERS = ["@bazuchan"]
ballu_ns = cg.esphome_ns.namespace("ballu") ballu_ns = cg.esphome_ns.namespace("ballu")
BalluClimate = ballu_ns.class_("BalluClimate", climate_ir.ClimateIR) BalluClimate = ballu_ns.class_("BalluClimate", climate_ir.ClimateIR)
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(BalluClimate) CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(BalluClimate),
}
)
async def to_code(config): async def to_code(config):
await climate_ir.new_climate_ir(config) var = cg.new_Pvariable(config[CONF_ID])
await climate_ir.register_climate_ir(var, config)

View File

@@ -194,14 +194,11 @@ Trigger<> *BangBangClimate::get_heat_trigger() const { return this->heat_trigger
void BangBangClimate::set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; } void BangBangClimate::set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
void BangBangClimate::dump_config() { void BangBangClimate::dump_config() {
LOG_CLIMATE("", "Bang Bang Climate", this); LOG_CLIMATE("", "Bang Bang Climate", this);
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG, " Supports HEAT: %s", YESNO(this->supports_heat_));
" Supports HEAT: %s\n" ESP_LOGCONFIG(TAG, " Supports COOL: %s", YESNO(this->supports_cool_));
" Supports COOL: %s\n" ESP_LOGCONFIG(TAG, " Supports AWAY mode: %s", YESNO(this->supports_away_));
" Supports AWAY mode: %s\n" ESP_LOGCONFIG(TAG, " Default Target Temperature Low: %.2f°C", this->normal_config_.default_temperature_low);
" Default Target Temperature Low: %.2f°C\n" ESP_LOGCONFIG(TAG, " Default Target Temperature High: %.2f°C", this->normal_config_.default_temperature_high);
" Default Target Temperature High: %.2f°C",
YESNO(this->supports_heat_), YESNO(this->supports_cool_), YESNO(this->supports_away_),
this->normal_config_.default_temperature_low, this->normal_config_.default_temperature_high);
} }
BangBangClimateTargetTempConfig::BangBangClimateTargetTempConfig() = default; BangBangClimateTargetTempConfig::BangBangClimateTargetTempConfig() = default;

View File

@@ -9,6 +9,7 @@ from esphome.const import (
CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_DEFAULT_TARGET_TEMPERATURE_LOW,
CONF_HEAT_ACTION, CONF_HEAT_ACTION,
CONF_HUMIDITY_SENSOR, CONF_HUMIDITY_SENSOR,
CONF_ID,
CONF_IDLE_ACTION, CONF_IDLE_ACTION,
CONF_SENSOR, CONF_SENSOR,
) )
@@ -18,9 +19,9 @@ BangBangClimate = bang_bang_ns.class_("BangBangClimate", climate.Climate, cg.Com
BangBangClimateTargetTempConfig = bang_bang_ns.struct("BangBangClimateTargetTempConfig") BangBangClimateTargetTempConfig = bang_bang_ns.struct("BangBangClimateTargetTempConfig")
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
climate.climate_schema(BangBangClimate) climate.CLIMATE_SCHEMA.extend(
.extend(
{ {
cv.GenerateID(): cv.declare_id(BangBangClimate),
cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
cv.Optional(CONF_HUMIDITY_SENSOR): cv.use_id(sensor.Sensor), cv.Optional(CONF_HUMIDITY_SENSOR): cv.use_id(sensor.Sensor),
cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature,
@@ -35,15 +36,15 @@ CONFIG_SCHEMA = cv.All(
} }
), ),
} }
) ).extend(cv.COMPONENT_SCHEMA),
.extend(cv.COMPONENT_SCHEMA),
cv.has_at_least_one_key(CONF_COOL_ACTION, CONF_HEAT_ACTION), cv.has_at_least_one_key(CONF_COOL_ACTION, CONF_HEAT_ACTION),
) )
async def to_code(config): 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 cg.register_component(var, config)
await climate.register_climate(var, config)
sens = await cg.get_variable(config[CONF_SENSOR]) sens = await cg.get_variable(config[CONF_SENSOR])
cg.add(var.set_sensor(sens)) cg.add(var.set_sensor(sens))

View File

@@ -3,7 +3,6 @@
#include "bedjet_hub.h" #include "bedjet_hub.h"
#include "bedjet_child.h" #include "bedjet_child.h"
#include "bedjet_const.h" #include "bedjet_const.h"
#include "esphome/core/application.h"
#include <cinttypes> #include <cinttypes>
namespace esphome { namespace esphome {
@@ -480,19 +479,13 @@ void BedJetHub::set_clock(uint8_t hour, uint8_t minute) {
/* Internal */ /* Internal */
void BedJetHub::loop() { void BedJetHub::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 BedJetHub::update() { this->dispatch_status_(); } void BedJetHub::update() { this->dispatch_status_(); }
void BedJetHub::dump_config() { void BedJetHub::dump_config() {
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG, "BedJet Hub '%s'", this->get_name().c_str());
"BedJet Hub '%s'\n" ESP_LOGCONFIG(TAG, " ble_client.app_id: %d", this->parent()->app_id);
" ble_client.app_id: %d\n" ESP_LOGCONFIG(TAG, " ble_client.conn_id: %d", this->parent()->get_conn_id());
" ble_client.conn_id: %d",
this->get_name().c_str(), this->parent()->app_id, this->parent()->get_conn_id());
LOG_UPDATE_INTERVAL(this) LOG_UPDATE_INTERVAL(this)
ESP_LOGCONFIG(TAG, " Child components (%d):", this->children_.size()); ESP_LOGCONFIG(TAG, " Child components (%d):", this->children_.size());
for (auto *child : this->children_) { for (auto *child : this->children_) {
@@ -533,7 +526,7 @@ void BedJetHub::dispatch_status_() {
} }
if (this->timeout_ > 0 && diff > this->timeout_ && this->parent()->enabled) { if (this->timeout_ > 0 && diff > this->timeout_ && this->parent()->enabled) {
ESP_LOGW(TAG, "[%s] Timed out after %" PRId32 " sec. Retrying", this->get_name().c_str(), this->timeout_); ESP_LOGW(TAG, "[%s] Timed out after %" PRId32 " sec. Retrying...", this->get_name().c_str(), this->timeout_);
// set_enabled(false) will only close the connection if state != IDLE. // set_enabled(false) will only close the connection if state != IDLE.
this->parent()->set_state(espbt::ClientState::CONNECTING); this->parent()->set_state(espbt::ClientState::CONNECTING);
this->parent()->set_enabled(false); this->parent()->set_enabled(false);

View File

@@ -1,8 +1,11 @@
import logging
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import ble_client, climate from esphome.components import ble_client, climate
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_HEAT_MODE, CONF_HEAT_MODE,
CONF_ID,
CONF_RECEIVE_TIMEOUT, CONF_RECEIVE_TIMEOUT,
CONF_TEMPERATURE_SOURCE, CONF_TEMPERATURE_SOURCE,
CONF_TIME_ID, CONF_TIME_ID,
@@ -10,6 +13,7 @@ from esphome.const import (
from .. import BEDJET_CLIENT_SCHEMA, bedjet_ns, register_bedjet_child from .. import BEDJET_CLIENT_SCHEMA, bedjet_ns, register_bedjet_child
_LOGGER = logging.getLogger(__name__)
CODEOWNERS = ["@jhansche"] CODEOWNERS = ["@jhansche"]
DEPENDENCIES = ["bedjet"] DEPENDENCIES = ["bedjet"]
@@ -26,9 +30,9 @@ BEDJET_TEMPERATURE_SOURCES = {
} }
CONFIG_SCHEMA = ( CONFIG_SCHEMA = (
climate.climate_schema(BedJetClimate) climate.CLIMATE_SCHEMA.extend(
.extend(
{ {
cv.GenerateID(): cv.declare_id(BedJetClimate),
cv.Optional(CONF_HEAT_MODE, default="heat"): cv.enum( cv.Optional(CONF_HEAT_MODE, default="heat"): cv.enum(
BEDJET_HEAT_MODES, lower=True BEDJET_HEAT_MODES, lower=True
), ),
@@ -59,8 +63,9 @@ CONFIG_SCHEMA = (
async def to_code(config): 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 cg.register_component(var, config)
await climate.register_climate(var, config)
await register_bedjet_child(var, config) await register_bedjet_child(var, config)
cg.add(var.set_heating_mode(config[CONF_HEAT_MODE])) cg.add(var.set_heating_mode(config[CONF_HEAT_MODE]))

View File

@@ -83,11 +83,7 @@ void BedJetClimate::reset_state_() {
this->publish_state(); this->publish_state();
} }
void BedJetClimate::loop() { void BedJetClimate::loop() {}
// This component is controlled via the parent BedJetHub
// Empty loop not needed, disable to save CPU cycles
this->disable_loop();
}
void BedJetClimate::control(const ClimateCall &call) { void BedJetClimate::control(const ClimateCall &call) {
ESP_LOGD(TAG, "Received BedJetClimate::control"); ESP_LOGD(TAG, "Received BedJetClimate::control");

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