mirror of
https://github.com/esphome/esphome.git
synced 2025-08-24 11:09:24 +00:00
Compare commits
2 Commits
mdns_servi
...
error-outp
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b3b55c6b6b | ||
![]() |
52204b2439 |
@@ -168,8 +168,6 @@ This document provides essential context for AI models interacting with this pro
|
|||||||
* `platformio.ini`: Configures the PlatformIO build environments for different microcontrollers.
|
* `platformio.ini`: Configures the PlatformIO build environments for different microcontrollers.
|
||||||
* `.pre-commit-config.yaml`: Configures the pre-commit hooks for linting and formatting.
|
* `.pre-commit-config.yaml`: Configures the pre-commit hooks for linting and formatting.
|
||||||
* **CI/CD Pipeline:** Defined in `.github/workflows`.
|
* **CI/CD Pipeline:** Defined in `.github/workflows`.
|
||||||
* **Static Analysis & Development:**
|
|
||||||
* `esphome/core/defines.h`: A comprehensive header file containing all `#define` directives that can be added by components using `cg.add_define()` in Python. This file is used exclusively for development, static analysis tools, and CI testing - it is not used during runtime compilation. When developing components that add new defines, they must be added to this file to ensure proper IDE support and static analysis coverage. The file includes feature flags, build configurations, and platform-specific defines that help static analyzers understand the complete codebase without needing to compile for specific platforms.
|
|
||||||
|
|
||||||
## 6. Development & Testing Workflow
|
## 6. Development & Testing Workflow
|
||||||
|
|
||||||
|
2
.github/actions/restore-python/action.yml
vendored
2
.github/actions/restore-python/action.yml
vendored
@@ -22,7 +22,7 @@ runs:
|
|||||||
python-version: ${{ inputs.python-version }}
|
python-version: ${{ inputs.python-version }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.2.4
|
uses: actions/cache/restore@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
|
2
.github/workflows/auto-label-pr.yml
vendored
2
.github/workflows/auto-label-pr.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
if: github.event.action != 'labeled' || github.event.sender.type != 'Bot'
|
if: github.event.action != 'labeled' || github.event.sender.type != 'Bot'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v4.2.2
|
||||||
|
|
||||||
- name: Generate a token
|
- name: Generate a token
|
||||||
id: generate-token
|
id: generate-token
|
||||||
|
2
.github/workflows/ci-api-proto.yml
vendored
2
.github/workflows/ci-api-proto.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v4.2.2
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@v5.6.0
|
||||||
with:
|
with:
|
||||||
|
2
.github/workflows/ci-clang-tidy-hash.yml
vendored
2
.github/workflows/ci-clang-tidy-hash.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v4.2.2
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@v5.6.0
|
||||||
|
2
.github/workflows/ci-docker.yml
vendored
2
.github/workflows/ci-docker.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
|||||||
- "docker"
|
- "docker"
|
||||||
# - "lint"
|
# - "lint"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5.0.0
|
- uses: actions/checkout@v4.2.2
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@v5.6.0
|
||||||
with:
|
with:
|
||||||
|
32
.github/workflows/ci.yml
vendored
32
.github/workflows/ci.yml
vendored
@@ -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@v5.0.0
|
uses: actions/checkout@v4.2.2
|
||||||
- name: Generate cache-key
|
- name: Generate cache-key
|
||||||
id: cache-key
|
id: cache-key
|
||||||
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
|
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
|
||||||
@@ -47,7 +47,7 @@ jobs:
|
|||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v4.2.4
|
uses: actions/cache@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
if: needs.determine-jobs.outputs.python-linters == 'true'
|
if: needs.determine-jobs.outputs.python-linters == 'true'
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v4.2.2
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
@@ -91,7 +91,7 @@ jobs:
|
|||||||
- common
|
- common
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v4.2.2
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
@@ -136,7 +136,7 @@ jobs:
|
|||||||
- common
|
- common
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v4.2.2
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
id: restore-python
|
id: restore-python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
@@ -161,7 +161,7 @@ jobs:
|
|||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
- name: Save Python virtual environment cache
|
- name: Save Python virtual environment cache
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
uses: actions/cache/save@v4.2.4
|
uses: actions/cache/save@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||||
@@ -179,7 +179,7 @@ jobs:
|
|||||||
component-test-count: ${{ steps.determine.outputs.component-test-count }}
|
component-test-count: ${{ steps.determine.outputs.component-test-count }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v4.2.2
|
||||||
with:
|
with:
|
||||||
# Fetch enough history to find the merge base
|
# Fetch enough history to find the merge base
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
@@ -214,7 +214,7 @@ jobs:
|
|||||||
if: needs.determine-jobs.outputs.integration-tests == 'true'
|
if: needs.determine-jobs.outputs.integration-tests == 'true'
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v4.2.2
|
||||||
- name: Set up Python 3.13
|
- name: Set up Python 3.13
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@v5.6.0
|
||||||
@@ -222,7 +222,7 @@ jobs:
|
|||||||
python-version: "3.13"
|
python-version: "3.13"
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v4.2.4
|
uses: actions/cache@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||||
@@ -287,7 +287,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v4.2.2
|
||||||
with:
|
with:
|
||||||
# Need history for HEAD~1 to work for checking changed files
|
# Need history for HEAD~1 to work for checking changed files
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
@@ -300,14 +300,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
uses: actions/cache@v4.2.4
|
uses: actions/cache@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref != 'refs/heads/dev'
|
if: github.ref != 'refs/heads/dev'
|
||||||
uses: actions/cache/restore@v4.2.4
|
uses: actions/cache/restore@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||||
@@ -374,7 +374,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@v5.0.0
|
uses: actions/checkout@v4.2.2
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
@@ -400,7 +400,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@v5.0.0
|
uses: actions/checkout@v4.2.2
|
||||||
- name: Split components into 20 groups
|
- name: Split components into 20 groups
|
||||||
id: split
|
id: split
|
||||||
run: |
|
run: |
|
||||||
@@ -430,7 +430,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@v5.0.0
|
uses: actions/checkout@v4.2.2
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
@@ -459,7 +459,7 @@ jobs:
|
|||||||
if: github.event_name == 'pull_request' && github.base_ref != 'beta' && github.base_ref != 'release'
|
if: github.event_name == 'pull_request' && github.base_ref != 'beta' && github.base_ref != 'release'
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v4.2.2
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
|
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@@ -54,7 +54,7 @@ jobs:
|
|||||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
|
18
.github/workflows/release.yml
vendored
18
.github/workflows/release.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
branch_build: ${{ steps.tag.outputs.branch_build }}
|
branch_build: ${{ steps.tag.outputs.branch_build }}
|
||||||
deploy_env: ${{ steps.tag.outputs.deploy_env }}
|
deploy_env: ${{ steps.tag.outputs.deploy_env }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5.0.0
|
- uses: actions/checkout@v4.2.2
|
||||||
- name: Get tag
|
- name: Get tag
|
||||||
id: tag
|
id: tag
|
||||||
# yamllint disable rule:line-length
|
# yamllint disable rule:line-length
|
||||||
@@ -60,7 +60,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5.0.0
|
- uses: actions/checkout@v4.2.2
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@v5.6.0
|
||||||
with:
|
with:
|
||||||
@@ -92,7 +92,7 @@ jobs:
|
|||||||
os: "ubuntu-24.04-arm"
|
os: "ubuntu-24.04-arm"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5.0.0
|
- uses: actions/checkout@v4.2.2
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@v5.6.0
|
||||||
with:
|
with:
|
||||||
@@ -102,12 +102,12 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@v3.11.1
|
uses: docker/setup-buildx-action@v3.11.1
|
||||||
|
|
||||||
- name: Log in to docker hub
|
- name: Log in to docker hub
|
||||||
uses: docker/login-action@v3.5.0
|
uses: docker/login-action@v3.4.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
- name: Log in to the GitHub container registry
|
- name: Log in to the GitHub container registry
|
||||||
uses: docker/login-action@v3.5.0
|
uses: docker/login-action@v3.4.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -168,10 +168,10 @@ jobs:
|
|||||||
- ghcr
|
- ghcr
|
||||||
- dockerhub
|
- dockerhub
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5.0.0
|
- uses: actions/checkout@v4.2.2
|
||||||
|
|
||||||
- name: Download digests
|
- name: Download digests
|
||||||
uses: actions/download-artifact@v5.0.0
|
uses: actions/download-artifact@v4.3.0
|
||||||
with:
|
with:
|
||||||
pattern: digests-*
|
pattern: digests-*
|
||||||
path: /tmp/digests
|
path: /tmp/digests
|
||||||
@@ -182,13 +182,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Log in to docker hub
|
- name: Log in to docker hub
|
||||||
if: matrix.registry == 'dockerhub'
|
if: matrix.registry == 'dockerhub'
|
||||||
uses: docker/login-action@v3.5.0
|
uses: docker/login-action@v3.4.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
- name: Log in to the GitHub container registry
|
- name: Log in to the GitHub container registry
|
||||||
if: matrix.registry == 'ghcr'
|
if: matrix.registry == 'ghcr'
|
||||||
uses: docker/login-action@v3.5.0
|
uses: docker/login-action@v3.4.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
|
4
.github/workflows/sync-device-classes.yml
vendored
4
.github/workflows/sync-device-classes.yml
vendored
@@ -13,10 +13,10 @@ jobs:
|
|||||||
if: github.repository == 'esphome/esphome'
|
if: github.repository == 'esphome/esphome'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v4.2.2
|
||||||
|
|
||||||
- name: Checkout Home Assistant
|
- name: Checkout Home Assistant
|
||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v4.2.2
|
||||||
with:
|
with:
|
||||||
repository: home-assistant/core
|
repository: home-assistant/core
|
||||||
path: lib/home-assistant
|
path: lib/home-assistant
|
||||||
|
@@ -11,7 +11,7 @@ ci:
|
|||||||
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.9
|
rev: v0.12.7
|
||||||
hooks:
|
hooks:
|
||||||
# Run the linter.
|
# Run the linter.
|
||||||
- id: ruff
|
- id: ruff
|
||||||
|
22
CODEOWNERS
22
CODEOWNERS
@@ -40,11 +40,11 @@ esphome/components/analog_threshold/* @ianchi
|
|||||||
esphome/components/animation/* @syndlex
|
esphome/components/animation/* @syndlex
|
||||||
esphome/components/anova/* @buxtronix
|
esphome/components/anova/* @buxtronix
|
||||||
esphome/components/apds9306/* @aodrenah
|
esphome/components/apds9306/* @aodrenah
|
||||||
esphome/components/api/* @esphome/core
|
esphome/components/api/* @OttoWinter
|
||||||
esphome/components/as5600/* @ammmze
|
esphome/components/as5600/* @ammmze
|
||||||
esphome/components/as5600/sensor/* @ammmze
|
esphome/components/as5600/sensor/* @ammmze
|
||||||
esphome/components/as7341/* @mrgnr
|
esphome/components/as7341/* @mrgnr
|
||||||
esphome/components/async_tcp/* @esphome/core
|
esphome/components/async_tcp/* @OttoWinter
|
||||||
esphome/components/at581x/* @X-Ryl669
|
esphome/components/at581x/* @X-Ryl669
|
||||||
esphome/components/atc_mithermometer/* @ahpohl
|
esphome/components/atc_mithermometer/* @ahpohl
|
||||||
esphome/components/atm90e26/* @danieltwagner
|
esphome/components/atm90e26/* @danieltwagner
|
||||||
@@ -69,7 +69,7 @@ esphome/components/bl0939/* @ziceva
|
|||||||
esphome/components/bl0940/* @tobias-
|
esphome/components/bl0940/* @tobias-
|
||||||
esphome/components/bl0942/* @dbuezas @dwmw2
|
esphome/components/bl0942/* @dbuezas @dwmw2
|
||||||
esphome/components/ble_client/* @buxtronix @clydebarrow
|
esphome/components/ble_client/* @buxtronix @clydebarrow
|
||||||
esphome/components/bluetooth_proxy/* @bdraco @jesserockz
|
esphome/components/bluetooth_proxy/* @jesserockz
|
||||||
esphome/components/bme280_base/* @esphome/core
|
esphome/components/bme280_base/* @esphome/core
|
||||||
esphome/components/bme280_spi/* @apbodrov
|
esphome/components/bme280_spi/* @apbodrov
|
||||||
esphome/components/bme680_bsec/* @trvrnrth
|
esphome/components/bme680_bsec/* @trvrnrth
|
||||||
@@ -91,7 +91,7 @@ esphome/components/bytebuffer/* @clydebarrow
|
|||||||
esphome/components/camera/* @DT-art1 @bdraco
|
esphome/components/camera/* @DT-art1 @bdraco
|
||||||
esphome/components/canbus/* @danielschramm @mvturnho
|
esphome/components/canbus/* @danielschramm @mvturnho
|
||||||
esphome/components/cap1188/* @mreditor97
|
esphome/components/cap1188/* @mreditor97
|
||||||
esphome/components/captive_portal/* @esphome/core
|
esphome/components/captive_portal/* @OttoWinter
|
||||||
esphome/components/ccs811/* @habbie
|
esphome/components/ccs811/* @habbie
|
||||||
esphome/components/cd74hc4067/* @asoehlke
|
esphome/components/cd74hc4067/* @asoehlke
|
||||||
esphome/components/ch422g/* @clydebarrow @jesterret
|
esphome/components/ch422g/* @clydebarrow @jesterret
|
||||||
@@ -118,7 +118,7 @@ esphome/components/dallas_temp/* @ssieb
|
|||||||
esphome/components/daly_bms/* @s1lvi0
|
esphome/components/daly_bms/* @s1lvi0
|
||||||
esphome/components/dashboard_import/* @esphome/core
|
esphome/components/dashboard_import/* @esphome/core
|
||||||
esphome/components/datetime/* @jesserockz @rfdarter
|
esphome/components/datetime/* @jesserockz @rfdarter
|
||||||
esphome/components/debug/* @esphome/core
|
esphome/components/debug/* @OttoWinter
|
||||||
esphome/components/delonghi/* @grob6000
|
esphome/components/delonghi/* @grob6000
|
||||||
esphome/components/dfplayer/* @glmnet
|
esphome/components/dfplayer/* @glmnet
|
||||||
esphome/components/dfrobot_sen0395/* @niklasweber
|
esphome/components/dfrobot_sen0395/* @niklasweber
|
||||||
@@ -144,10 +144,9 @@ esphome/components/es8156/* @kbx81
|
|||||||
esphome/components/es8311/* @kahrendt @kroimon
|
esphome/components/es8311/* @kahrendt @kroimon
|
||||||
esphome/components/es8388/* @P4uLT
|
esphome/components/es8388/* @P4uLT
|
||||||
esphome/components/esp32/* @esphome/core
|
esphome/components/esp32/* @esphome/core
|
||||||
esphome/components/esp32_ble/* @Rapsssito @bdraco @jesserockz
|
esphome/components/esp32_ble/* @Rapsssito @jesserockz
|
||||||
esphome/components/esp32_ble_client/* @bdraco @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_ble_tracker/* @bdraco
|
|
||||||
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_hosted/* @swoboda1337
|
||||||
@@ -238,7 +237,7 @@ esphome/components/integration/* @OttoWinter
|
|||||||
esphome/components/internal_temperature/* @Mat931
|
esphome/components/internal_temperature/* @Mat931
|
||||||
esphome/components/interval/* @esphome/core
|
esphome/components/interval/* @esphome/core
|
||||||
esphome/components/jsn_sr04t/* @Mafus1
|
esphome/components/jsn_sr04t/* @Mafus1
|
||||||
esphome/components/json/* @esphome/core
|
esphome/components/json/* @OttoWinter
|
||||||
esphome/components/kamstrup_kmp/* @cfeenstra1024
|
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
|
||||||
@@ -246,7 +245,6 @@ esphome/components/kuntze/* @ssieb
|
|||||||
esphome/components/lc709203f/* @ilikecake
|
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/ld2412/* @Rihan9
|
|
||||||
esphome/components/ld2420/* @descipher
|
esphome/components/ld2420/* @descipher
|
||||||
esphome/components/ld2450/* @hareeshmu
|
esphome/components/ld2450/* @hareeshmu
|
||||||
esphome/components/ld24xx/* @kbx81
|
esphome/components/ld24xx/* @kbx81
|
||||||
@@ -469,7 +467,7 @@ esphome/components/template/event/* @nohat
|
|||||||
esphome/components/template/fan/* @ssieb
|
esphome/components/template/fan/* @ssieb
|
||||||
esphome/components/text/* @mauritskorse
|
esphome/components/text/* @mauritskorse
|
||||||
esphome/components/thermostat/* @kbx81
|
esphome/components/thermostat/* @kbx81
|
||||||
esphome/components/time/* @esphome/core
|
esphome/components/time/* @OttoWinter
|
||||||
esphome/components/tlc5947/* @rnauber
|
esphome/components/tlc5947/* @rnauber
|
||||||
esphome/components/tlc5971/* @IJIJI
|
esphome/components/tlc5971/* @IJIJI
|
||||||
esphome/components/tm1621/* @Philippe12
|
esphome/components/tm1621/* @Philippe12
|
||||||
@@ -513,7 +511,7 @@ 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
|
||||||
esphome/components/web_server/ota/* @esphome/core
|
esphome/components/web_server/ota/* @esphome/core
|
||||||
esphome/components/web_server_base/* @esphome/core
|
esphome/components/web_server_base/* @OttoWinter
|
||||||
esphome/components/web_server_idf/* @dentra
|
esphome/components/web_server_idf/* @dentra
|
||||||
esphome/components/weikai/* @DrCoolZic
|
esphome/components/weikai/* @DrCoolZic
|
||||||
esphome/components/weikai_i2c/* @DrCoolZic
|
esphome/components/weikai_i2c/* @DrCoolZic
|
||||||
|
2
Doxyfile
2
Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
|||||||
# could be handy for archiving the generated documentation or if some version
|
# could be handy for archiving the generated documentation or if some version
|
||||||
# control system is used.
|
# control system is used.
|
||||||
|
|
||||||
PROJECT_NUMBER = 2025.9.0-dev
|
PROJECT_NUMBER = 2025.8.0-dev
|
||||||
|
|
||||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||||
# for a project that appears at the top of each page and should give viewer a
|
# for a project that appears at the top of each page and should give viewer a
|
||||||
|
@@ -90,7 +90,7 @@ def main():
|
|||||||
def run_command(*cmd, ignore_error: bool = False):
|
def run_command(*cmd, ignore_error: bool = False):
|
||||||
print(f"$ {shlex.join(list(cmd))}")
|
print(f"$ {shlex.join(list(cmd))}")
|
||||||
if not args.dry_run:
|
if not args.dry_run:
|
||||||
rc = subprocess.call(list(cmd), close_fds=False)
|
rc = subprocess.call(list(cmd))
|
||||||
if rc != 0 and not ignore_error:
|
if rc != 0 and not ignore_error:
|
||||||
print("Command failed")
|
print("Command failed")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
@@ -9,7 +9,6 @@ import os
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from typing import Protocol
|
|
||||||
|
|
||||||
import argcomplete
|
import argcomplete
|
||||||
|
|
||||||
@@ -45,7 +44,6 @@ from esphome.const import (
|
|||||||
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 AnsiFore, color, setup_log
|
||||||
from esphome.types import ConfigType
|
|
||||||
from esphome.util import (
|
from esphome.util import (
|
||||||
get_serial_ports,
|
get_serial_ports,
|
||||||
list_yaml_files,
|
list_yaml_files,
|
||||||
@@ -57,23 +55,6 @@ from esphome.util import (
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ArgsProtocol(Protocol):
|
|
||||||
device: list[str] | None
|
|
||||||
reset: bool
|
|
||||||
username: str | None
|
|
||||||
password: str | None
|
|
||||||
client_id: str | None
|
|
||||||
topic: str | None
|
|
||||||
file: str | None
|
|
||||||
no_logs: bool
|
|
||||||
only_generate: bool
|
|
||||||
show_secrets: bool
|
|
||||||
dashboard: bool
|
|
||||||
configuration: str
|
|
||||||
name: str
|
|
||||||
upload_speed: str | None
|
|
||||||
|
|
||||||
|
|
||||||
def choose_prompt(options, purpose: str = None):
|
def choose_prompt(options, purpose: str = None):
|
||||||
if not options:
|
if not options:
|
||||||
raise EsphomeError(
|
raise EsphomeError(
|
||||||
@@ -107,54 +88,30 @@ def choose_prompt(options, purpose: str = None):
|
|||||||
|
|
||||||
|
|
||||||
def choose_upload_log_host(
|
def choose_upload_log_host(
|
||||||
default: list[str] | str | None,
|
default, check_default, show_ota, show_mqtt, show_api, purpose: str = None
|
||||||
check_default: str | None,
|
):
|
||||||
show_ota: bool,
|
|
||||||
show_mqtt: bool,
|
|
||||||
show_api: bool,
|
|
||||||
purpose: str | None = None,
|
|
||||||
) -> list[str]:
|
|
||||||
# Convert to list for uniform handling
|
|
||||||
defaults = [default] if isinstance(default, str) else default or []
|
|
||||||
|
|
||||||
# If devices specified, resolve them
|
|
||||||
if defaults:
|
|
||||||
resolved: list[str] = []
|
|
||||||
for device in defaults:
|
|
||||||
if device == "SERIAL":
|
|
||||||
serial_ports = get_serial_ports()
|
|
||||||
if not serial_ports:
|
|
||||||
_LOGGER.warning("No serial ports found, skipping SERIAL device")
|
|
||||||
continue
|
|
||||||
options = [
|
|
||||||
(f"{port.path} ({port.description})", port.path)
|
|
||||||
for port in serial_ports
|
|
||||||
]
|
|
||||||
resolved.append(choose_prompt(options, purpose=purpose))
|
|
||||||
elif device == "OTA":
|
|
||||||
if (show_ota and "ota" in CORE.config) or (
|
|
||||||
show_api and "api" in CORE.config
|
|
||||||
):
|
|
||||||
resolved.append(CORE.address)
|
|
||||||
elif show_mqtt and has_mqtt_logging():
|
|
||||||
resolved.append("MQTT")
|
|
||||||
else:
|
|
||||||
resolved.append(device)
|
|
||||||
return resolved
|
|
||||||
|
|
||||||
# No devices specified, show interactive chooser
|
|
||||||
options = [
|
options = [
|
||||||
(f"{port.path} ({port.description})", port.path) for port in get_serial_ports()
|
(f"{port.path} ({port.description})", port.path) for port in get_serial_ports()
|
||||||
]
|
]
|
||||||
|
if default == "SERIAL":
|
||||||
|
return choose_prompt(options, purpose=purpose)
|
||||||
if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config):
|
if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config):
|
||||||
options.append((f"Over The Air ({CORE.address})", CORE.address))
|
options.append((f"Over The Air ({CORE.address})", CORE.address))
|
||||||
if show_mqtt and has_mqtt_logging():
|
if default == "OTA":
|
||||||
mqtt_config = CORE.config[CONF_MQTT]
|
return CORE.address
|
||||||
|
if (
|
||||||
|
show_mqtt
|
||||||
|
and (mqtt_config := CORE.config.get(CONF_MQTT))
|
||||||
|
and mqtt_logging_enabled(mqtt_config)
|
||||||
|
):
|
||||||
options.append((f"MQTT ({mqtt_config[CONF_BROKER]})", "MQTT"))
|
options.append((f"MQTT ({mqtt_config[CONF_BROKER]})", "MQTT"))
|
||||||
|
if default == "OTA":
|
||||||
|
return "MQTT"
|
||||||
|
if default is not None:
|
||||||
|
return default
|
||||||
if check_default is not None and check_default in [opt[1] for opt in options]:
|
if check_default is not None and check_default in [opt[1] for opt in options]:
|
||||||
return [check_default]
|
return check_default
|
||||||
return [choose_prompt(options, purpose=purpose)]
|
return choose_prompt(options, purpose=purpose)
|
||||||
|
|
||||||
|
|
||||||
def mqtt_logging_enabled(mqtt_config):
|
def mqtt_logging_enabled(mqtt_config):
|
||||||
@@ -166,14 +123,7 @@ def mqtt_logging_enabled(mqtt_config):
|
|||||||
return log_topic.get(CONF_LEVEL, None) != "NONE"
|
return log_topic.get(CONF_LEVEL, None) != "NONE"
|
||||||
|
|
||||||
|
|
||||||
def has_mqtt_logging() -> bool:
|
def get_port_type(port):
|
||||||
"""Check if MQTT logging is available."""
|
|
||||||
return (mqtt_config := CORE.config.get(CONF_MQTT)) and mqtt_logging_enabled(
|
|
||||||
mqtt_config
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_port_type(port: str) -> str:
|
|
||||||
if port.startswith("/") or port.startswith("COM"):
|
if port.startswith("/") or port.startswith("COM"):
|
||||||
return "SERIAL"
|
return "SERIAL"
|
||||||
if port == "MQTT":
|
if port == "MQTT":
|
||||||
@@ -181,7 +131,7 @@ def get_port_type(port: str) -> str:
|
|||||||
return "NETWORK"
|
return "NETWORK"
|
||||||
|
|
||||||
|
|
||||||
def run_miniterm(config: ConfigType, port: str, args) -> int:
|
def run_miniterm(config, port, args):
|
||||||
from aioesphomeapi import LogParser
|
from aioesphomeapi import LogParser
|
||||||
import serial
|
import serial
|
||||||
|
|
||||||
@@ -258,7 +208,7 @@ def wrap_to_code(name, comp):
|
|||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
def write_cpp(config: ConfigType) -> int:
|
def write_cpp(config):
|
||||||
if not get_bool_env(ENV_NOGITIGNORE):
|
if not get_bool_env(ENV_NOGITIGNORE):
|
||||||
writer.write_gitignore()
|
writer.write_gitignore()
|
||||||
|
|
||||||
@@ -266,7 +216,7 @@ def write_cpp(config: ConfigType) -> int:
|
|||||||
return write_cpp_file()
|
return write_cpp_file()
|
||||||
|
|
||||||
|
|
||||||
def generate_cpp_contents(config: ConfigType) -> None:
|
def generate_cpp_contents(config):
|
||||||
_LOGGER.info("Generating C++ source...")
|
_LOGGER.info("Generating C++ source...")
|
||||||
|
|
||||||
for name, component, conf in iter_component_configs(CORE.config):
|
for name, component, conf in iter_component_configs(CORE.config):
|
||||||
@@ -277,7 +227,7 @@ def generate_cpp_contents(config: ConfigType) -> None:
|
|||||||
CORE.flush_tasks()
|
CORE.flush_tasks()
|
||||||
|
|
||||||
|
|
||||||
def write_cpp_file() -> int:
|
def write_cpp_file():
|
||||||
code_s = indent(CORE.cpp_main_section)
|
code_s = indent(CORE.cpp_main_section)
|
||||||
writer.write_cpp(code_s)
|
writer.write_cpp(code_s)
|
||||||
|
|
||||||
@@ -288,7 +238,7 @@ def write_cpp_file() -> int:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def compile_program(args: ArgsProtocol, config: ConfigType) -> int:
|
def compile_program(args, config):
|
||||||
from esphome import platformio_api
|
from esphome import platformio_api
|
||||||
|
|
||||||
_LOGGER.info("Compiling app...")
|
_LOGGER.info("Compiling app...")
|
||||||
@@ -299,9 +249,7 @@ def compile_program(args: ArgsProtocol, config: ConfigType) -> int:
|
|||||||
return 0 if idedata is not None else 1
|
return 0 if idedata is not None else 1
|
||||||
|
|
||||||
|
|
||||||
def upload_using_esptool(
|
def upload_using_esptool(config, port, file, speed):
|
||||||
config: ConfigType, port: str, file: str, speed: int
|
|
||||||
) -> str | int:
|
|
||||||
from esphome import platformio_api
|
from esphome import platformio_api
|
||||||
|
|
||||||
first_baudrate = speed or config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
|
first_baudrate = speed or config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
|
||||||
@@ -329,20 +277,20 @@ def upload_using_esptool(
|
|||||||
|
|
||||||
def run_esptool(baud_rate):
|
def run_esptool(baud_rate):
|
||||||
cmd = [
|
cmd = [
|
||||||
"esptool",
|
"esptool.py",
|
||||||
"--before",
|
"--before",
|
||||||
"default-reset",
|
"default_reset",
|
||||||
"--after",
|
"--after",
|
||||||
"hard-reset",
|
"hard_reset",
|
||||||
"--baud",
|
"--baud",
|
||||||
str(baud_rate),
|
str(baud_rate),
|
||||||
"--port",
|
"--port",
|
||||||
port,
|
port,
|
||||||
"--chip",
|
"--chip",
|
||||||
mcu,
|
mcu,
|
||||||
"write-flash",
|
"write_flash",
|
||||||
"-z",
|
"-z",
|
||||||
"--flash-size",
|
"--flash_size",
|
||||||
"detect",
|
"detect",
|
||||||
]
|
]
|
||||||
for img in flash_images:
|
for img in flash_images:
|
||||||
@@ -366,7 +314,7 @@ def upload_using_esptool(
|
|||||||
return run_esptool(115200)
|
return run_esptool(115200)
|
||||||
|
|
||||||
|
|
||||||
def upload_using_platformio(config: ConfigType, port: str):
|
def upload_using_platformio(config, port):
|
||||||
from esphome import platformio_api
|
from esphome import platformio_api
|
||||||
|
|
||||||
upload_args = ["-t", "upload", "-t", "nobuild"]
|
upload_args = ["-t", "upload", "-t", "nobuild"]
|
||||||
@@ -375,7 +323,7 @@ def upload_using_platformio(config: ConfigType, port: str):
|
|||||||
return platformio_api.run_platformio_cli_run(config, CORE.verbose, *upload_args)
|
return platformio_api.run_platformio_cli_run(config, CORE.verbose, *upload_args)
|
||||||
|
|
||||||
|
|
||||||
def check_permissions(port: str):
|
def check_permissions(port):
|
||||||
if os.name == "posix" and get_port_type(port) == "SERIAL":
|
if os.name == "posix" and get_port_type(port) == "SERIAL":
|
||||||
# Check if we can open selected serial port
|
# Check if we can open selected serial port
|
||||||
if not os.access(port, os.F_OK):
|
if not os.access(port, os.F_OK):
|
||||||
@@ -393,7 +341,7 @@ def check_permissions(port: str):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def upload_program(config: ConfigType, args: ArgsProtocol, host: str) -> int | str:
|
def upload_program(config, args, host):
|
||||||
try:
|
try:
|
||||||
module = importlib.import_module("esphome.components." + CORE.target_platform)
|
module = importlib.import_module("esphome.components." + CORE.target_platform)
|
||||||
if getattr(module, "upload_program")(config, args, host):
|
if getattr(module, "upload_program")(config, args, host):
|
||||||
@@ -408,7 +356,7 @@ def upload_program(config: ConfigType, args: ArgsProtocol, host: str) -> int | s
|
|||||||
return upload_using_esptool(config, host, file, args.upload_speed)
|
return upload_using_esptool(config, host, file, args.upload_speed)
|
||||||
|
|
||||||
if CORE.target_platform in (PLATFORM_RP2040):
|
if CORE.target_platform in (PLATFORM_RP2040):
|
||||||
return upload_using_platformio(config, host)
|
return upload_using_platformio(config, args.device)
|
||||||
|
|
||||||
if CORE.is_libretiny:
|
if CORE.is_libretiny:
|
||||||
return upload_using_platformio(config, host)
|
return upload_using_platformio(config, host)
|
||||||
@@ -431,12 +379,9 @@ def upload_program(config: ConfigType, args: ArgsProtocol, host: str) -> int | s
|
|||||||
remote_port = int(ota_conf[CONF_PORT])
|
remote_port = int(ota_conf[CONF_PORT])
|
||||||
password = ota_conf.get(CONF_PASSWORD, "")
|
password = ota_conf.get(CONF_PASSWORD, "")
|
||||||
|
|
||||||
# Check if we should use MQTT for address resolution
|
|
||||||
# This happens when no device was specified, or the current host is "MQTT"/"OTA"
|
|
||||||
devices: list[str] = args.device or []
|
|
||||||
if (
|
if (
|
||||||
CONF_MQTT in config # pylint: disable=too-many-boolean-expressions
|
CONF_MQTT in config # pylint: disable=too-many-boolean-expressions
|
||||||
and (not devices or host in ("MQTT", "OTA"))
|
and (not args.device or args.device in ("MQTT", "OTA"))
|
||||||
and (
|
and (
|
||||||
((config[CONF_MDNS][CONF_DISABLED]) and not is_ip_address(CORE.address))
|
((config[CONF_MDNS][CONF_DISABLED]) and not is_ip_address(CORE.address))
|
||||||
or get_port_type(host) == "MQTT"
|
or get_port_type(host) == "MQTT"
|
||||||
@@ -454,28 +399,23 @@ def upload_program(config: ConfigType, args: ArgsProtocol, host: str) -> int | s
|
|||||||
return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
||||||
|
|
||||||
|
|
||||||
def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int | None:
|
def show_logs(config, args, port):
|
||||||
if "logger" not in config:
|
if "logger" not in config:
|
||||||
raise EsphomeError("Logger is not configured!")
|
raise EsphomeError("Logger is not configured!")
|
||||||
|
|
||||||
port = devices[0]
|
|
||||||
|
|
||||||
if get_port_type(port) == "SERIAL":
|
if get_port_type(port) == "SERIAL":
|
||||||
check_permissions(port)
|
check_permissions(port)
|
||||||
return run_miniterm(config, port, args)
|
return run_miniterm(config, port, args)
|
||||||
if get_port_type(port) == "NETWORK" and "api" in config:
|
if get_port_type(port) == "NETWORK" and "api" in config:
|
||||||
addresses_to_use = devices
|
|
||||||
if config[CONF_MDNS][CONF_DISABLED] and CONF_MQTT in config:
|
if config[CONF_MDNS][CONF_DISABLED] and CONF_MQTT in config:
|
||||||
from esphome import mqtt
|
from esphome import mqtt
|
||||||
|
|
||||||
mqtt_address = mqtt.get_esphome_device_ip(
|
port = mqtt.get_esphome_device_ip(
|
||||||
config, args.username, args.password, args.client_id
|
config, args.username, args.password, args.client_id
|
||||||
)[0]
|
)[0]
|
||||||
addresses_to_use = [mqtt_address]
|
|
||||||
|
|
||||||
from esphome.components.api.client import run_logs
|
from esphome.components.api.client import run_logs
|
||||||
|
|
||||||
return run_logs(config, addresses_to_use)
|
return run_logs(config, port)
|
||||||
if get_port_type(port) == "MQTT" and "mqtt" in config:
|
if get_port_type(port) == "MQTT" and "mqtt" in config:
|
||||||
from esphome import mqtt
|
from esphome import mqtt
|
||||||
|
|
||||||
@@ -486,7 +426,7 @@ def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int
|
|||||||
raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)")
|
raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)")
|
||||||
|
|
||||||
|
|
||||||
def clean_mqtt(config: ConfigType, args: ArgsProtocol) -> int | None:
|
def clean_mqtt(config, args):
|
||||||
from esphome import mqtt
|
from esphome import mqtt
|
||||||
|
|
||||||
return mqtt.clear_topic(
|
return mqtt.clear_topic(
|
||||||
@@ -494,13 +434,13 @@ def clean_mqtt(config: ConfigType, args: ArgsProtocol) -> int | None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def command_wizard(args: ArgsProtocol) -> int | None:
|
def command_wizard(args):
|
||||||
from esphome import wizard
|
from esphome import wizard
|
||||||
|
|
||||||
return wizard.wizard(args.configuration)
|
return wizard.wizard(args.configuration)
|
||||||
|
|
||||||
|
|
||||||
def command_config(args: ArgsProtocol, config: ConfigType) -> int | None:
|
def command_config(args, config):
|
||||||
if not CORE.verbose:
|
if not CORE.verbose:
|
||||||
config = strip_default_ids(config)
|
config = strip_default_ids(config)
|
||||||
output = yaml_util.dump(config, args.show_secrets)
|
output = yaml_util.dump(config, args.show_secrets)
|
||||||
@@ -515,7 +455,7 @@ def command_config(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def command_vscode(args: ArgsProtocol) -> int | None:
|
def command_vscode(args):
|
||||||
from esphome import vscode
|
from esphome import vscode
|
||||||
|
|
||||||
logging.disable(logging.INFO)
|
logging.disable(logging.INFO)
|
||||||
@@ -523,7 +463,7 @@ def command_vscode(args: ArgsProtocol) -> int | None:
|
|||||||
vscode.read_config(args)
|
vscode.read_config(args)
|
||||||
|
|
||||||
|
|
||||||
def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None:
|
def command_compile(args, config):
|
||||||
exit_code = write_cpp(config)
|
exit_code = write_cpp(config)
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
return exit_code
|
return exit_code
|
||||||
@@ -537,9 +477,8 @@ def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def command_upload(args: ArgsProtocol, config: ConfigType) -> int | None:
|
def command_upload(args, config):
|
||||||
# Get devices, resolving special identifiers like OTA
|
port = choose_upload_log_host(
|
||||||
devices = choose_upload_log_host(
|
|
||||||
default=args.device,
|
default=args.device,
|
||||||
check_default=None,
|
check_default=None,
|
||||||
show_ota=True,
|
show_ota=True,
|
||||||
@@ -547,22 +486,14 @@ def command_upload(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|||||||
show_api=False,
|
show_api=False,
|
||||||
purpose="uploading",
|
purpose="uploading",
|
||||||
)
|
)
|
||||||
|
exit_code = upload_program(config, args, port)
|
||||||
# Try each device until one succeeds
|
if exit_code != 0:
|
||||||
exit_code = 1
|
return exit_code
|
||||||
for device in devices:
|
_LOGGER.info("Successfully uploaded program.")
|
||||||
_LOGGER.info("Uploading to %s", device)
|
return 0
|
||||||
exit_code = upload_program(config, args, device)
|
|
||||||
if exit_code == 0:
|
|
||||||
_LOGGER.info("Successfully uploaded program.")
|
|
||||||
return 0
|
|
||||||
if len(devices) > 1:
|
|
||||||
_LOGGER.warning("Failed to upload to %s", device)
|
|
||||||
|
|
||||||
return exit_code
|
|
||||||
|
|
||||||
|
|
||||||
def command_discover(args: ArgsProtocol, config: ConfigType) -> int | None:
|
def command_discover(args, config):
|
||||||
if "mqtt" in config:
|
if "mqtt" in config:
|
||||||
from esphome import mqtt
|
from esphome import mqtt
|
||||||
|
|
||||||
@@ -571,9 +502,8 @@ def command_discover(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|||||||
raise EsphomeError("No discover method configured (mqtt)")
|
raise EsphomeError("No discover method configured (mqtt)")
|
||||||
|
|
||||||
|
|
||||||
def command_logs(args: ArgsProtocol, config: ConfigType) -> int | None:
|
def command_logs(args, config):
|
||||||
# Get devices, resolving special identifiers like OTA
|
port = choose_upload_log_host(
|
||||||
devices = choose_upload_log_host(
|
|
||||||
default=args.device,
|
default=args.device,
|
||||||
check_default=None,
|
check_default=None,
|
||||||
show_ota=False,
|
show_ota=False,
|
||||||
@@ -581,10 +511,10 @@ def command_logs(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|||||||
show_api=True,
|
show_api=True,
|
||||||
purpose="logging",
|
purpose="logging",
|
||||||
)
|
)
|
||||||
return show_logs(config, args, devices)
|
return show_logs(config, args, port)
|
||||||
|
|
||||||
|
|
||||||
def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
|
def command_run(args, config):
|
||||||
exit_code = write_cpp(config)
|
exit_code = write_cpp(config)
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
return exit_code
|
return exit_code
|
||||||
@@ -601,8 +531,7 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|||||||
program_path = idedata.raw["prog_path"]
|
program_path = idedata.raw["prog_path"]
|
||||||
return run_external_process(program_path)
|
return run_external_process(program_path)
|
||||||
|
|
||||||
# Get devices, resolving special identifiers like OTA
|
port = choose_upload_log_host(
|
||||||
devices = choose_upload_log_host(
|
|
||||||
default=args.device,
|
default=args.device,
|
||||||
check_default=None,
|
check_default=None,
|
||||||
show_ota=True,
|
show_ota=True,
|
||||||
@@ -610,53 +539,39 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|||||||
show_api=True,
|
show_api=True,
|
||||||
purpose="uploading",
|
purpose="uploading",
|
||||||
)
|
)
|
||||||
|
exit_code = upload_program(config, args, port)
|
||||||
# Try each device for upload until one succeeds
|
if exit_code != 0:
|
||||||
successful_device: str | None = None
|
|
||||||
for device in devices:
|
|
||||||
_LOGGER.info("Uploading to %s", device)
|
|
||||||
exit_code = upload_program(config, args, device)
|
|
||||||
if exit_code == 0:
|
|
||||||
_LOGGER.info("Successfully uploaded program.")
|
|
||||||
successful_device = device
|
|
||||||
break
|
|
||||||
if len(devices) > 1:
|
|
||||||
_LOGGER.warning("Failed to upload to %s", device)
|
|
||||||
|
|
||||||
if successful_device is None:
|
|
||||||
return exit_code
|
return exit_code
|
||||||
|
_LOGGER.info("Successfully uploaded program.")
|
||||||
if args.no_logs:
|
if args.no_logs:
|
||||||
return 0
|
return 0
|
||||||
|
port = choose_upload_log_host(
|
||||||
# For logs, prefer the device we successfully uploaded to
|
default=args.device,
|
||||||
devices = choose_upload_log_host(
|
check_default=port,
|
||||||
default=successful_device,
|
|
||||||
check_default=successful_device,
|
|
||||||
show_ota=False,
|
show_ota=False,
|
||||||
show_mqtt=True,
|
show_mqtt=True,
|
||||||
show_api=True,
|
show_api=True,
|
||||||
purpose="logging",
|
purpose="logging",
|
||||||
)
|
)
|
||||||
return show_logs(config, args, devices)
|
return show_logs(config, args, port)
|
||||||
|
|
||||||
|
|
||||||
def command_clean_mqtt(args: ArgsProtocol, config: ConfigType) -> int | None:
|
def command_clean_mqtt(args, config):
|
||||||
return clean_mqtt(config, args)
|
return clean_mqtt(config, args)
|
||||||
|
|
||||||
|
|
||||||
def command_mqtt_fingerprint(args: ArgsProtocol, config: ConfigType) -> int | None:
|
def command_mqtt_fingerprint(args, config):
|
||||||
from esphome import mqtt
|
from esphome import mqtt
|
||||||
|
|
||||||
return mqtt.get_fingerprint(config)
|
return mqtt.get_fingerprint(config)
|
||||||
|
|
||||||
|
|
||||||
def command_version(args: ArgsProtocol) -> int | None:
|
def command_version(args):
|
||||||
safe_print(f"Version: {const.__version__}")
|
safe_print(f"Version: {const.__version__}")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def command_clean(args: ArgsProtocol, config: ConfigType) -> int | None:
|
def command_clean(args, config):
|
||||||
try:
|
try:
|
||||||
writer.clean_build()
|
writer.clean_build()
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
@@ -666,13 +581,13 @@ def command_clean(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def command_dashboard(args: ArgsProtocol) -> int | None:
|
def command_dashboard(args):
|
||||||
from esphome.dashboard import dashboard
|
from esphome.dashboard import dashboard
|
||||||
|
|
||||||
return dashboard.start_dashboard(args)
|
return dashboard.start_dashboard(args)
|
||||||
|
|
||||||
|
|
||||||
def command_update_all(args: ArgsProtocol) -> int | None:
|
def command_update_all(args):
|
||||||
import click
|
import click
|
||||||
|
|
||||||
success = {}
|
success = {}
|
||||||
@@ -719,7 +634,7 @@ def command_update_all(args: ArgsProtocol) -> int | None:
|
|||||||
return failed
|
return failed
|
||||||
|
|
||||||
|
|
||||||
def command_idedata(args: ArgsProtocol, config: ConfigType) -> int:
|
def command_idedata(args, config):
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from esphome import platformio_api
|
from esphome import platformio_api
|
||||||
@@ -735,7 +650,7 @@ def command_idedata(args: ArgsProtocol, config: ConfigType) -> int:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
|
def command_rename(args, config):
|
||||||
for c in args.name:
|
for c in args.name:
|
||||||
if c not in ALLOWED_NAME_CHARS:
|
if c not in ALLOWED_NAME_CHARS:
|
||||||
print(
|
print(
|
||||||
@@ -852,12 +767,6 @@ POST_CONFIG_ACTIONS = {
|
|||||||
"discover": command_discover,
|
"discover": command_discover,
|
||||||
}
|
}
|
||||||
|
|
||||||
SIMPLE_CONFIG_ACTIONS = [
|
|
||||||
"clean",
|
|
||||||
"clean-mqtt",
|
|
||||||
"config",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args(argv):
|
def parse_args(argv):
|
||||||
options_parser = argparse.ArgumentParser(add_help=False)
|
options_parser = argparse.ArgumentParser(add_help=False)
|
||||||
@@ -945,8 +854,7 @@ def parse_args(argv):
|
|||||||
)
|
)
|
||||||
parser_upload.add_argument(
|
parser_upload.add_argument(
|
||||||
"--device",
|
"--device",
|
||||||
action="append",
|
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
|
||||||
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0. Can be specified multiple times for fallback addresses.",
|
|
||||||
)
|
)
|
||||||
parser_upload.add_argument(
|
parser_upload.add_argument(
|
||||||
"--upload_speed",
|
"--upload_speed",
|
||||||
@@ -968,8 +876,7 @@ def parse_args(argv):
|
|||||||
)
|
)
|
||||||
parser_logs.add_argument(
|
parser_logs.add_argument(
|
||||||
"--device",
|
"--device",
|
||||||
action="append",
|
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
|
||||||
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0. Can be specified multiple times for fallback addresses.",
|
|
||||||
)
|
)
|
||||||
parser_logs.add_argument(
|
parser_logs.add_argument(
|
||||||
"--reset",
|
"--reset",
|
||||||
@@ -998,8 +905,7 @@ def parse_args(argv):
|
|||||||
)
|
)
|
||||||
parser_run.add_argument(
|
parser_run.add_argument(
|
||||||
"--device",
|
"--device",
|
||||||
action="append",
|
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
|
||||||
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0. Can be specified multiple times for fallback addresses.",
|
|
||||||
)
|
)
|
||||||
parser_run.add_argument(
|
parser_run.add_argument(
|
||||||
"--upload_speed",
|
"--upload_speed",
|
||||||
@@ -1126,13 +1032,6 @@ def parse_args(argv):
|
|||||||
arguments = argv[1:]
|
arguments = argv[1:]
|
||||||
|
|
||||||
argcomplete.autocomplete(parser)
|
argcomplete.autocomplete(parser)
|
||||||
|
|
||||||
if len(arguments) > 0 and arguments[0] in SIMPLE_CONFIG_ACTIONS:
|
|
||||||
args, unknown_args = parser.parse_known_args(arguments)
|
|
||||||
if unknown_args:
|
|
||||||
_LOGGER.warning("Ignored unrecognized arguments: %s", unknown_args)
|
|
||||||
return args
|
|
||||||
|
|
||||||
return parser.parse_args(arguments)
|
return parser.parse_args(arguments)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -5,7 +5,7 @@ from esphome.const import (
|
|||||||
CONF_EQUATION,
|
CONF_EQUATION,
|
||||||
CONF_HUMIDITY,
|
CONF_HUMIDITY,
|
||||||
CONF_TEMPERATURE,
|
CONF_TEMPERATURE,
|
||||||
DEVICE_CLASS_ABSOLUTE_HUMIDITY,
|
ICON_WATER,
|
||||||
STATE_CLASS_MEASUREMENT,
|
STATE_CLASS_MEASUREMENT,
|
||||||
UNIT_GRAMS_PER_CUBIC_METER,
|
UNIT_GRAMS_PER_CUBIC_METER,
|
||||||
)
|
)
|
||||||
@@ -27,8 +27,8 @@ EQUATION = {
|
|||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
sensor.sensor_schema(
|
sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_GRAMS_PER_CUBIC_METER,
|
unit_of_measurement=UNIT_GRAMS_PER_CUBIC_METER,
|
||||||
|
icon=ICON_WATER,
|
||||||
accuracy_decimals=2,
|
accuracy_decimals=2,
|
||||||
device_class=DEVICE_CLASS_ABSOLUTE_HUMIDITY,
|
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
)
|
)
|
||||||
.extend(
|
.extend(
|
||||||
|
@@ -36,7 +36,6 @@ from esphome.const import (
|
|||||||
UNIT_WATT,
|
UNIT_WATT,
|
||||||
UNIT_WATT_HOURS,
|
UNIT_WATT_HOURS,
|
||||||
)
|
)
|
||||||
from esphome.types import ConfigType
|
|
||||||
|
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
@@ -52,20 +51,6 @@ CONF_POWER_GAIN = "power_gain"
|
|||||||
|
|
||||||
CONF_NEUTRAL = "neutral"
|
CONF_NEUTRAL = "neutral"
|
||||||
|
|
||||||
# Tuple of power channel phases
|
|
||||||
POWER_PHASES = (CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C)
|
|
||||||
|
|
||||||
# Tuple of sensor types that can be configured for power channels
|
|
||||||
POWER_SENSOR_TYPES = (
|
|
||||||
CONF_CURRENT,
|
|
||||||
CONF_VOLTAGE,
|
|
||||||
CONF_ACTIVE_POWER,
|
|
||||||
CONF_APPARENT_POWER,
|
|
||||||
CONF_POWER_FACTOR,
|
|
||||||
CONF_FORWARD_ACTIVE_ENERGY,
|
|
||||||
CONF_REVERSE_ACTIVE_ENERGY,
|
|
||||||
)
|
|
||||||
|
|
||||||
NEUTRAL_CHANNEL_SCHEMA = cv.Schema(
|
NEUTRAL_CHANNEL_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(NeutralChannel),
|
cv.GenerateID(): cv.declare_id(NeutralChannel),
|
||||||
@@ -165,64 +150,7 @@ POWER_CHANNEL_SCHEMA = cv.Schema(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
def prefix_sensor_name(
|
|
||||||
sensor_conf: ConfigType,
|
|
||||||
channel_name: str,
|
|
||||||
channel_config: ConfigType,
|
|
||||||
sensor_type: str,
|
|
||||||
) -> None:
|
|
||||||
"""Helper to prefix sensor name with channel name.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
sensor_conf: The sensor configuration (dict or string)
|
|
||||||
channel_name: The channel name to prefix with
|
|
||||||
channel_config: The channel configuration to update
|
|
||||||
sensor_type: The sensor type key in the channel config
|
|
||||||
"""
|
|
||||||
if isinstance(sensor_conf, dict) and CONF_NAME in sensor_conf:
|
|
||||||
sensor_name = sensor_conf[CONF_NAME]
|
|
||||||
if sensor_name and not sensor_name.startswith(channel_name):
|
|
||||||
sensor_conf[CONF_NAME] = f"{channel_name} {sensor_name}"
|
|
||||||
elif isinstance(sensor_conf, str):
|
|
||||||
# Simple value case - convert to dict with prefixed name
|
|
||||||
channel_config[sensor_type] = {CONF_NAME: f"{channel_name} {sensor_conf}"}
|
|
||||||
|
|
||||||
|
|
||||||
def process_channel_sensors(
|
|
||||||
config: ConfigType, channel_key: str, sensor_types: tuple
|
|
||||||
) -> None:
|
|
||||||
"""Process sensors for a channel and prefix their names.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
config: The main configuration
|
|
||||||
channel_key: The channel key (e.g., CONF_PHASE_A, CONF_NEUTRAL)
|
|
||||||
sensor_types: Tuple of sensor types to process for this channel
|
|
||||||
"""
|
|
||||||
if not (channel_config := config.get(channel_key)) or not (
|
|
||||||
channel_name := channel_config.get(CONF_NAME)
|
|
||||||
):
|
|
||||||
return
|
|
||||||
|
|
||||||
for sensor_type in sensor_types:
|
|
||||||
if sensor_conf := channel_config.get(sensor_type):
|
|
||||||
prefix_sensor_name(sensor_conf, channel_name, channel_config, sensor_type)
|
|
||||||
|
|
||||||
|
|
||||||
def preprocess_channels(config: ConfigType) -> ConfigType:
|
|
||||||
"""Preprocess channel configurations to add channel name prefix to sensor names."""
|
|
||||||
# Process power channels
|
|
||||||
for channel in POWER_PHASES:
|
|
||||||
process_channel_sensors(config, channel, POWER_SENSOR_TYPES)
|
|
||||||
|
|
||||||
# Process neutral channel
|
|
||||||
process_channel_sensors(config, CONF_NEUTRAL, (CONF_CURRENT,))
|
|
||||||
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
|
||||||
preprocess_channels,
|
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(ADE7880),
|
cv.GenerateID(): cv.declare_id(ADE7880),
|
||||||
@@ -239,7 +167,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.extend(cv.polling_component_schema("60s"))
|
.extend(cv.polling_component_schema("60s"))
|
||||||
.extend(i2c.i2c_device_schema(0x38)),
|
.extend(i2c.i2c_device_schema(0x38))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -260,7 +188,15 @@ async def neutral_channel(config):
|
|||||||
async def power_channel(config):
|
async def power_channel(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
|
||||||
for sensor_type in POWER_SENSOR_TYPES:
|
for sensor_type in [
|
||||||
|
CONF_CURRENT,
|
||||||
|
CONF_VOLTAGE,
|
||||||
|
CONF_ACTIVE_POWER,
|
||||||
|
CONF_APPARENT_POWER,
|
||||||
|
CONF_POWER_FACTOR,
|
||||||
|
CONF_FORWARD_ACTIVE_ENERGY,
|
||||||
|
CONF_REVERSE_ACTIVE_ENERGY,
|
||||||
|
]:
|
||||||
if conf := config.get(sensor_type):
|
if conf := config.get(sensor_type):
|
||||||
sens = await sensor.new_sensor(conf)
|
sens = await sensor.new_sensor(conf)
|
||||||
cg.add(getattr(var, f"set_{sensor_type}")(sens))
|
cg.add(getattr(var, f"set_{sensor_type}")(sens))
|
||||||
@@ -280,6 +216,44 @@ async def power_channel(config):
|
|||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
def final_validate(config):
|
||||||
|
for channel in [CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]:
|
||||||
|
if channel := config.get(channel):
|
||||||
|
channel_name = channel.get(CONF_NAME)
|
||||||
|
|
||||||
|
for sensor_type in [
|
||||||
|
CONF_CURRENT,
|
||||||
|
CONF_VOLTAGE,
|
||||||
|
CONF_ACTIVE_POWER,
|
||||||
|
CONF_APPARENT_POWER,
|
||||||
|
CONF_POWER_FACTOR,
|
||||||
|
CONF_FORWARD_ACTIVE_ENERGY,
|
||||||
|
CONF_REVERSE_ACTIVE_ENERGY,
|
||||||
|
]:
|
||||||
|
if conf := channel.get(sensor_type):
|
||||||
|
sensor_name = conf.get(CONF_NAME)
|
||||||
|
if (
|
||||||
|
sensor_name
|
||||||
|
and channel_name
|
||||||
|
and not sensor_name.startswith(channel_name)
|
||||||
|
):
|
||||||
|
conf[CONF_NAME] = f"{channel_name} {sensor_name}"
|
||||||
|
|
||||||
|
if channel := config.get(CONF_NEUTRAL):
|
||||||
|
channel_name = channel.get(CONF_NAME)
|
||||||
|
if conf := channel.get(CONF_CURRENT):
|
||||||
|
sensor_name = conf.get(CONF_NAME)
|
||||||
|
if (
|
||||||
|
sensor_name
|
||||||
|
and channel_name
|
||||||
|
and not sensor_name.startswith(channel_name)
|
||||||
|
):
|
||||||
|
conf[CONF_NAME] = f"{channel_name} {sensor_name}"
|
||||||
|
|
||||||
|
|
||||||
|
FINAL_VALIDATE_SCHEMA = final_validate
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
@@ -29,7 +29,7 @@ from esphome.core import CORE, coroutine_with_priority
|
|||||||
DOMAIN = "api"
|
DOMAIN = "api"
|
||||||
DEPENDENCIES = ["network"]
|
DEPENDENCIES = ["network"]
|
||||||
AUTO_LOAD = ["socket"]
|
AUTO_LOAD = ["socket"]
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@OttoWinter"]
|
||||||
|
|
||||||
api_ns = cg.esphome_ns.namespace("api")
|
api_ns = cg.esphome_ns.namespace("api")
|
||||||
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
|
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
|
||||||
|
@@ -250,8 +250,8 @@ message DeviceInfoResponse {
|
|||||||
// Supports receiving and saving api encryption key
|
// Supports receiving and saving api encryption key
|
||||||
bool api_encryption_supported = 19 [(field_ifdef) = "USE_API_NOISE"];
|
bool api_encryption_supported = 19 [(field_ifdef) = "USE_API_NOISE"];
|
||||||
|
|
||||||
repeated DeviceInfo devices = 20 [(field_ifdef) = "USE_DEVICES", (fixed_array_size_define) = "ESPHOME_DEVICE_COUNT"];
|
repeated DeviceInfo devices = 20 [(field_ifdef) = "USE_DEVICES"];
|
||||||
repeated AreaInfo areas = 21 [(field_ifdef) = "USE_AREAS", (fixed_array_size_define) = "ESPHOME_AREA_COUNT"];
|
repeated AreaInfo areas = 21 [(field_ifdef) = "USE_AREAS"];
|
||||||
|
|
||||||
// Top-level area info to phase out suggested_area
|
// Top-level area info to phase out suggested_area
|
||||||
AreaInfo area = 22 [(field_ifdef) = "USE_AREAS"];
|
AreaInfo area = 22 [(field_ifdef) = "USE_AREAS"];
|
||||||
@@ -1438,11 +1438,11 @@ message BluetoothLERawAdvertisementsResponse {
|
|||||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
|
||||||
repeated BluetoothLERawAdvertisement advertisements = 1 [(fixed_array_with_length_define) = "BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE"];
|
repeated BluetoothLERawAdvertisement advertisements = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum BluetoothDeviceRequestType {
|
enum BluetoothDeviceRequestType {
|
||||||
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0 [deprecated = true]; // V1 removed, use V3 variants
|
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0;
|
||||||
BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1;
|
BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1;
|
||||||
BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2;
|
BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2;
|
||||||
BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3;
|
BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3;
|
||||||
|
@@ -289,26 +289,16 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess
|
|||||||
return 0; // Doesn't fit
|
return 0; // Doesn't fit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allocate buffer space - pass payload size, allocation functions add header/footer space
|
||||||
|
ProtoWriteBuffer buffer = is_single ? conn->allocate_single_message_buffer(calculated_size)
|
||||||
|
: conn->allocate_batch_message_buffer(calculated_size);
|
||||||
|
|
||||||
// Get buffer size after allocation (which includes header padding)
|
// Get buffer size after allocation (which includes header padding)
|
||||||
std::vector<uint8_t> &shared_buf = conn->parent_->get_shared_buffer_ref();
|
std::vector<uint8_t> &shared_buf = conn->parent_->get_shared_buffer_ref();
|
||||||
|
size_t size_before_encode = shared_buf.size();
|
||||||
if (is_single || conn->flags_.batch_first_message) {
|
|
||||||
// Single message or first batch message
|
|
||||||
conn->prepare_first_message_buffer(shared_buf, header_padding, total_calculated_size);
|
|
||||||
if (conn->flags_.batch_first_message) {
|
|
||||||
conn->flags_.batch_first_message = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Batch message second or later
|
|
||||||
// Add padding for previous message footer + this message header
|
|
||||||
size_t current_size = shared_buf.size();
|
|
||||||
shared_buf.reserve(current_size + total_calculated_size);
|
|
||||||
shared_buf.resize(current_size + footer_size + header_padding);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode directly into buffer
|
// Encode directly into buffer
|
||||||
size_t size_before_encode = shared_buf.size();
|
msg.encode(buffer);
|
||||||
msg.encode({&shared_buf});
|
|
||||||
|
|
||||||
// Calculate actual encoded size (not including header that was already added)
|
// Calculate actual encoded size (not including header that was already added)
|
||||||
size_t actual_payload_size = shared_buf.size() - size_before_encode;
|
size_t actual_payload_size = shared_buf.size() - size_before_encode;
|
||||||
@@ -1472,22 +1462,18 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
|||||||
resp.api_encryption_supported = true;
|
resp.api_encryption_supported = true;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_DEVICES
|
#ifdef USE_DEVICES
|
||||||
size_t device_index = 0;
|
|
||||||
for (auto const &device : App.get_devices()) {
|
for (auto const &device : App.get_devices()) {
|
||||||
if (device_index >= ESPHOME_DEVICE_COUNT)
|
resp.devices.emplace_back();
|
||||||
break;
|
auto &device_info = resp.devices.back();
|
||||||
auto &device_info = resp.devices[device_index++];
|
|
||||||
device_info.device_id = device->get_device_id();
|
device_info.device_id = device->get_device_id();
|
||||||
device_info.set_name(StringRef(device->get_name()));
|
device_info.set_name(StringRef(device->get_name()));
|
||||||
device_info.area_id = device->get_area_id();
|
device_info.area_id = device->get_area_id();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_AREAS
|
#ifdef USE_AREAS
|
||||||
size_t area_index = 0;
|
|
||||||
for (auto const &area : App.get_areas()) {
|
for (auto const &area : App.get_areas()) {
|
||||||
if (area_index >= ESPHOME_AREA_COUNT)
|
resp.areas.emplace_back();
|
||||||
break;
|
auto &area_info = resp.areas.back();
|
||||||
auto &area_info = resp.areas[area_index++];
|
|
||||||
area_info.area_id = area->get_area_id();
|
area_info.area_id = area->get_area_id();
|
||||||
area_info.set_name(StringRef(area->get_name()));
|
area_info.set_name(StringRef(area->get_name()));
|
||||||
}
|
}
|
||||||
@@ -1630,6 +1616,14 @@ bool APIConnection::schedule_batch_() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProtoWriteBuffer APIConnection::allocate_single_message_buffer(uint16_t size) { return this->create_buffer(size); }
|
||||||
|
|
||||||
|
ProtoWriteBuffer APIConnection::allocate_batch_message_buffer(uint16_t size) {
|
||||||
|
ProtoWriteBuffer result = this->prepare_message_buffer(size, this->flags_.batch_first_message);
|
||||||
|
this->flags_.batch_first_message = false;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void APIConnection::process_batch_() {
|
void APIConnection::process_batch_() {
|
||||||
// Ensure PacketInfo remains trivially destructible for our placement new approach
|
// Ensure PacketInfo remains trivially destructible for our placement new approach
|
||||||
static_assert(std::is_trivially_destructible<PacketInfo>::value,
|
static_assert(std::is_trivially_destructible<PacketInfo>::value,
|
||||||
@@ -1737,7 +1731,7 @@ void APIConnection::process_batch_() {
|
|||||||
}
|
}
|
||||||
remaining_size -= payload_size;
|
remaining_size -= payload_size;
|
||||||
// Calculate where the next message's header padding will start
|
// Calculate where the next message's header padding will start
|
||||||
// Current buffer size + footer space for this message
|
// Current buffer size + footer space (that prepare_message_buffer will add for this message)
|
||||||
current_offset = shared_buf.size() + footer_size;
|
current_offset = shared_buf.size() + footer_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -252,21 +252,44 @@ class APIConnection : public APIServerConnection {
|
|||||||
|
|
||||||
// Get header padding size - used for both reserve and insert
|
// Get header padding size - used for both reserve and insert
|
||||||
uint8_t header_padding = this->helper_->frame_header_padding();
|
uint8_t header_padding = this->helper_->frame_header_padding();
|
||||||
|
|
||||||
// Get shared buffer from parent server
|
// Get shared buffer from parent server
|
||||||
std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
|
std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
|
||||||
this->prepare_first_message_buffer(shared_buf, header_padding,
|
|
||||||
reserve_size + header_padding + this->helper_->frame_footer_size());
|
|
||||||
return {&shared_buf};
|
|
||||||
}
|
|
||||||
|
|
||||||
void prepare_first_message_buffer(std::vector<uint8_t> &shared_buf, size_t header_padding, size_t total_size) {
|
|
||||||
shared_buf.clear();
|
shared_buf.clear();
|
||||||
// Reserve space for header padding + message + footer
|
// Reserve space for header padding + message + footer
|
||||||
// - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext)
|
// - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext)
|
||||||
// - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext)
|
// - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext)
|
||||||
shared_buf.reserve(total_size);
|
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
|
// Resize to add header padding so message encoding starts at the correct position
|
||||||
shared_buf.resize(header_padding);
|
shared_buf.resize(header_padding);
|
||||||
|
return {&shared_buf};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare buffer for next message in batch
|
||||||
|
ProtoWriteBuffer prepare_message_buffer(uint16_t message_size, bool is_first_message) {
|
||||||
|
// Get reference to shared buffer (it maintains state between batch messages)
|
||||||
|
std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
|
||||||
|
|
||||||
|
if (is_first_message) {
|
||||||
|
shared_buf.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t current_size = shared_buf.size();
|
||||||
|
|
||||||
|
// Calculate padding to add:
|
||||||
|
// - First message: just header padding
|
||||||
|
// - Subsequent messages: footer for previous message + header padding for this message
|
||||||
|
size_t padding_to_add = is_first_message
|
||||||
|
? this->helper_->frame_header_padding()
|
||||||
|
: this->helper_->frame_header_padding() + this->helper_->frame_footer_size();
|
||||||
|
|
||||||
|
// Reserve space for padding + message
|
||||||
|
shared_buf.reserve(current_size + padding_to_add + message_size);
|
||||||
|
|
||||||
|
// Resize to add the padding bytes
|
||||||
|
shared_buf.resize(current_size + padding_to_add);
|
||||||
|
|
||||||
|
return {&shared_buf};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool try_to_clear_buffer(bool log_out_of_space);
|
bool try_to_clear_buffer(bool log_out_of_space);
|
||||||
@@ -274,6 +297,10 @@ class APIConnection : public APIServerConnection {
|
|||||||
|
|
||||||
std::string get_client_combined_info() const { return this->client_info_.get_combined_info(); }
|
std::string get_client_combined_info() const { return this->client_info_.get_combined_info(); }
|
||||||
|
|
||||||
|
// 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 handle authentication completion
|
// Helper function to handle authentication completion
|
||||||
void complete_authentication_();
|
void complete_authentication_();
|
||||||
@@ -676,16 +703,10 @@ class APIConnection : public APIServerConnection {
|
|||||||
bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint8_t message_type,
|
bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint8_t message_type,
|
||||||
uint8_t estimated_size) {
|
uint8_t estimated_size) {
|
||||||
// Try to send immediately if:
|
// Try to send immediately if:
|
||||||
// 1. It's an UpdateStateResponse (always send immediately to handle cases where
|
// 1. We should try to send immediately (should_try_send_immediately = true)
|
||||||
// the main loop is blocked, e.g., during OTA updates)
|
// 2. Batch delay is 0 (user has opted in to immediate sending)
|
||||||
// 2. OR: We should try to send immediately (should_try_send_immediately = true)
|
// 3. Buffer has space available
|
||||||
// AND Batch delay is 0 (user has opted in to immediate sending)
|
if (this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0 &&
|
||||||
// 3. AND: Buffer has space available
|
|
||||||
if ((
|
|
||||||
#ifdef USE_UPDATE
|
|
||||||
message_type == UpdateStateResponse::MESSAGE_TYPE ||
|
|
||||||
#endif
|
|
||||||
(this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0)) &&
|
|
||||||
this->helper_->can_write_without_blocking()) {
|
this->helper_->can_write_without_blocking()) {
|
||||||
// Now actually encode and send
|
// Now actually encode and send
|
||||||
if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true) &&
|
if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true) &&
|
||||||
|
@@ -156,9 +156,7 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to send directly if no buffered data
|
// Try to send directly if no buffered data
|
||||||
// Optimize for single iovec case (common for plaintext API)
|
ssize_t sent = this->socket_->writev(iov, iovcnt);
|
||||||
ssize_t sent =
|
|
||||||
(iovcnt == 1) ? this->socket_->write(iov[0].iov_base, iov[0].iov_len) : this->socket_->writev(iov, iovcnt);
|
|
||||||
|
|
||||||
if (sent == -1) {
|
if (sent == -1) {
|
||||||
APIError err = this->handle_socket_write_error_();
|
APIError err = this->handle_socket_write_error_();
|
||||||
|
@@ -30,7 +30,6 @@ extend google.protobuf.FieldOptions {
|
|||||||
optional bool no_zero_copy = 50008 [default=false];
|
optional bool no_zero_copy = 50008 [default=false];
|
||||||
optional bool fixed_array_skip_zero = 50009 [default=false];
|
optional bool fixed_array_skip_zero = 50009 [default=false];
|
||||||
optional string fixed_array_size_define = 50010;
|
optional string fixed_array_size_define = 50010;
|
||||||
optional string fixed_array_with_length_define = 50011;
|
|
||||||
|
|
||||||
// container_pointer: Zero-copy optimization for repeated fields.
|
// container_pointer: Zero-copy optimization for repeated fields.
|
||||||
//
|
//
|
||||||
|
@@ -115,12 +115,12 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_bool(19, this->api_encryption_supported);
|
buffer.encode_bool(19, this->api_encryption_supported);
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_DEVICES
|
#ifdef USE_DEVICES
|
||||||
for (const auto &it : this->devices) {
|
for (auto &it : this->devices) {
|
||||||
buffer.encode_message(20, it, true);
|
buffer.encode_message(20, it, true);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_AREAS
|
#ifdef USE_AREAS
|
||||||
for (const auto &it : this->areas) {
|
for (auto &it : this->areas) {
|
||||||
buffer.encode_message(21, it, true);
|
buffer.encode_message(21, it, true);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -167,14 +167,10 @@ void DeviceInfoResponse::calculate_size(ProtoSize &size) const {
|
|||||||
size.add_bool(2, this->api_encryption_supported);
|
size.add_bool(2, this->api_encryption_supported);
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_DEVICES
|
#ifdef USE_DEVICES
|
||||||
for (const auto &it : this->devices) {
|
size.add_repeated_message(2, this->devices);
|
||||||
size.add_message_object_force(2, it);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_AREAS
|
#ifdef USE_AREAS
|
||||||
for (const auto &it : this->areas) {
|
size.add_repeated_message(2, this->areas);
|
||||||
size.add_message_object_force(2, it);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_AREAS
|
#ifdef USE_AREAS
|
||||||
size.add_message_object(2, this->area);
|
size.add_message_object(2, this->area);
|
||||||
@@ -1843,14 +1839,12 @@ void BluetoothLERawAdvertisement::calculate_size(ProtoSize &size) const {
|
|||||||
size.add_length(1, this->data_len);
|
size.add_length(1, this->data_len);
|
||||||
}
|
}
|
||||||
void BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer buffer) const {
|
void BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
for (uint16_t i = 0; i < this->advertisements_len; i++) {
|
for (auto &it : this->advertisements) {
|
||||||
buffer.encode_message(1, this->advertisements[i], true);
|
buffer.encode_message(1, it, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void BluetoothLERawAdvertisementsResponse::calculate_size(ProtoSize &size) const {
|
void BluetoothLERawAdvertisementsResponse::calculate_size(ProtoSize &size) const {
|
||||||
for (uint16_t i = 0; i < this->advertisements_len; i++) {
|
size.add_repeated_message(1, this->advertisements);
|
||||||
size.add_message_object_force(1, this->advertisements[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
switch (field_id) {
|
switch (field_id) {
|
||||||
|
@@ -490,7 +490,7 @@ class DeviceInfo : public ProtoMessage {
|
|||||||
class DeviceInfoResponse : public ProtoMessage {
|
class DeviceInfoResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 10;
|
static constexpr uint8_t MESSAGE_TYPE = 10;
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 247;
|
static constexpr uint8_t ESTIMATED_SIZE = 211;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "device_info_response"; }
|
const char *message_name() const override { return "device_info_response"; }
|
||||||
#endif
|
#endif
|
||||||
@@ -543,10 +543,10 @@ class DeviceInfoResponse : public ProtoMessage {
|
|||||||
bool api_encryption_supported{false};
|
bool api_encryption_supported{false};
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_DEVICES
|
#ifdef USE_DEVICES
|
||||||
std::array<DeviceInfo, ESPHOME_DEVICE_COUNT> devices{};
|
std::vector<DeviceInfo> devices{};
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_AREAS
|
#ifdef USE_AREAS
|
||||||
std::array<AreaInfo, ESPHOME_AREA_COUNT> areas{};
|
std::vector<AreaInfo> areas{};
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_AREAS
|
#ifdef USE_AREAS
|
||||||
AreaInfo area{};
|
AreaInfo area{};
|
||||||
@@ -1788,12 +1788,11 @@ class BluetoothLERawAdvertisement : public ProtoMessage {
|
|||||||
class BluetoothLERawAdvertisementsResponse : public ProtoMessage {
|
class BluetoothLERawAdvertisementsResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 93;
|
static constexpr uint8_t MESSAGE_TYPE = 93;
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 136;
|
static constexpr uint8_t ESTIMATED_SIZE = 34;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "bluetooth_le_raw_advertisements_response"; }
|
const char *message_name() const override { return "bluetooth_le_raw_advertisements_response"; }
|
||||||
#endif
|
#endif
|
||||||
std::array<BluetoothLERawAdvertisement, BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE> advertisements{};
|
std::vector<BluetoothLERawAdvertisement> advertisements{};
|
||||||
uint16_t advertisements_len{0};
|
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(ProtoSize &size) const override;
|
void calculate_size(ProtoSize &size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
@@ -1534,9 +1534,9 @@ void BluetoothLERawAdvertisement::dump_to(std::string &out) const {
|
|||||||
}
|
}
|
||||||
void BluetoothLERawAdvertisementsResponse::dump_to(std::string &out) const {
|
void BluetoothLERawAdvertisementsResponse::dump_to(std::string &out) const {
|
||||||
MessageDumpHelper helper(out, "BluetoothLERawAdvertisementsResponse");
|
MessageDumpHelper helper(out, "BluetoothLERawAdvertisementsResponse");
|
||||||
for (uint16_t i = 0; i < this->advertisements_len; i++) {
|
for (const auto &it : this->advertisements) {
|
||||||
out.append(" advertisements: ");
|
out.append(" advertisements: ");
|
||||||
this->advertisements[i].dump_to(out);
|
it.dump_to(out);
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -30,7 +30,7 @@ if TYPE_CHECKING:
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None:
|
async def async_run_logs(config: dict[str, Any], address: str) -> None:
|
||||||
"""Run the logs command in the event loop."""
|
"""Run the logs command in the event loop."""
|
||||||
conf = config["api"]
|
conf = config["api"]
|
||||||
name = config["esphome"]["name"]
|
name = config["esphome"]["name"]
|
||||||
@@ -39,21 +39,13 @@ async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None:
|
|||||||
noise_psk: str | None = None
|
noise_psk: str | None = None
|
||||||
if (encryption := conf.get(CONF_ENCRYPTION)) and (key := encryption.get(CONF_KEY)):
|
if (encryption := conf.get(CONF_ENCRYPTION)) and (key := encryption.get(CONF_KEY)):
|
||||||
noise_psk = key
|
noise_psk = key
|
||||||
|
_LOGGER.info("Starting log output from %s using esphome API", address)
|
||||||
if len(addresses) == 1:
|
|
||||||
_LOGGER.info("Starting log output from %s using esphome API", addresses[0])
|
|
||||||
else:
|
|
||||||
_LOGGER.info(
|
|
||||||
"Starting log output from %s using esphome API", " or ".join(addresses)
|
|
||||||
)
|
|
||||||
|
|
||||||
cli = APIClient(
|
cli = APIClient(
|
||||||
addresses[0], # Primary address for compatibility
|
address,
|
||||||
port,
|
port,
|
||||||
password,
|
password,
|
||||||
client_info=f"ESPHome Logs {__version__}",
|
client_info=f"ESPHome Logs {__version__}",
|
||||||
noise_psk=noise_psk,
|
noise_psk=noise_psk,
|
||||||
addresses=addresses, # Pass all addresses for automatic retry
|
|
||||||
)
|
)
|
||||||
dashboard = CORE.dashboard
|
dashboard = CORE.dashboard
|
||||||
|
|
||||||
@@ -74,7 +66,7 @@ async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None:
|
|||||||
await stop()
|
await stop()
|
||||||
|
|
||||||
|
|
||||||
def run_logs(config: dict[str, Any], addresses: list[str]) -> None:
|
def run_logs(config: dict[str, Any], address: str) -> None:
|
||||||
"""Run the logs command."""
|
"""Run the logs command."""
|
||||||
with contextlib.suppress(KeyboardInterrupt):
|
with contextlib.suppress(KeyboardInterrupt):
|
||||||
asyncio.run(async_run_logs(config, addresses))
|
asyncio.run(async_run_logs(config, address))
|
||||||
|
@@ -56,14 +56,6 @@ class CustomAPIDevice {
|
|||||||
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT
|
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT
|
||||||
global_api_server->register_user_service(service);
|
global_api_server->register_user_service(service);
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
template<typename T, typename... Ts>
|
|
||||||
void register_service(void (T::*callback)(Ts...), const std::string &name,
|
|
||||||
const std::array<std::string, sizeof...(Ts)> &arg_names) {
|
|
||||||
static_assert(
|
|
||||||
sizeof(T) == 0,
|
|
||||||
"register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration");
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/** Register a custom native API service that will show up in Home Assistant.
|
/** Register a custom native API service that will show up in Home Assistant.
|
||||||
@@ -89,12 +81,6 @@ class CustomAPIDevice {
|
|||||||
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT
|
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT
|
||||||
global_api_server->register_user_service(service);
|
global_api_server->register_user_service(service);
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
|
|
||||||
static_assert(
|
|
||||||
sizeof(T) == 0,
|
|
||||||
"register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration");
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
@@ -149,22 +135,6 @@ class CustomAPIDevice {
|
|||||||
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
|
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
|
||||||
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), f);
|
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), f);
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
template<typename T>
|
|
||||||
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id,
|
|
||||||
const std::string &attribute = "") {
|
|
||||||
static_assert(sizeof(T) == 0,
|
|
||||||
"subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section "
|
|
||||||
"of your YAML configuration");
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id,
|
|
||||||
const std::string &attribute = "") {
|
|
||||||
static_assert(sizeof(T) == 0,
|
|
||||||
"subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section "
|
|
||||||
"of your YAML configuration");
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
@@ -252,28 +222,6 @@ class CustomAPIDevice {
|
|||||||
}
|
}
|
||||||
global_api_server->send_homeassistant_service_call(resp);
|
global_api_server->send_homeassistant_service_call(resp);
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
template<typename T = void> void call_homeassistant_service(const std::string &service_name) {
|
|
||||||
static_assert(sizeof(T) == 0, "call_homeassistant_service() requires 'homeassistant_services: true' in the 'api:' "
|
|
||||||
"section of your YAML configuration");
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T = void>
|
|
||||||
void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
|
||||||
static_assert(sizeof(T) == 0, "call_homeassistant_service() requires 'homeassistant_services: true' in the 'api:' "
|
|
||||||
"section of your YAML configuration");
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T = void> void fire_homeassistant_event(const std::string &event_name) {
|
|
||||||
static_assert(sizeof(T) == 0, "fire_homeassistant_event() requires 'homeassistant_services: true' in the 'api:' "
|
|
||||||
"section of your YAML configuration");
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T = void>
|
|
||||||
void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
|
||||||
static_assert(sizeof(T) == 0, "fire_homeassistant_event() requires 'homeassistant_services: true' in the 'api:' "
|
|
||||||
"section of your YAML configuration");
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -15,23 +15,6 @@
|
|||||||
|
|
||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
|
|
||||||
// Helper functions for ZigZag encoding/decoding
|
|
||||||
inline constexpr uint32_t encode_zigzag32(int32_t value) {
|
|
||||||
return (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
|
|
||||||
}
|
|
||||||
|
|
||||||
inline constexpr uint64_t encode_zigzag64(int64_t value) {
|
|
||||||
return (static_cast<uint64_t>(value) << 1) ^ (static_cast<uint64_t>(value >> 63));
|
|
||||||
}
|
|
||||||
|
|
||||||
inline constexpr int32_t decode_zigzag32(uint32_t value) {
|
|
||||||
return (value & 1) ? static_cast<int32_t>(~(value >> 1)) : static_cast<int32_t>(value >> 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline constexpr int64_t decode_zigzag64(uint64_t value) {
|
|
||||||
return (value & 1) ? static_cast<int64_t>(~(value >> 1)) : static_cast<int64_t>(value >> 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* StringRef Ownership Model for API Protocol Messages
|
* StringRef Ownership Model for API Protocol Messages
|
||||||
* ===================================================
|
* ===================================================
|
||||||
@@ -104,25 +87,33 @@ class ProtoVarInt {
|
|||||||
return {}; // Incomplete or invalid varint
|
return {}; // Incomplete or invalid varint
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr uint16_t as_uint16() const { return this->value_; }
|
uint16_t as_uint16() const { return this->value_; }
|
||||||
constexpr uint32_t as_uint32() const { return this->value_; }
|
uint32_t as_uint32() const { return this->value_; }
|
||||||
constexpr uint64_t as_uint64() const { return this->value_; }
|
uint64_t as_uint64() const { return this->value_; }
|
||||||
constexpr bool as_bool() const { return this->value_; }
|
bool as_bool() const { return this->value_; }
|
||||||
constexpr int32_t as_int32() const {
|
int32_t as_int32() const {
|
||||||
// Not ZigZag encoded
|
// Not ZigZag encoded
|
||||||
return static_cast<int32_t>(this->as_int64());
|
return static_cast<int32_t>(this->as_int64());
|
||||||
}
|
}
|
||||||
constexpr int64_t as_int64() const {
|
int64_t as_int64() const {
|
||||||
// Not ZigZag encoded
|
// Not ZigZag encoded
|
||||||
return static_cast<int64_t>(this->value_);
|
return static_cast<int64_t>(this->value_);
|
||||||
}
|
}
|
||||||
constexpr int32_t as_sint32() const {
|
int32_t as_sint32() const {
|
||||||
// with ZigZag encoding
|
// with ZigZag encoding
|
||||||
return decode_zigzag32(static_cast<uint32_t>(this->value_));
|
if (this->value_ & 1) {
|
||||||
|
return static_cast<int32_t>(~(this->value_ >> 1));
|
||||||
|
} else {
|
||||||
|
return static_cast<int32_t>(this->value_ >> 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
constexpr int64_t as_sint64() const {
|
int64_t as_sint64() const {
|
||||||
// with ZigZag encoding
|
// with ZigZag encoding
|
||||||
return decode_zigzag64(this->value_);
|
if (this->value_ & 1) {
|
||||||
|
return static_cast<int64_t>(~(this->value_ >> 1));
|
||||||
|
} else {
|
||||||
|
return static_cast<int64_t>(this->value_ >> 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Encode the varint value to a pre-allocated buffer without bounds checking.
|
* Encode the varint value to a pre-allocated buffer without bounds checking.
|
||||||
@@ -318,10 +309,22 @@ class ProtoWriteBuffer {
|
|||||||
this->encode_uint64(field_id, static_cast<uint64_t>(value), force);
|
this->encode_uint64(field_id, static_cast<uint64_t>(value), force);
|
||||||
}
|
}
|
||||||
void encode_sint32(uint32_t field_id, int32_t value, bool force = false) {
|
void encode_sint32(uint32_t field_id, int32_t value, bool force = false) {
|
||||||
this->encode_uint32(field_id, encode_zigzag32(value), force);
|
uint32_t uvalue;
|
||||||
|
if (value < 0) {
|
||||||
|
uvalue = ~(value << 1);
|
||||||
|
} else {
|
||||||
|
uvalue = value << 1;
|
||||||
|
}
|
||||||
|
this->encode_uint32(field_id, uvalue, force);
|
||||||
}
|
}
|
||||||
void encode_sint64(uint32_t field_id, int64_t value, bool force = false) {
|
void encode_sint64(uint32_t field_id, int64_t value, bool force = false) {
|
||||||
this->encode_uint64(field_id, encode_zigzag64(value), force);
|
uint64_t uvalue;
|
||||||
|
if (value < 0) {
|
||||||
|
uvalue = ~(value << 1);
|
||||||
|
} else {
|
||||||
|
uvalue = value << 1;
|
||||||
|
}
|
||||||
|
this->encode_uint64(field_id, uvalue, force);
|
||||||
}
|
}
|
||||||
void encode_message(uint32_t field_id, const ProtoMessage &value, bool force = false);
|
void encode_message(uint32_t field_id, const ProtoMessage &value, bool force = false);
|
||||||
std::vector<uint8_t> *get_buffer() const { return buffer_; }
|
std::vector<uint8_t> *get_buffer() const { return buffer_; }
|
||||||
@@ -392,7 +395,7 @@ class ProtoSize {
|
|||||||
* @param value The uint32_t value to calculate size for
|
* @param value The uint32_t value to calculate size for
|
||||||
* @return The number of bytes needed to encode the value
|
* @return The number of bytes needed to encode the value
|
||||||
*/
|
*/
|
||||||
static constexpr uint32_t varint(uint32_t value) {
|
static inline uint32_t varint(uint32_t value) {
|
||||||
// Optimized varint size calculation using leading zeros
|
// Optimized varint size calculation using leading zeros
|
||||||
// Each 7 bits requires one byte in the varint encoding
|
// Each 7 bits requires one byte in the varint encoding
|
||||||
if (value < 128)
|
if (value < 128)
|
||||||
@@ -416,7 +419,7 @@ class ProtoSize {
|
|||||||
* @param value The uint64_t value to calculate size for
|
* @param value The uint64_t value to calculate size for
|
||||||
* @return The number of bytes needed to encode the value
|
* @return The number of bytes needed to encode the value
|
||||||
*/
|
*/
|
||||||
static constexpr uint32_t varint(uint64_t value) {
|
static inline uint32_t varint(uint64_t value) {
|
||||||
// Handle common case of values fitting in uint32_t (vast majority of use cases)
|
// Handle common case of values fitting in uint32_t (vast majority of use cases)
|
||||||
if (value <= UINT32_MAX) {
|
if (value <= UINT32_MAX) {
|
||||||
return varint(static_cast<uint32_t>(value));
|
return varint(static_cast<uint32_t>(value));
|
||||||
@@ -447,7 +450,7 @@ class ProtoSize {
|
|||||||
* @param value The int32_t value to calculate size for
|
* @param value The int32_t value to calculate size for
|
||||||
* @return The number of bytes needed to encode the value
|
* @return The number of bytes needed to encode the value
|
||||||
*/
|
*/
|
||||||
static constexpr uint32_t varint(int32_t value) {
|
static inline uint32_t varint(int32_t value) {
|
||||||
// Negative values are sign-extended to 64 bits in protocol buffers,
|
// Negative values are sign-extended to 64 bits in protocol buffers,
|
||||||
// which always results in a 10-byte varint for negative int32
|
// which always results in a 10-byte varint for negative int32
|
||||||
if (value < 0) {
|
if (value < 0) {
|
||||||
@@ -463,7 +466,7 @@ class ProtoSize {
|
|||||||
* @param value The int64_t value to calculate size for
|
* @param value The int64_t value to calculate size for
|
||||||
* @return The number of bytes needed to encode the value
|
* @return The number of bytes needed to encode the value
|
||||||
*/
|
*/
|
||||||
static constexpr uint32_t varint(int64_t value) {
|
static inline uint32_t varint(int64_t value) {
|
||||||
// For int64_t, we convert to uint64_t and calculate the size
|
// For int64_t, we convert to uint64_t and calculate the size
|
||||||
// This works because the bit pattern determines the encoding size,
|
// This works because the bit pattern determines the encoding size,
|
||||||
// and we've handled negative int32 values as a special case above
|
// and we've handled negative int32 values as a special case above
|
||||||
@@ -477,7 +480,7 @@ class ProtoSize {
|
|||||||
* @param type The wire type value (from the WireType enum in the protobuf spec)
|
* @param type The wire type value (from the WireType enum in the protobuf spec)
|
||||||
* @return The number of bytes needed to encode the field ID and wire type
|
* @return The number of bytes needed to encode the field ID and wire type
|
||||||
*/
|
*/
|
||||||
static constexpr uint32_t field(uint32_t field_id, uint32_t type) {
|
static inline uint32_t field(uint32_t field_id, uint32_t type) {
|
||||||
uint32_t tag = (field_id << 3) | (type & 0b111);
|
uint32_t tag = (field_id << 3) | (type & 0b111);
|
||||||
return varint(tag);
|
return varint(tag);
|
||||||
}
|
}
|
||||||
@@ -604,8 +607,9 @@ class ProtoSize {
|
|||||||
*/
|
*/
|
||||||
inline void add_sint32_force(uint32_t field_id_size, int32_t value) {
|
inline void add_sint32_force(uint32_t field_id_size, int32_t value) {
|
||||||
// Always calculate size when force is true
|
// Always calculate size when force is true
|
||||||
// ZigZag encoding for sint32
|
// ZigZag encoding for sint32: (n << 1) ^ (n >> 31)
|
||||||
total_size_ += field_id_size + varint(encode_zigzag32(value));
|
uint32_t zigzag = (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
|
||||||
|
total_size_ += field_id_size + varint(zigzag);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -7,7 +7,6 @@ from esphome.const import (
|
|||||||
CONF_DIRECTION,
|
CONF_DIRECTION,
|
||||||
CONF_HYSTERESIS,
|
CONF_HYSTERESIS,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_POWER_MODE,
|
|
||||||
CONF_RANGE,
|
CONF_RANGE,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -58,6 +57,7 @@ FAST_FILTER = {
|
|||||||
CONF_RAW_ANGLE = "raw_angle"
|
CONF_RAW_ANGLE = "raw_angle"
|
||||||
CONF_RAW_POSITION = "raw_position"
|
CONF_RAW_POSITION = "raw_position"
|
||||||
CONF_WATCHDOG = "watchdog"
|
CONF_WATCHDOG = "watchdog"
|
||||||
|
CONF_POWER_MODE = "power_mode"
|
||||||
CONF_SLOW_FILTER = "slow_filter"
|
CONF_SLOW_FILTER = "slow_filter"
|
||||||
CONF_FAST_FILTER = "fast_filter"
|
CONF_FAST_FILTER = "fast_filter"
|
||||||
CONF_START_POSITION = "start_position"
|
CONF_START_POSITION = "start_position"
|
||||||
|
@@ -24,6 +24,7 @@ AS5600Sensor = as5600_ns.class_("AS5600Sensor", sensor.Sensor, cg.PollingCompone
|
|||||||
CONF_RAW_ANGLE = "raw_angle"
|
CONF_RAW_ANGLE = "raw_angle"
|
||||||
CONF_RAW_POSITION = "raw_position"
|
CONF_RAW_POSITION = "raw_position"
|
||||||
CONF_WATCHDOG = "watchdog"
|
CONF_WATCHDOG = "watchdog"
|
||||||
|
CONF_POWER_MODE = "power_mode"
|
||||||
CONF_SLOW_FILTER = "slow_filter"
|
CONF_SLOW_FILTER = "slow_filter"
|
||||||
CONF_FAST_FILTER = "fast_filter"
|
CONF_FAST_FILTER = "fast_filter"
|
||||||
CONF_PWM_FREQUENCY = "pwm_frequency"
|
CONF_PWM_FREQUENCY = "pwm_frequency"
|
||||||
|
@@ -10,7 +10,7 @@ from esphome.const import (
|
|||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@OttoWinter"]
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.Schema({}),
|
cv.Schema({}),
|
||||||
|
@@ -110,8 +110,6 @@ void ATM90E32Component::update() {
|
|||||||
|
|
||||||
void ATM90E32Component::setup() {
|
void ATM90E32Component::setup() {
|
||||||
this->spi_setup();
|
this->spi_setup();
|
||||||
this->cs_summary_ = this->cs_->dump_summary();
|
|
||||||
const char *cs = this->cs_summary_.c_str();
|
|
||||||
|
|
||||||
uint16_t mmode0 = 0x87; // 3P4W 50Hz
|
uint16_t mmode0 = 0x87; // 3P4W 50Hz
|
||||||
uint16_t high_thresh = 0;
|
uint16_t high_thresh = 0;
|
||||||
@@ -132,9 +130,9 @@ void ATM90E32Component::setup() {
|
|||||||
mmode0 |= 0 << 1; // sets 1st bit to 0, phase b is not counted into the all-phase sum energy/power (P/Q/S)
|
mmode0 |= 0 << 1; // sets 1st bit to 0, phase b is not counted into the all-phase sum energy/power (P/Q/S)
|
||||||
}
|
}
|
||||||
|
|
||||||
this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A, false); // Perform soft reset
|
this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A); // Perform soft reset
|
||||||
delay(6); // Wait for the minimum 5ms + 1ms
|
delay(6); // Wait for the minimum 5ms + 1ms
|
||||||
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA); // enable register config access
|
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA); // enable register config access
|
||||||
if (!this->validate_spi_read_(0x55AA, "setup()")) {
|
if (!this->validate_spi_read_(0x55AA, "setup()")) {
|
||||||
ESP_LOGW(TAG, "Could not initialize ATM90E32 IC, check SPI settings");
|
ESP_LOGW(TAG, "Could not initialize ATM90E32 IC, check SPI settings");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
@@ -158,17 +156,16 @@ void ATM90E32Component::setup() {
|
|||||||
|
|
||||||
if (this->enable_offset_calibration_) {
|
if (this->enable_offset_calibration_) {
|
||||||
// Initialize flash storage for offset calibrations
|
// Initialize flash storage for offset calibrations
|
||||||
uint32_t o_hash = fnv1_hash(std::string("_offset_calibration_") + this->cs_summary_);
|
uint32_t o_hash = fnv1_hash(std::string("_offset_calibration_") + this->cs_->dump_summary());
|
||||||
this->offset_pref_ = global_preferences->make_preference<OffsetCalibration[3]>(o_hash, true);
|
this->offset_pref_ = global_preferences->make_preference<OffsetCalibration[3]>(o_hash, true);
|
||||||
this->restore_offset_calibrations_();
|
this->restore_offset_calibrations_();
|
||||||
|
|
||||||
// Initialize flash storage for power offset calibrations
|
// Initialize flash storage for power offset calibrations
|
||||||
uint32_t po_hash = fnv1_hash(std::string("_power_offset_calibration_") + this->cs_summary_);
|
uint32_t po_hash = fnv1_hash(std::string("_power_offset_calibration_") + this->cs_->dump_summary());
|
||||||
this->power_offset_pref_ = global_preferences->make_preference<PowerOffsetCalibration[3]>(po_hash, true);
|
this->power_offset_pref_ = global_preferences->make_preference<PowerOffsetCalibration[3]>(po_hash, true);
|
||||||
this->restore_power_offset_calibrations_();
|
this->restore_power_offset_calibrations_();
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Power & Voltage/Current offset calibration is disabled. Using config file values.",
|
ESP_LOGI(TAG, "[CALIBRATION] Power & Voltage/Current offset calibration is disabled. Using config file values.");
|
||||||
cs);
|
|
||||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||||
this->write16_(this->voltage_offset_registers[phase],
|
this->write16_(this->voltage_offset_registers[phase],
|
||||||
static_cast<uint16_t>(this->offset_phase_[phase].voltage_offset_));
|
static_cast<uint16_t>(this->offset_phase_[phase].voltage_offset_));
|
||||||
@@ -183,18 +180,21 @@ void ATM90E32Component::setup() {
|
|||||||
|
|
||||||
if (this->enable_gain_calibration_) {
|
if (this->enable_gain_calibration_) {
|
||||||
// Initialize flash storage for gain calibration
|
// Initialize flash storage for gain calibration
|
||||||
uint32_t g_hash = fnv1_hash(std::string("_gain_calibration_") + this->cs_summary_);
|
uint32_t g_hash = fnv1_hash(std::string("_gain_calibration_") + this->cs_->dump_summary());
|
||||||
this->gain_calibration_pref_ = global_preferences->make_preference<GainCalibration[3]>(g_hash, true);
|
this->gain_calibration_pref_ = global_preferences->make_preference<GainCalibration[3]>(g_hash, true);
|
||||||
this->restore_gain_calibrations_();
|
this->restore_gain_calibrations_();
|
||||||
|
|
||||||
if (!this->using_saved_calibrations_) {
|
if (this->using_saved_calibrations_) {
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION] Successfully restored gain calibration from memory.");
|
||||||
|
} else {
|
||||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||||
this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
|
this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
|
||||||
this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
|
this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration is disabled. Using config file values.", cs);
|
ESP_LOGI(TAG, "[CALIBRATION] Gain calibration is disabled. Using config file values.");
|
||||||
|
|
||||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||||
this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
|
this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
|
||||||
this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
|
this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
|
||||||
@@ -213,122 +213,6 @@ void ATM90E32Component::setup() {
|
|||||||
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000); // end configuration
|
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000); // end configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::log_calibration_status_() {
|
|
||||||
const char *cs = this->cs_summary_.c_str();
|
|
||||||
|
|
||||||
bool offset_mismatch = false;
|
|
||||||
bool power_mismatch = false;
|
|
||||||
bool gain_mismatch = false;
|
|
||||||
|
|
||||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
|
||||||
offset_mismatch |= this->offset_calibration_mismatch_[phase];
|
|
||||||
power_mismatch |= this->power_offset_calibration_mismatch_[phase];
|
|
||||||
gain_mismatch |= this->gain_calibration_mismatch_[phase];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (offset_mismatch) {
|
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ", cs);
|
|
||||||
ESP_LOGW(TAG,
|
|
||||||
"[CALIBRATION][%s] ===================== Offset mismatch: using flash values =====================", cs);
|
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
|
||||||
cs);
|
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
|
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | | config | flash | config | flash |", cs);
|
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
|
||||||
cs);
|
|
||||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | %c | %6d | %6d | %6d | %6d |", cs, 'A' + phase,
|
|
||||||
this->config_offset_phase_[phase].voltage_offset_, this->offset_phase_[phase].voltage_offset_,
|
|
||||||
this->config_offset_phase_[phase].current_offset_, this->offset_phase_[phase].current_offset_);
|
|
||||||
}
|
|
||||||
ESP_LOGW(TAG,
|
|
||||||
"[CALIBRATION][%s] ===============================================================================", cs);
|
|
||||||
}
|
|
||||||
if (power_mismatch) {
|
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ", cs);
|
|
||||||
ESP_LOGW(TAG,
|
|
||||||
"[CALIBRATION][%s] ================= Power offset mismatch: using flash values =================", cs);
|
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
|
||||||
cs);
|
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | Phase | offset_active_power|offset_reactive_power|", cs);
|
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | | config | flash | config | flash |", cs);
|
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
|
||||||
cs);
|
|
||||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | %c | %6d | %6d | %6d | %6d |", cs, 'A' + phase,
|
|
||||||
this->config_power_offset_phase_[phase].active_power_offset,
|
|
||||||
this->power_offset_phase_[phase].active_power_offset,
|
|
||||||
this->config_power_offset_phase_[phase].reactive_power_offset,
|
|
||||||
this->power_offset_phase_[phase].reactive_power_offset);
|
|
||||||
}
|
|
||||||
ESP_LOGW(TAG,
|
|
||||||
"[CALIBRATION][%s] ===============================================================================", cs);
|
|
||||||
}
|
|
||||||
if (gain_mismatch) {
|
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ", cs);
|
|
||||||
ESP_LOGW(TAG,
|
|
||||||
"[CALIBRATION][%s] ====================== Gain mismatch: using flash values =====================", cs);
|
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
|
||||||
cs);
|
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
|
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | | config | flash | config | flash |", cs);
|
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
|
||||||
cs);
|
|
||||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | %c | %6u | %6u | %6u | %6u |", cs, 'A' + phase,
|
|
||||||
this->config_gain_phase_[phase].voltage_gain, this->gain_phase_[phase].voltage_gain,
|
|
||||||
this->config_gain_phase_[phase].current_gain, this->gain_phase_[phase].current_gain);
|
|
||||||
}
|
|
||||||
ESP_LOGW(TAG,
|
|
||||||
"[CALIBRATION][%s] ===============================================================================", cs);
|
|
||||||
}
|
|
||||||
if (!this->enable_offset_calibration_) {
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Power & Voltage/Current offset calibration is disabled. Using config file values.",
|
|
||||||
cs);
|
|
||||||
} else if (this->restored_offset_calibration_ && !offset_mismatch) {
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ============== Restored offset calibration from memory ==============", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
|
||||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
|
|
||||||
this->offset_phase_[phase].voltage_offset_, this->offset_phase_[phase].current_offset_);
|
|
||||||
}
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ==============================================================\\n", cs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->restored_power_offset_calibration_ && !power_mismatch) {
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ============ Restored power offset calibration from memory ============", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
|
||||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
|
|
||||||
this->power_offset_phase_[phase].active_power_offset,
|
|
||||||
this->power_offset_phase_[phase].reactive_power_offset);
|
|
||||||
}
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
|
|
||||||
}
|
|
||||||
if (!this->enable_gain_calibration_) {
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration is disabled. Using config file values.", cs);
|
|
||||||
} else if (this->restored_gain_calibration_ && !gain_mismatch) {
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ============ Restoring saved gain calibrations to registers ============", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
|
||||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6u | %6u |", cs, 'A' + phase,
|
|
||||||
this->gain_phase_[phase].voltage_gain, this->gain_phase_[phase].current_gain);
|
|
||||||
}
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\\n", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration loaded and verified successfully.\n", cs);
|
|
||||||
}
|
|
||||||
this->calibration_message_printed_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ATM90E32Component::dump_config() {
|
void ATM90E32Component::dump_config() {
|
||||||
ESP_LOGCONFIG("", "ATM90E32:");
|
ESP_LOGCONFIG("", "ATM90E32:");
|
||||||
LOG_PIN(" CS Pin: ", this->cs_);
|
LOG_PIN(" CS Pin: ", this->cs_);
|
||||||
@@ -371,10 +255,6 @@ void ATM90E32Component::dump_config() {
|
|||||||
LOG_SENSOR(" ", "Peak Current C", this->phase_[PHASEC].peak_current_sensor_);
|
LOG_SENSOR(" ", "Peak Current C", this->phase_[PHASEC].peak_current_sensor_);
|
||||||
LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
|
LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
|
||||||
LOG_SENSOR(" ", "Chip Temp", this->chip_temperature_sensor_);
|
LOG_SENSOR(" ", "Chip Temp", this->chip_temperature_sensor_);
|
||||||
if (this->restored_offset_calibration_ || this->restored_power_offset_calibration_ ||
|
|
||||||
this->restored_gain_calibration_ || !this->enable_offset_calibration_ || !this->enable_gain_calibration_) {
|
|
||||||
this->log_calibration_status_();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float ATM90E32Component::get_setup_priority() const { return setup_priority::IO; }
|
float ATM90E32Component::get_setup_priority() const { return setup_priority::IO; }
|
||||||
@@ -382,35 +262,26 @@ float ATM90E32Component::get_setup_priority() const { return setup_priority::IO;
|
|||||||
// R/C registers can conly be cleared after the LastSPIData register is updated (register 78H)
|
// R/C registers can conly be cleared after the LastSPIData register is updated (register 78H)
|
||||||
// Peakdetect period: 05H. Bit 15:8 are PeakDet_period in ms. 7:0 are Sag_period
|
// Peakdetect period: 05H. Bit 15:8 are PeakDet_period in ms. 7:0 are Sag_period
|
||||||
// Default is 143FH (20ms, 63ms)
|
// Default is 143FH (20ms, 63ms)
|
||||||
uint16_t ATM90E32Component::read16_transaction_(uint16_t a_register) {
|
uint16_t ATM90E32Component::read16_(uint16_t a_register) {
|
||||||
uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03);
|
uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03);
|
||||||
uint8_t addrl = (a_register & 0xFF);
|
uint8_t addrl = (a_register & 0xFF);
|
||||||
uint8_t data[4] = {addrh, addrl, 0x00, 0x00};
|
uint8_t data[2];
|
||||||
this->transfer_array(data, 4);
|
uint16_t output;
|
||||||
uint16_t output = encode_uint16(data[2], data[3]);
|
this->enable();
|
||||||
|
delay_microseconds_safe(1); // min delay between CS low and first SCK is 200ns - 1ms is plenty
|
||||||
|
this->write_byte(addrh);
|
||||||
|
this->write_byte(addrl);
|
||||||
|
this->read_array(data, 2);
|
||||||
|
this->disable();
|
||||||
|
|
||||||
|
output = (uint16_t(data[0] & 0xFF) << 8) | (data[1] & 0xFF);
|
||||||
ESP_LOGVV(TAG, "read16_ 0x%04" PRIX16 " output 0x%04" PRIX16, a_register, output);
|
ESP_LOGVV(TAG, "read16_ 0x%04" PRIX16 " output 0x%04" PRIX16, a_register, output);
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t ATM90E32Component::read16_(uint16_t a_register) {
|
|
||||||
this->enable();
|
|
||||||
delay_microseconds_safe(1); // min delay between CS low and first SCK is 200ns - 1us is plenty
|
|
||||||
uint16_t output = this->read16_transaction_(a_register);
|
|
||||||
delay_microseconds_safe(1); // allow the last clock to propagate before releasing CS
|
|
||||||
this->disable();
|
|
||||||
delay_microseconds_safe(1); // meet minimum CS high time before next transaction
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
|
int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
|
||||||
this->enable();
|
const uint16_t val_h = this->read16_(addr_h);
|
||||||
delay_microseconds_safe(1);
|
const uint16_t val_l = this->read16_(addr_l);
|
||||||
const uint16_t val_h = this->read16_transaction_(addr_h);
|
|
||||||
delay_microseconds_safe(1);
|
|
||||||
const uint16_t val_l = this->read16_transaction_(addr_l);
|
|
||||||
delay_microseconds_safe(1);
|
|
||||||
this->disable();
|
|
||||||
delay_microseconds_safe(1);
|
|
||||||
const int32_t val = (val_h << 16) | val_l;
|
const int32_t val = (val_h << 16) | val_l;
|
||||||
|
|
||||||
ESP_LOGVV(TAG,
|
ESP_LOGVV(TAG,
|
||||||
@@ -421,19 +292,13 @@ int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
|
|||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::write16_(uint16_t a_register, uint16_t val, bool validate) {
|
void ATM90E32Component::write16_(uint16_t a_register, uint16_t val) {
|
||||||
ESP_LOGVV(TAG, "write16_ 0x%04" PRIX16 " val 0x%04" PRIX16, a_register, val);
|
ESP_LOGVV(TAG, "write16_ 0x%04" PRIX16 " val 0x%04" PRIX16, a_register, val);
|
||||||
uint8_t addrh = ((a_register >> 8) & 0x03);
|
|
||||||
uint8_t addrl = (a_register & 0xFF);
|
|
||||||
uint8_t data[4] = {addrh, addrl, uint8_t((val >> 8) & 0xFF), uint8_t(val & 0xFF)};
|
|
||||||
this->enable();
|
this->enable();
|
||||||
delay_microseconds_safe(1); // ensure CS setup time
|
this->write_byte16(a_register);
|
||||||
this->write_array(data, 4);
|
this->write_byte16(val);
|
||||||
delay_microseconds_safe(1); // allow clock to settle before raising CS
|
|
||||||
this->disable();
|
this->disable();
|
||||||
delay_microseconds_safe(1); // ensure minimum CS high time
|
this->validate_spi_read_(val, "write16()");
|
||||||
if (validate)
|
|
||||||
this->validate_spi_read_(val, "write16()");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float ATM90E32Component::get_local_phase_voltage_(uint8_t phase) { return this->phase_[phase].voltage_; }
|
float ATM90E32Component::get_local_phase_voltage_(uint8_t phase) { return this->phase_[phase].voltage_; }
|
||||||
@@ -576,10 +441,8 @@ float ATM90E32Component::get_chip_temperature_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::run_gain_calibrations() {
|
void ATM90E32Component::run_gain_calibrations() {
|
||||||
const char *cs = this->cs_summary_.c_str();
|
|
||||||
if (!this->enable_gain_calibration_) {
|
if (!this->enable_gain_calibration_) {
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Gain calibration is disabled! Enable it first with enable_gain_calibration: true",
|
ESP_LOGW(TAG, "[CALIBRATION] Gain calibration is disabled! Enable it first with enable_gain_calibration: true");
|
||||||
cs);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,14 +454,12 @@ void ATM90E32Component::run_gain_calibrations() {
|
|||||||
float ref_currents[3] = {this->get_reference_current(0), this->get_reference_current(1),
|
float ref_currents[3] = {this->get_reference_current(0), this->get_reference_current(1),
|
||||||
this->get_reference_current(2)};
|
this->get_reference_current(2)};
|
||||||
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
ESP_LOGI(TAG, "[CALIBRATION] ");
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ========================= Gain Calibration =========================", cs);
|
ESP_LOGI(TAG, "[CALIBRATION] ========================= Gain Calibration =========================");
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
ESP_LOGI(TAG, "[CALIBRATION] ---------------------------------------------------------------------");
|
||||||
ESP_LOGI(
|
ESP_LOGI(TAG,
|
||||||
TAG,
|
"[CALIBRATION] | Phase | V_meas (V) | I_meas (A) | V_ref | I_ref | V_gain (old→new) | I_gain (old→new) |");
|
||||||
"[CALIBRATION][%s] | Phase | V_meas (V) | I_meas (A) | V_ref | I_ref | V_gain (old→new) | I_gain (old→new) |",
|
ESP_LOGI(TAG, "[CALIBRATION] ---------------------------------------------------------------------");
|
||||||
cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
|
||||||
|
|
||||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||||
float measured_voltage = this->get_phase_voltage_avg_(phase);
|
float measured_voltage = this->get_phase_voltage_avg_(phase);
|
||||||
@@ -615,22 +476,22 @@ void ATM90E32Component::run_gain_calibrations() {
|
|||||||
|
|
||||||
// Voltage calibration
|
// Voltage calibration
|
||||||
if (ref_voltage <= 0.0f) {
|
if (ref_voltage <= 0.0f) {
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping voltage calibration: reference voltage is 0.", cs,
|
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping voltage calibration: reference voltage is 0.",
|
||||||
phase_labels[phase]);
|
phase_labels[phase]);
|
||||||
} else if (measured_voltage == 0.0f) {
|
} else if (measured_voltage == 0.0f) {
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping voltage calibration: measured voltage is 0.", cs,
|
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping voltage calibration: measured voltage is 0.",
|
||||||
phase_labels[phase]);
|
phase_labels[phase]);
|
||||||
} else {
|
} else {
|
||||||
uint32_t new_voltage_gain = static_cast<uint16_t>((ref_voltage / measured_voltage) * current_voltage_gain);
|
uint32_t new_voltage_gain = static_cast<uint16_t>((ref_voltage / measured_voltage) * current_voltage_gain);
|
||||||
if (new_voltage_gain == 0) {
|
if (new_voltage_gain == 0) {
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Voltage gain would be 0. Check reference and measured voltage.", cs,
|
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Voltage gain would be 0. Check reference and measured voltage.",
|
||||||
phase_labels[phase]);
|
phase_labels[phase]);
|
||||||
} else {
|
} else {
|
||||||
if (new_voltage_gain >= 65535) {
|
if (new_voltage_gain >= 65535) {
|
||||||
ESP_LOGW(TAG,
|
ESP_LOGW(
|
||||||
"[CALIBRATION][%s] Phase %s - Voltage gain exceeds 65535. You may need a higher output voltage "
|
TAG,
|
||||||
"transformer.",
|
"[CALIBRATION] Phase %s - Voltage gain exceeds 65535. You may need a higher output voltage transformer.",
|
||||||
cs, phase_labels[phase]);
|
phase_labels[phase]);
|
||||||
new_voltage_gain = 65535;
|
new_voltage_gain = 65535;
|
||||||
}
|
}
|
||||||
this->gain_phase_[phase].voltage_gain = static_cast<uint16_t>(new_voltage_gain);
|
this->gain_phase_[phase].voltage_gain = static_cast<uint16_t>(new_voltage_gain);
|
||||||
@@ -640,20 +501,20 @@ void ATM90E32Component::run_gain_calibrations() {
|
|||||||
|
|
||||||
// Current calibration
|
// Current calibration
|
||||||
if (ref_current == 0.0f) {
|
if (ref_current == 0.0f) {
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping current calibration: reference current is 0.", cs,
|
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping current calibration: reference current is 0.",
|
||||||
phase_labels[phase]);
|
phase_labels[phase]);
|
||||||
} else if (measured_current == 0.0f) {
|
} else if (measured_current == 0.0f) {
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping current calibration: measured current is 0.", cs,
|
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping current calibration: measured current is 0.",
|
||||||
phase_labels[phase]);
|
phase_labels[phase]);
|
||||||
} else {
|
} else {
|
||||||
uint32_t new_current_gain = static_cast<uint16_t>((ref_current / measured_current) * current_current_gain);
|
uint32_t new_current_gain = static_cast<uint16_t>((ref_current / measured_current) * current_current_gain);
|
||||||
if (new_current_gain == 0) {
|
if (new_current_gain == 0) {
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Current gain would be 0. Check reference and measured current.", cs,
|
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Current gain would be 0. Check reference and measured current.",
|
||||||
phase_labels[phase]);
|
phase_labels[phase]);
|
||||||
} else {
|
} else {
|
||||||
if (new_current_gain >= 65535) {
|
if (new_current_gain >= 65535) {
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Current gain exceeds 65535. You may need to turn up pga gain.",
|
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Current gain exceeds 65535. You may need to turn up pga gain.",
|
||||||
cs, phase_labels[phase]);
|
phase_labels[phase]);
|
||||||
new_current_gain = 65535;
|
new_current_gain = 65535;
|
||||||
}
|
}
|
||||||
this->gain_phase_[phase].current_gain = static_cast<uint16_t>(new_current_gain);
|
this->gain_phase_[phase].current_gain = static_cast<uint16_t>(new_current_gain);
|
||||||
@@ -662,13 +523,13 @@ void ATM90E32Component::run_gain_calibrations() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Final row output
|
// Final row output
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %9.2f | %9.4f | %5.2f | %6.4f | %5u → %-5u | %5u → %-5u |", cs,
|
ESP_LOGI(TAG, "[CALIBRATION] | %c | %9.2f | %9.4f | %5.2f | %6.4f | %5u → %-5u | %5u → %-5u |",
|
||||||
'A' + phase, measured_voltage, measured_current, ref_voltage, ref_current, current_voltage_gain,
|
'A' + phase, measured_voltage, measured_current, ref_voltage, ref_current, current_voltage_gain,
|
||||||
did_voltage ? this->gain_phase_[phase].voltage_gain : current_voltage_gain, current_current_gain,
|
did_voltage ? this->gain_phase_[phase].voltage_gain : current_voltage_gain, current_current_gain,
|
||||||
did_current ? this->gain_phase_[phase].current_gain : current_current_gain);
|
did_current ? this->gain_phase_[phase].current_gain : current_current_gain);
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
|
ESP_LOGI(TAG, "[CALIBRATION] =====================================================================\n");
|
||||||
|
|
||||||
this->save_gain_calibration_to_memory_();
|
this->save_gain_calibration_to_memory_();
|
||||||
this->write_gains_to_registers_();
|
this->write_gains_to_registers_();
|
||||||
@@ -676,108 +537,54 @@ void ATM90E32Component::run_gain_calibrations() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::save_gain_calibration_to_memory_() {
|
void ATM90E32Component::save_gain_calibration_to_memory_() {
|
||||||
const char *cs = this->cs_summary_.c_str();
|
|
||||||
bool success = this->gain_calibration_pref_.save(&this->gain_phase_);
|
bool success = this->gain_calibration_pref_.save(&this->gain_phase_);
|
||||||
global_preferences->sync();
|
|
||||||
if (success) {
|
if (success) {
|
||||||
this->using_saved_calibrations_ = true;
|
this->using_saved_calibrations_ = true;
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration saved to memory.", cs);
|
ESP_LOGI(TAG, "[CALIBRATION] Gain calibration saved to memory.");
|
||||||
} else {
|
} else {
|
||||||
this->using_saved_calibrations_ = false;
|
this->using_saved_calibrations_ = false;
|
||||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to save gain calibration to memory!", cs);
|
ESP_LOGE(TAG, "[CALIBRATION] Failed to save gain calibration to memory!");
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ATM90E32Component::save_offset_calibration_to_memory_() {
|
|
||||||
const char *cs = this->cs_summary_.c_str();
|
|
||||||
bool success = this->offset_pref_.save(&this->offset_phase_);
|
|
||||||
global_preferences->sync();
|
|
||||||
if (success) {
|
|
||||||
this->using_saved_calibrations_ = true;
|
|
||||||
this->restored_offset_calibration_ = true;
|
|
||||||
for (bool &phase : this->offset_calibration_mismatch_)
|
|
||||||
phase = false;
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Offset calibration saved to memory.", cs);
|
|
||||||
} else {
|
|
||||||
this->using_saved_calibrations_ = false;
|
|
||||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to save offset calibration to memory!", cs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ATM90E32Component::save_power_offset_calibration_to_memory_() {
|
|
||||||
const char *cs = this->cs_summary_.c_str();
|
|
||||||
bool success = this->power_offset_pref_.save(&this->power_offset_phase_);
|
|
||||||
global_preferences->sync();
|
|
||||||
if (success) {
|
|
||||||
this->using_saved_calibrations_ = true;
|
|
||||||
this->restored_power_offset_calibration_ = true;
|
|
||||||
for (bool &phase : this->power_offset_calibration_mismatch_)
|
|
||||||
phase = false;
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Power offset calibration saved to memory.", cs);
|
|
||||||
} else {
|
|
||||||
this->using_saved_calibrations_ = false;
|
|
||||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to save power offset calibration to memory!", cs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::run_offset_calibrations() {
|
void ATM90E32Component::run_offset_calibrations() {
|
||||||
const char *cs = this->cs_summary_.c_str();
|
|
||||||
if (!this->enable_offset_calibration_) {
|
if (!this->enable_offset_calibration_) {
|
||||||
ESP_LOGW(TAG,
|
ESP_LOGW(TAG, "[CALIBRATION] Offset calibration is disabled! Enable it first with enable_offset_calibration: true");
|
||||||
"[CALIBRATION][%s] Offset calibration is disabled! Enable it first with enable_offset_calibration: true",
|
|
||||||
cs);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ======================== Offset Calibration ========================", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------", cs);
|
|
||||||
|
|
||||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||||
int16_t voltage_offset = calibrate_offset(phase, true);
|
int16_t voltage_offset = calibrate_offset(phase, true);
|
||||||
int16_t current_offset = calibrate_offset(phase, false);
|
int16_t current_offset = calibrate_offset(phase, false);
|
||||||
|
|
||||||
this->write_offsets_to_registers_(phase, voltage_offset, current_offset);
|
this->write_offsets_to_registers_(phase, voltage_offset, current_offset);
|
||||||
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, voltage_offset,
|
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_voltage: %d, offset_current: %d", 'A' + phase, voltage_offset,
|
||||||
current_offset);
|
current_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ==================================================================\n", cs);
|
this->offset_pref_.save(&this->offset_phase_); // Save to flash
|
||||||
|
|
||||||
this->save_offset_calibration_to_memory_();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::run_power_offset_calibrations() {
|
void ATM90E32Component::run_power_offset_calibrations() {
|
||||||
const char *cs = this->cs_summary_.c_str();
|
|
||||||
if (!this->enable_offset_calibration_) {
|
if (!this->enable_offset_calibration_) {
|
||||||
ESP_LOGW(
|
ESP_LOGW(
|
||||||
TAG,
|
TAG,
|
||||||
"[CALIBRATION][%s] Offset power calibration is disabled! Enable it first with enable_offset_calibration: true",
|
"[CALIBRATION] Offset power calibration is disabled! Enable it first with enable_offset_calibration: true");
|
||||||
cs);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ===================== Power Offset Calibration =====================", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
|
||||||
|
|
||||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||||
int16_t active_offset = calibrate_power_offset(phase, false);
|
int16_t active_offset = calibrate_power_offset(phase, false);
|
||||||
int16_t reactive_offset = calibrate_power_offset(phase, true);
|
int16_t reactive_offset = calibrate_power_offset(phase, true);
|
||||||
|
|
||||||
this->write_power_offsets_to_registers_(phase, active_offset, reactive_offset);
|
this->write_power_offsets_to_registers_(phase, active_offset, reactive_offset);
|
||||||
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, active_offset,
|
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_active_power: %d, offset_reactive_power: %d", 'A' + phase,
|
||||||
reactive_offset);
|
active_offset, reactive_offset);
|
||||||
}
|
}
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
|
|
||||||
|
|
||||||
this->save_power_offset_calibration_to_memory_();
|
this->power_offset_pref_.save(&this->power_offset_phase_); // Save to flash
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::write_gains_to_registers_() {
|
void ATM90E32Component::write_gains_to_registers_() {
|
||||||
@@ -824,276 +631,102 @@ void ATM90E32Component::write_power_offsets_to_registers_(uint8_t phase, int16_t
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::restore_gain_calibrations_() {
|
void ATM90E32Component::restore_gain_calibrations_() {
|
||||||
const char *cs = this->cs_summary_.c_str();
|
|
||||||
for (uint8_t i = 0; i < 3; ++i) {
|
|
||||||
this->config_gain_phase_[i].voltage_gain = this->phase_[i].voltage_gain_;
|
|
||||||
this->config_gain_phase_[i].current_gain = this->phase_[i].ct_gain_;
|
|
||||||
this->gain_phase_[i] = this->config_gain_phase_[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->gain_calibration_pref_.load(&this->gain_phase_)) {
|
if (this->gain_calibration_pref_.load(&this->gain_phase_)) {
|
||||||
bool all_zero = true;
|
ESP_LOGI(TAG, "[CALIBRATION] Restoring saved gain calibrations to registers:");
|
||||||
bool same_as_config = true;
|
|
||||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||||
const auto &cfg = this->config_gain_phase_[phase];
|
uint16_t v_gain = this->gain_phase_[phase].voltage_gain;
|
||||||
const auto &saved = this->gain_phase_[phase];
|
uint16_t i_gain = this->gain_phase_[phase].current_gain;
|
||||||
if (saved.voltage_gain != 0 || saved.current_gain != 0)
|
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - Voltage Gain: %u, Current Gain: %u", 'A' + phase, v_gain, i_gain);
|
||||||
all_zero = false;
|
|
||||||
if (saved.voltage_gain != cfg.voltage_gain || saved.current_gain != cfg.current_gain)
|
|
||||||
same_as_config = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!all_zero && !same_as_config) {
|
this->write_gains_to_registers_();
|
||||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
|
||||||
bool mismatch = false;
|
|
||||||
if (this->has_config_voltage_gain_[phase] &&
|
|
||||||
this->gain_phase_[phase].voltage_gain != this->config_gain_phase_[phase].voltage_gain)
|
|
||||||
mismatch = true;
|
|
||||||
if (this->has_config_current_gain_[phase] &&
|
|
||||||
this->gain_phase_[phase].current_gain != this->config_gain_phase_[phase].current_gain)
|
|
||||||
mismatch = true;
|
|
||||||
if (mismatch)
|
|
||||||
this->gain_calibration_mismatch_[phase] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->write_gains_to_registers_();
|
|
||||||
|
|
||||||
if (this->verify_gain_writes_()) {
|
|
||||||
this->using_saved_calibrations_ = true;
|
|
||||||
this->restored_gain_calibration_ = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (this->verify_gain_writes_()) {
|
||||||
|
this->using_saved_calibrations_ = true;
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION] Gain calibration loaded and verified successfully.");
|
||||||
|
} else {
|
||||||
this->using_saved_calibrations_ = false;
|
this->using_saved_calibrations_ = false;
|
||||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Gain verification failed! Calibration may not be applied correctly.", cs);
|
ESP_LOGE(TAG, "[CALIBRATION] Gain verification failed! Calibration may not be applied correctly.");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this->using_saved_calibrations_ = false;
|
||||||
|
ESP_LOGW(TAG, "[CALIBRATION] No stored gain calibrations found. Using config file values.");
|
||||||
}
|
}
|
||||||
|
|
||||||
this->using_saved_calibrations_ = false;
|
|
||||||
for (uint8_t i = 0; i < 3; ++i)
|
|
||||||
this->gain_phase_[i] = this->config_gain_phase_[i];
|
|
||||||
this->write_gains_to_registers_();
|
|
||||||
|
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] No stored gain calibrations found. Using config file values.", cs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::restore_offset_calibrations_() {
|
void ATM90E32Component::restore_offset_calibrations_() {
|
||||||
const char *cs = this->cs_summary_.c_str();
|
if (this->offset_pref_.load(&this->offset_phase_)) {
|
||||||
for (uint8_t i = 0; i < 3; ++i)
|
ESP_LOGI(TAG, "[CALIBRATION] Successfully restored offset calibration from memory.");
|
||||||
this->config_offset_phase_[i] = this->offset_phase_[i];
|
|
||||||
|
|
||||||
bool have_data = this->offset_pref_.load(&this->offset_phase_);
|
|
||||||
bool all_zero = true;
|
|
||||||
if (have_data) {
|
|
||||||
for (auto &phase : this->offset_phase_) {
|
|
||||||
if (phase.voltage_offset_ != 0 || phase.current_offset_ != 0) {
|
|
||||||
all_zero = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (have_data && !all_zero) {
|
|
||||||
this->restored_offset_calibration_ = true;
|
|
||||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||||
auto &offset = this->offset_phase_[phase];
|
auto &offset = this->offset_phase_[phase];
|
||||||
bool mismatch = false;
|
write_offsets_to_registers_(phase, offset.voltage_offset_, offset.current_offset_);
|
||||||
if (this->has_config_voltage_offset_[phase] &&
|
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_voltage:: %d, offset_current: %d", 'A' + phase,
|
||||||
offset.voltage_offset_ != this->config_offset_phase_[phase].voltage_offset_)
|
offset.voltage_offset_, offset.current_offset_);
|
||||||
mismatch = true;
|
|
||||||
if (this->has_config_current_offset_[phase] &&
|
|
||||||
offset.current_offset_ != this->config_offset_phase_[phase].current_offset_)
|
|
||||||
mismatch = true;
|
|
||||||
if (mismatch)
|
|
||||||
this->offset_calibration_mismatch_[phase] = true;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (uint8_t phase = 0; phase < 3; phase++)
|
ESP_LOGW(TAG, "[CALIBRATION] No stored offset calibrations found. Using default values.");
|
||||||
this->offset_phase_[phase] = this->config_offset_phase_[phase];
|
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] No stored offset calibrations found. Using default values.", cs);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
|
||||||
write_offsets_to_registers_(phase, this->offset_phase_[phase].voltage_offset_,
|
|
||||||
this->offset_phase_[phase].current_offset_);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::restore_power_offset_calibrations_() {
|
void ATM90E32Component::restore_power_offset_calibrations_() {
|
||||||
const char *cs = this->cs_summary_.c_str();
|
if (this->power_offset_pref_.load(&this->power_offset_phase_)) {
|
||||||
for (uint8_t i = 0; i < 3; ++i)
|
ESP_LOGI(TAG, "[CALIBRATION] Successfully restored power offset calibration from memory.");
|
||||||
this->config_power_offset_phase_[i] = this->power_offset_phase_[i];
|
|
||||||
|
|
||||||
bool have_data = this->power_offset_pref_.load(&this->power_offset_phase_);
|
|
||||||
bool all_zero = true;
|
|
||||||
if (have_data) {
|
|
||||||
for (auto &phase : this->power_offset_phase_) {
|
|
||||||
if (phase.active_power_offset != 0 || phase.reactive_power_offset != 0) {
|
|
||||||
all_zero = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (have_data && !all_zero) {
|
|
||||||
this->restored_power_offset_calibration_ = true;
|
|
||||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||||
auto &offset = this->power_offset_phase_[phase];
|
auto &offset = this->power_offset_phase_[phase];
|
||||||
bool mismatch = false;
|
write_power_offsets_to_registers_(phase, offset.active_power_offset, offset.reactive_power_offset);
|
||||||
if (this->has_config_active_power_offset_[phase] &&
|
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_active_power: %d, offset_reactive_power: %d", 'A' + phase,
|
||||||
offset.active_power_offset != this->config_power_offset_phase_[phase].active_power_offset)
|
offset.active_power_offset, offset.reactive_power_offset);
|
||||||
mismatch = true;
|
|
||||||
if (this->has_config_reactive_power_offset_[phase] &&
|
|
||||||
offset.reactive_power_offset != this->config_power_offset_phase_[phase].reactive_power_offset)
|
|
||||||
mismatch = true;
|
|
||||||
if (mismatch)
|
|
||||||
this->power_offset_calibration_mismatch_[phase] = true;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (uint8_t phase = 0; phase < 3; ++phase)
|
ESP_LOGW(TAG, "[CALIBRATION] No stored power offsets found. Using default values.");
|
||||||
this->power_offset_phase_[phase] = this->config_power_offset_phase_[phase];
|
|
||||||
ESP_LOGW(TAG, "[CALIBRATION][%s] No stored power offsets found. Using default values.", cs);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
|
||||||
write_power_offsets_to_registers_(phase, this->power_offset_phase_[phase].active_power_offset,
|
|
||||||
this->power_offset_phase_[phase].reactive_power_offset);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::clear_gain_calibrations() {
|
void ATM90E32Component::clear_gain_calibrations() {
|
||||||
const char *cs = this->cs_summary_.c_str();
|
ESP_LOGI(TAG, "[CALIBRATION] Clearing stored gain calibrations and restoring config-defined values");
|
||||||
if (!this->using_saved_calibrations_) {
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored gain calibrations to clear. Current values:", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
|
|
||||||
for (int phase = 0; phase < 3; phase++) {
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6u | %6u |", cs, 'A' + phase,
|
|
||||||
this->gain_phase_[phase].voltage_gain, this->gain_phase_[phase].current_gain);
|
|
||||||
}
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ==========================================================\n", cs);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Clearing stored gain calibrations and restoring config-defined values", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
|
|
||||||
|
|
||||||
for (int phase = 0; phase < 3; phase++) {
|
for (int phase = 0; phase < 3; phase++) {
|
||||||
uint16_t voltage_gain = this->phase_[phase].voltage_gain_;
|
gain_phase_[phase].voltage_gain = this->phase_[phase].voltage_gain_;
|
||||||
uint16_t current_gain = this->phase_[phase].ct_gain_;
|
gain_phase_[phase].current_gain = this->phase_[phase].ct_gain_;
|
||||||
|
|
||||||
this->config_gain_phase_[phase].voltage_gain = voltage_gain;
|
|
||||||
this->config_gain_phase_[phase].current_gain = current_gain;
|
|
||||||
this->gain_phase_[phase].voltage_gain = voltage_gain;
|
|
||||||
this->gain_phase_[phase].current_gain = current_gain;
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6u | %6u |", cs, 'A' + phase, voltage_gain, current_gain);
|
|
||||||
}
|
}
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ==========================================================\n", cs);
|
|
||||||
|
|
||||||
GainCalibration zero_gains[3]{{0, 0}, {0, 0}, {0, 0}};
|
|
||||||
bool success = this->gain_calibration_pref_.save(&zero_gains);
|
|
||||||
global_preferences->sync();
|
|
||||||
|
|
||||||
|
bool success = this->gain_calibration_pref_.save(&this->gain_phase_);
|
||||||
this->using_saved_calibrations_ = false;
|
this->using_saved_calibrations_ = false;
|
||||||
this->restored_gain_calibration_ = false;
|
|
||||||
for (bool &phase : this->gain_calibration_mismatch_)
|
|
||||||
phase = false;
|
|
||||||
|
|
||||||
if (!success) {
|
if (success) {
|
||||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to clear gain calibrations!", cs);
|
ESP_LOGI(TAG, "[CALIBRATION] Gain calibrations cleared. Config values restored:");
|
||||||
|
for (int phase = 0; phase < 3; phase++) {
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - Voltage Gain: %u, Current Gain: %u", 'A' + phase,
|
||||||
|
gain_phase_[phase].voltage_gain, gain_phase_[phase].current_gain);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "[CALIBRATION] Failed to clear gain calibrations!");
|
||||||
}
|
}
|
||||||
|
|
||||||
this->write_gains_to_registers_(); // Apply them to the chip immediately
|
this->write_gains_to_registers_(); // Apply them to the chip immediately
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::clear_offset_calibrations() {
|
void ATM90E32Component::clear_offset_calibrations() {
|
||||||
const char *cs = this->cs_summary_.c_str();
|
|
||||||
if (!this->restored_offset_calibration_) {
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored offset calibrations to clear. Current values:", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
|
||||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
|
|
||||||
this->offset_phase_[phase].voltage_offset_, this->offset_phase_[phase].current_offset_);
|
|
||||||
}
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ==============================================================\n", cs);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Clearing stored offset calibrations and restoring config-defined values", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
|
||||||
|
|
||||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||||
int16_t voltage_offset =
|
this->write_offsets_to_registers_(phase, 0, 0);
|
||||||
this->has_config_voltage_offset_[phase] ? this->config_offset_phase_[phase].voltage_offset_ : 0;
|
|
||||||
int16_t current_offset =
|
|
||||||
this->has_config_current_offset_[phase] ? this->config_offset_phase_[phase].current_offset_ : 0;
|
|
||||||
this->write_offsets_to_registers_(phase, voltage_offset, current_offset);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, voltage_offset,
|
|
||||||
current_offset);
|
|
||||||
}
|
}
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ==============================================================\n", cs);
|
|
||||||
|
|
||||||
OffsetCalibration zero_offsets[3]{{0, 0}, {0, 0}, {0, 0}};
|
this->offset_pref_.save(&this->offset_phase_); // Save cleared values to flash memory
|
||||||
this->offset_pref_.save(&zero_offsets); // Clear stored values in flash
|
|
||||||
global_preferences->sync();
|
|
||||||
|
|
||||||
this->restored_offset_calibration_ = false;
|
ESP_LOGI(TAG, "[CALIBRATION] Offsets cleared.");
|
||||||
for (bool &phase : this->offset_calibration_mismatch_)
|
|
||||||
phase = false;
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Offsets cleared.", cs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::clear_power_offset_calibrations() {
|
void ATM90E32Component::clear_power_offset_calibrations() {
|
||||||
const char *cs = this->cs_summary_.c_str();
|
|
||||||
if (!this->restored_power_offset_calibration_) {
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored power offsets to clear. Current values:", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
|
||||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
|
|
||||||
this->power_offset_phase_[phase].active_power_offset,
|
|
||||||
this->power_offset_phase_[phase].reactive_power_offset);
|
|
||||||
}
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Clearing stored power offsets and restoring config-defined values", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
|
||||||
|
|
||||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||||
int16_t active_offset =
|
this->write_power_offsets_to_registers_(phase, 0, 0);
|
||||||
this->has_config_active_power_offset_[phase] ? this->config_power_offset_phase_[phase].active_power_offset : 0;
|
|
||||||
int16_t reactive_offset = this->has_config_reactive_power_offset_[phase]
|
|
||||||
? this->config_power_offset_phase_[phase].reactive_power_offset
|
|
||||||
: 0;
|
|
||||||
this->write_power_offsets_to_registers_(phase, active_offset, reactive_offset);
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, active_offset,
|
|
||||||
reactive_offset);
|
|
||||||
}
|
}
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
|
|
||||||
|
|
||||||
PowerOffsetCalibration zero_power_offsets[3]{{0, 0}, {0, 0}, {0, 0}};
|
this->power_offset_pref_.save(&this->power_offset_phase_);
|
||||||
this->power_offset_pref_.save(&zero_power_offsets);
|
|
||||||
global_preferences->sync();
|
|
||||||
|
|
||||||
this->restored_power_offset_calibration_ = false;
|
ESP_LOGI(TAG, "[CALIBRATION] Power offsets cleared.");
|
||||||
for (bool &phase : this->power_offset_calibration_mismatch_)
|
|
||||||
phase = false;
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Power offsets cleared.", cs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int16_t ATM90E32Component::calibrate_offset(uint8_t phase, bool voltage) {
|
int16_t ATM90E32Component::calibrate_offset(uint8_t phase, bool voltage) {
|
||||||
@@ -1114,21 +747,20 @@ int16_t ATM90E32Component::calibrate_offset(uint8_t phase, bool voltage) {
|
|||||||
|
|
||||||
int16_t ATM90E32Component::calibrate_power_offset(uint8_t phase, bool reactive) {
|
int16_t ATM90E32Component::calibrate_power_offset(uint8_t phase, bool reactive) {
|
||||||
const uint8_t num_reads = 5;
|
const uint8_t num_reads = 5;
|
||||||
int64_t total_value = 0;
|
uint64_t total_value = 0;
|
||||||
|
|
||||||
for (uint8_t i = 0; i < num_reads; ++i) {
|
for (uint8_t i = 0; i < num_reads; ++i) {
|
||||||
int32_t reading = reactive ? this->read32_(ATM90E32_REGISTER_QMEAN + phase, ATM90E32_REGISTER_QMEANLSB + phase)
|
uint32_t reading = reactive ? this->read32_(ATM90E32_REGISTER_QMEAN + phase, ATM90E32_REGISTER_QMEANLSB + phase)
|
||||||
: this->read32_(ATM90E32_REGISTER_PMEAN + phase, ATM90E32_REGISTER_PMEANLSB + phase);
|
: this->read32_(ATM90E32_REGISTER_PMEAN + phase, ATM90E32_REGISTER_PMEANLSB + phase);
|
||||||
total_value += reading;
|
total_value += reading;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t average_value = total_value / num_reads;
|
const uint32_t average_value = total_value / num_reads;
|
||||||
int32_t power_offset = -average_value;
|
const uint32_t power_offset = ~average_value + 1;
|
||||||
return static_cast<int16_t>(power_offset); // Takes the lower 16 bits
|
return static_cast<int16_t>(power_offset); // Takes the lower 16 bits
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ATM90E32Component::verify_gain_writes_() {
|
bool ATM90E32Component::verify_gain_writes_() {
|
||||||
const char *cs = this->cs_summary_.c_str();
|
|
||||||
bool success = true;
|
bool success = true;
|
||||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||||
uint16_t read_voltage = this->read16_(voltage_gain_registers[phase]);
|
uint16_t read_voltage = this->read16_(voltage_gain_registers[phase]);
|
||||||
@@ -1136,7 +768,7 @@ bool ATM90E32Component::verify_gain_writes_() {
|
|||||||
|
|
||||||
if (read_voltage != this->gain_phase_[phase].voltage_gain ||
|
if (read_voltage != this->gain_phase_[phase].voltage_gain ||
|
||||||
read_current != this->gain_phase_[phase].current_gain) {
|
read_current != this->gain_phase_[phase].current_gain) {
|
||||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Mismatch detected for Phase %s!", cs, phase_labels[phase]);
|
ESP_LOGE(TAG, "[CALIBRATION] Mismatch detected for Phase %s!", phase_labels[phase]);
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1159,16 +791,16 @@ void ATM90E32Component::check_phase_status() {
|
|||||||
status += "Phase Loss; ";
|
status += "Phase Loss; ";
|
||||||
|
|
||||||
auto *sensor = this->phase_status_text_sensor_[phase];
|
auto *sensor = this->phase_status_text_sensor_[phase];
|
||||||
if (sensor == nullptr)
|
const char *phase_name = sensor ? sensor->get_name().c_str() : "Unknown Phase";
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!status.empty()) {
|
if (!status.empty()) {
|
||||||
status.pop_back(); // remove space
|
status.pop_back(); // remove space
|
||||||
status.pop_back(); // remove semicolon
|
status.pop_back(); // remove semicolon
|
||||||
ESP_LOGW(TAG, "%s: %s", sensor->get_name().c_str(), status.c_str());
|
ESP_LOGW(TAG, "%s: %s", phase_name, status.c_str());
|
||||||
sensor->publish_state(status);
|
if (sensor != nullptr)
|
||||||
|
sensor->publish_state(status);
|
||||||
} else {
|
} else {
|
||||||
sensor->publish_state("Okay");
|
if (sensor != nullptr)
|
||||||
|
sensor->publish_state("Okay");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1185,12 +817,9 @@ void ATM90E32Component::check_freq_status() {
|
|||||||
} else {
|
} else {
|
||||||
freq_status = "Normal";
|
freq_status = "Normal";
|
||||||
}
|
}
|
||||||
|
ESP_LOGW(TAG, "Frequency status: %s", freq_status.c_str());
|
||||||
|
|
||||||
if (this->freq_status_text_sensor_ != nullptr) {
|
if (this->freq_status_text_sensor_ != nullptr) {
|
||||||
if (freq_status == "Normal") {
|
|
||||||
ESP_LOGD(TAG, "Frequency status: %s", freq_status.c_str());
|
|
||||||
} else {
|
|
||||||
ESP_LOGW(TAG, "Frequency status: %s", freq_status.c_str());
|
|
||||||
}
|
|
||||||
this->freq_status_text_sensor_->publish_state(freq_status);
|
this->freq_status_text_sensor_->publish_state(freq_status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -61,29 +61,15 @@ class ATM90E32Component : public PollingComponent,
|
|||||||
this->phase_[phase].harmonic_active_power_sensor_ = obj;
|
this->phase_[phase].harmonic_active_power_sensor_ = obj;
|
||||||
}
|
}
|
||||||
void set_peak_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].peak_current_sensor_ = obj; }
|
void set_peak_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].peak_current_sensor_ = obj; }
|
||||||
void set_volt_gain(int phase, uint16_t gain) {
|
void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].voltage_gain_ = gain; }
|
||||||
this->phase_[phase].voltage_gain_ = gain;
|
void set_ct_gain(int phase, uint16_t gain) { this->phase_[phase].ct_gain_ = gain; }
|
||||||
this->has_config_voltage_gain_[phase] = true;
|
void set_voltage_offset(uint8_t phase, int16_t offset) { this->offset_phase_[phase].voltage_offset_ = offset; }
|
||||||
}
|
void set_current_offset(uint8_t phase, int16_t offset) { this->offset_phase_[phase].current_offset_ = offset; }
|
||||||
void set_ct_gain(int phase, uint16_t gain) {
|
|
||||||
this->phase_[phase].ct_gain_ = gain;
|
|
||||||
this->has_config_current_gain_[phase] = true;
|
|
||||||
}
|
|
||||||
void set_voltage_offset(uint8_t phase, int16_t offset) {
|
|
||||||
this->offset_phase_[phase].voltage_offset_ = offset;
|
|
||||||
this->has_config_voltage_offset_[phase] = true;
|
|
||||||
}
|
|
||||||
void set_current_offset(uint8_t phase, int16_t offset) {
|
|
||||||
this->offset_phase_[phase].current_offset_ = offset;
|
|
||||||
this->has_config_current_offset_[phase] = true;
|
|
||||||
}
|
|
||||||
void set_active_power_offset(uint8_t phase, int16_t offset) {
|
void set_active_power_offset(uint8_t phase, int16_t offset) {
|
||||||
this->power_offset_phase_[phase].active_power_offset = offset;
|
this->power_offset_phase_[phase].active_power_offset = offset;
|
||||||
this->has_config_active_power_offset_[phase] = true;
|
|
||||||
}
|
}
|
||||||
void set_reactive_power_offset(uint8_t phase, int16_t offset) {
|
void set_reactive_power_offset(uint8_t phase, int16_t offset) {
|
||||||
this->power_offset_phase_[phase].reactive_power_offset = offset;
|
this->power_offset_phase_[phase].reactive_power_offset = offset;
|
||||||
this->has_config_reactive_power_offset_[phase] = true;
|
|
||||||
}
|
}
|
||||||
void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; }
|
void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; }
|
||||||
void set_peak_current_signed(bool flag) { peak_current_signed_ = flag; }
|
void set_peak_current_signed(bool flag) { peak_current_signed_ = flag; }
|
||||||
@@ -140,9 +126,8 @@ class ATM90E32Component : public PollingComponent,
|
|||||||
number::Number *ref_currents_[3]{nullptr, nullptr, nullptr};
|
number::Number *ref_currents_[3]{nullptr, nullptr, nullptr};
|
||||||
#endif
|
#endif
|
||||||
uint16_t read16_(uint16_t a_register);
|
uint16_t read16_(uint16_t a_register);
|
||||||
uint16_t read16_transaction_(uint16_t a_register);
|
|
||||||
int read32_(uint16_t addr_h, uint16_t addr_l);
|
int read32_(uint16_t addr_h, uint16_t addr_l);
|
||||||
void write16_(uint16_t a_register, uint16_t val, bool validate = true);
|
void write16_(uint16_t a_register, uint16_t val);
|
||||||
float get_local_phase_voltage_(uint8_t phase);
|
float get_local_phase_voltage_(uint8_t phase);
|
||||||
float get_local_phase_current_(uint8_t phase);
|
float get_local_phase_current_(uint8_t phase);
|
||||||
float get_local_phase_active_power_(uint8_t phase);
|
float get_local_phase_active_power_(uint8_t phase);
|
||||||
@@ -174,15 +159,12 @@ class ATM90E32Component : public PollingComponent,
|
|||||||
void restore_offset_calibrations_();
|
void restore_offset_calibrations_();
|
||||||
void restore_power_offset_calibrations_();
|
void restore_power_offset_calibrations_();
|
||||||
void restore_gain_calibrations_();
|
void restore_gain_calibrations_();
|
||||||
void save_offset_calibration_to_memory_();
|
|
||||||
void save_gain_calibration_to_memory_();
|
void save_gain_calibration_to_memory_();
|
||||||
void save_power_offset_calibration_to_memory_();
|
|
||||||
void write_offsets_to_registers_(uint8_t phase, int16_t voltage_offset, int16_t current_offset);
|
void write_offsets_to_registers_(uint8_t phase, int16_t voltage_offset, int16_t current_offset);
|
||||||
void write_power_offsets_to_registers_(uint8_t phase, int16_t p_offset, int16_t q_offset);
|
void write_power_offsets_to_registers_(uint8_t phase, int16_t p_offset, int16_t q_offset);
|
||||||
void write_gains_to_registers_();
|
void write_gains_to_registers_();
|
||||||
bool verify_gain_writes_();
|
bool verify_gain_writes_();
|
||||||
bool validate_spi_read_(uint16_t expected, const char *context = nullptr);
|
bool validate_spi_read_(uint16_t expected, const char *context = nullptr);
|
||||||
void log_calibration_status_();
|
|
||||||
|
|
||||||
struct ATM90E32Phase {
|
struct ATM90E32Phase {
|
||||||
uint16_t voltage_gain_{0};
|
uint16_t voltage_gain_{0};
|
||||||
@@ -222,33 +204,19 @@ class ATM90E32Component : public PollingComponent,
|
|||||||
int16_t current_offset_{0};
|
int16_t current_offset_{0};
|
||||||
} offset_phase_[3];
|
} offset_phase_[3];
|
||||||
|
|
||||||
OffsetCalibration config_offset_phase_[3];
|
|
||||||
|
|
||||||
struct PowerOffsetCalibration {
|
struct PowerOffsetCalibration {
|
||||||
int16_t active_power_offset{0};
|
int16_t active_power_offset{0};
|
||||||
int16_t reactive_power_offset{0};
|
int16_t reactive_power_offset{0};
|
||||||
} power_offset_phase_[3];
|
} power_offset_phase_[3];
|
||||||
|
|
||||||
PowerOffsetCalibration config_power_offset_phase_[3];
|
|
||||||
|
|
||||||
struct GainCalibration {
|
struct GainCalibration {
|
||||||
uint16_t voltage_gain{1};
|
uint16_t voltage_gain{1};
|
||||||
uint16_t current_gain{1};
|
uint16_t current_gain{1};
|
||||||
} gain_phase_[3];
|
} gain_phase_[3];
|
||||||
|
|
||||||
GainCalibration config_gain_phase_[3];
|
|
||||||
|
|
||||||
bool has_config_voltage_offset_[3]{false, false, false};
|
|
||||||
bool has_config_current_offset_[3]{false, false, false};
|
|
||||||
bool has_config_active_power_offset_[3]{false, false, false};
|
|
||||||
bool has_config_reactive_power_offset_[3]{false, false, false};
|
|
||||||
bool has_config_voltage_gain_[3]{false, false, false};
|
|
||||||
bool has_config_current_gain_[3]{false, false, false};
|
|
||||||
|
|
||||||
ESPPreferenceObject offset_pref_;
|
ESPPreferenceObject offset_pref_;
|
||||||
ESPPreferenceObject power_offset_pref_;
|
ESPPreferenceObject power_offset_pref_;
|
||||||
ESPPreferenceObject gain_calibration_pref_;
|
ESPPreferenceObject gain_calibration_pref_;
|
||||||
std::string cs_summary_;
|
|
||||||
|
|
||||||
sensor::Sensor *freq_sensor_{nullptr};
|
sensor::Sensor *freq_sensor_{nullptr};
|
||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
@@ -263,13 +231,6 @@ class ATM90E32Component : public PollingComponent,
|
|||||||
bool peak_current_signed_{false};
|
bool peak_current_signed_{false};
|
||||||
bool enable_offset_calibration_{false};
|
bool enable_offset_calibration_{false};
|
||||||
bool enable_gain_calibration_{false};
|
bool enable_gain_calibration_{false};
|
||||||
bool restored_offset_calibration_{false};
|
|
||||||
bool restored_power_offset_calibration_{false};
|
|
||||||
bool restored_gain_calibration_{false};
|
|
||||||
bool calibration_message_printed_{false};
|
|
||||||
bool offset_calibration_mismatch_[3]{false, false, false};
|
|
||||||
bool power_offset_calibration_mismatch_[3]{false, false, false};
|
|
||||||
bool gain_calibration_mismatch_[3]{false, false, false};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace atm90e32
|
} // namespace atm90e32
|
||||||
|
@@ -286,7 +286,6 @@ async def remove_bond_to_code(config, action_id, template_arg, args):
|
|||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
# Register the loggers this component needs
|
# Register the loggers this component needs
|
||||||
esp32_ble.register_bt_logger(BTLoggers.GATT, BTLoggers.SMP)
|
esp32_ble.register_bt_logger(BTLoggers.GATT, BTLoggers.SMP)
|
||||||
cg.add_define("USE_ESP32_BLE_UUID")
|
|
||||||
|
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
@@ -1,19 +1,13 @@
|
|||||||
import logging
|
|
||||||
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import esp32_ble, esp32_ble_client, esp32_ble_tracker
|
from esphome.components import esp32_ble, esp32_ble_client, esp32_ble_tracker
|
||||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||||
from esphome.components.esp32_ble import BTLoggers
|
from esphome.components.esp32_ble import BTLoggers
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ACTIVE, CONF_ID
|
from esphome.const import CONF_ACTIVE, CONF_ID
|
||||||
from esphome.core import CORE
|
|
||||||
from esphome.log import AnsiFore, color
|
|
||||||
|
|
||||||
AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
|
AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
|
||||||
DEPENDENCIES = ["api", "esp32"]
|
DEPENDENCIES = ["api", "esp32"]
|
||||||
CODEOWNERS = ["@jesserockz", "@bdraco"]
|
CODEOWNERS = ["@jesserockz"]
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
CONF_CONNECTION_SLOTS = "connection_slots"
|
CONF_CONNECTION_SLOTS = "connection_slots"
|
||||||
CONF_CACHE_SERVICES = "cache_services"
|
CONF_CACHE_SERVICES = "cache_services"
|
||||||
@@ -47,27 +41,6 @@ def validate_connections(config):
|
|||||||
esp32_ble_tracker.consume_connection_slots(connection_slots, "bluetooth_proxy")(
|
esp32_ble_tracker.consume_connection_slots(connection_slots, "bluetooth_proxy")(
|
||||||
config
|
config
|
||||||
)
|
)
|
||||||
|
|
||||||
# Warn about connection slot waste when using Arduino framework
|
|
||||||
if CORE.using_arduino and connection_slots:
|
|
||||||
_LOGGER.warning(
|
|
||||||
"Bluetooth Proxy with active connections on Arduino framework has suboptimal performance.\n"
|
|
||||||
"If BLE connections fail, they can waste connection slots for 10 seconds because\n"
|
|
||||||
"Arduino doesn't allow configuring the BLE connection timeout (fixed at 30s).\n"
|
|
||||||
"ESP-IDF framework allows setting it to 20s to match client timeouts.\n"
|
|
||||||
"\n"
|
|
||||||
"To switch to ESP-IDF, add this to your YAML:\n"
|
|
||||||
" esp32:\n"
|
|
||||||
" framework:\n"
|
|
||||||
" type: esp-idf\n"
|
|
||||||
"\n"
|
|
||||||
"For detailed migration instructions, see:\n"
|
|
||||||
"%s",
|
|
||||||
color(
|
|
||||||
AnsiFore.BLUE, "https://esphome.io/guides/esp32_arduino_to_idf.html"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
**config,
|
**config,
|
||||||
CONF_CONNECTIONS: [CONNECTION_SCHEMA({}) for _ in range(connection_slots)],
|
CONF_CONNECTIONS: [CONNECTION_SCHEMA({}) for _ in range(connection_slots)],
|
||||||
@@ -118,12 +91,6 @@ async def to_code(config):
|
|||||||
connection_count = len(config.get(CONF_CONNECTIONS, []))
|
connection_count = len(config.get(CONF_CONNECTIONS, []))
|
||||||
cg.add_define("BLUETOOTH_PROXY_MAX_CONNECTIONS", connection_count)
|
cg.add_define("BLUETOOTH_PROXY_MAX_CONNECTIONS", connection_count)
|
||||||
|
|
||||||
# Define batch size for BLE advertisements
|
|
||||||
# Each advertisement is up to 80 bytes when packaged (including protocol overhead)
|
|
||||||
# 16 advertisements × 80 bytes (worst case) = 1280 bytes out of ~1320 bytes usable payload
|
|
||||||
# This achieves ~97% WiFi MTU utilization while staying under the limit
|
|
||||||
cg.add_define("BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE", 16)
|
|
||||||
|
|
||||||
for connection_conf in config.get(CONF_CONNECTIONS, []):
|
for connection_conf in config.get(CONF_CONNECTIONS, []):
|
||||||
connection_var = cg.new_Pvariable(connection_conf[CONF_ID])
|
connection_var = cg.new_Pvariable(connection_conf[CONF_ID])
|
||||||
await cg.register_component(connection_var, connection_conf)
|
await cg.register_component(connection_var, connection_conf)
|
||||||
|
@@ -12,30 +12,16 @@ namespace esphome::bluetooth_proxy {
|
|||||||
|
|
||||||
static const char *const TAG = "bluetooth_proxy.connection";
|
static const char *const TAG = "bluetooth_proxy.connection";
|
||||||
|
|
||||||
// This function is allocation-free and directly packs UUIDs into the output array
|
|
||||||
// using precalculated constants for the Bluetooth base UUID
|
|
||||||
static void fill_128bit_uuid_array(std::array<uint64_t, 2> &out, esp_bt_uuid_t uuid_source) {
|
static void fill_128bit_uuid_array(std::array<uint64_t, 2> &out, esp_bt_uuid_t uuid_source) {
|
||||||
// Bluetooth base UUID: 00000000-0000-1000-8000-00805F9B34FB
|
esp_bt_uuid_t uuid = espbt::ESPBTUUID::from_uuid(uuid_source).as_128bit().get_uuid();
|
||||||
// out[0] = bytes 8-15 (big-endian)
|
out[0] = ((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) |
|
||||||
// - For 128-bit UUIDs: use bytes 8-15 as-is
|
((uint64_t) uuid.uuid.uuid128[13] << 40) | ((uint64_t) uuid.uuid.uuid128[12] << 32) |
|
||||||
// - For 16/32-bit UUIDs: insert into bytes 12-15, use 0x00001000 for bytes 8-11
|
((uint64_t) uuid.uuid.uuid128[11] << 24) | ((uint64_t) uuid.uuid.uuid128[10] << 16) |
|
||||||
out[0] = uuid_source.len == ESP_UUID_LEN_128
|
((uint64_t) uuid.uuid.uuid128[9] << 8) | ((uint64_t) uuid.uuid.uuid128[8]);
|
||||||
? (((uint64_t) uuid_source.uuid.uuid128[15] << 56) | ((uint64_t) uuid_source.uuid.uuid128[14] << 48) |
|
out[1] = ((uint64_t) uuid.uuid.uuid128[7] << 56) | ((uint64_t) uuid.uuid.uuid128[6] << 48) |
|
||||||
((uint64_t) uuid_source.uuid.uuid128[13] << 40) | ((uint64_t) uuid_source.uuid.uuid128[12] << 32) |
|
((uint64_t) uuid.uuid.uuid128[5] << 40) | ((uint64_t) uuid.uuid.uuid128[4] << 32) |
|
||||||
((uint64_t) uuid_source.uuid.uuid128[11] << 24) | ((uint64_t) uuid_source.uuid.uuid128[10] << 16) |
|
((uint64_t) uuid.uuid.uuid128[3] << 24) | ((uint64_t) uuid.uuid.uuid128[2] << 16) |
|
||||||
((uint64_t) uuid_source.uuid.uuid128[9] << 8) | ((uint64_t) uuid_source.uuid.uuid128[8]))
|
((uint64_t) uuid.uuid.uuid128[1] << 8) | ((uint64_t) uuid.uuid.uuid128[0]);
|
||||||
: (((uint64_t) (uuid_source.len == ESP_UUID_LEN_16 ? uuid_source.uuid.uuid16 : uuid_source.uuid.uuid32)
|
|
||||||
<< 32) |
|
|
||||||
0x00001000ULL); // Base UUID bytes 8-11
|
|
||||||
// out[1] = bytes 0-7 (big-endian)
|
|
||||||
// - For 128-bit UUIDs: use bytes 0-7 as-is
|
|
||||||
// - For 16/32-bit UUIDs: use precalculated base UUID constant
|
|
||||||
out[1] = uuid_source.len == ESP_UUID_LEN_128
|
|
||||||
? ((uint64_t) uuid_source.uuid.uuid128[7] << 56) | ((uint64_t) uuid_source.uuid.uuid128[6] << 48) |
|
|
||||||
((uint64_t) uuid_source.uuid.uuid128[5] << 40) | ((uint64_t) uuid_source.uuid.uuid128[4] << 32) |
|
|
||||||
((uint64_t) uuid_source.uuid.uuid128[3] << 24) | ((uint64_t) uuid_source.uuid.uuid128[2] << 16) |
|
|
||||||
((uint64_t) uuid_source.uuid.uuid128[1] << 8) | ((uint64_t) uuid_source.uuid.uuid128[0])
|
|
||||||
: 0x800000805F9B34FBULL; // Base UUID bytes 0-7: 80-00-00-80-5F-9B-34-FB
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to fill UUID in the appropriate format based on client support and UUID type
|
// Helper to fill UUID in the appropriate format based on client support and UUID type
|
||||||
@@ -94,11 +80,9 @@ void BluetoothConnection::dump_config() {
|
|||||||
|
|
||||||
void BluetoothConnection::update_allocated_slot_(uint64_t find_value, uint64_t set_value) {
|
void BluetoothConnection::update_allocated_slot_(uint64_t find_value, uint64_t set_value) {
|
||||||
auto &allocated = this->proxy_->connections_free_response_.allocated;
|
auto &allocated = this->proxy_->connections_free_response_.allocated;
|
||||||
for (auto &slot : allocated) {
|
auto *it = std::find(allocated.begin(), allocated.end(), find_value);
|
||||||
if (slot == find_value) {
|
if (it != allocated.end()) {
|
||||||
slot = set_value;
|
*it = set_value;
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,24 +105,13 @@ void BluetoothConnection::set_address(uint64_t address) {
|
|||||||
void BluetoothConnection::loop() {
|
void BluetoothConnection::loop() {
|
||||||
BLEClientBase::loop();
|
BLEClientBase::loop();
|
||||||
|
|
||||||
// Early return if no active connection
|
// Early return if no active connection or not in service discovery phase
|
||||||
if (this->address_ == 0) {
|
if (this->address_ == 0 || this->send_service_ < 0 || this->send_service_ > this->service_count_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle service discovery if in valid range
|
// Handle service discovery
|
||||||
if (this->send_service_ >= 0 && this->send_service_ <= this->service_count_) {
|
this->send_service_for_discovery_();
|
||||||
this->send_service_for_discovery_();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we should disable the loop
|
|
||||||
// - For V3_WITH_CACHE: Services are never sent, disable after INIT state
|
|
||||||
// - For V3_WITHOUT_CACHE: Disable only after service discovery is complete
|
|
||||||
// (send_service_ == DONE_SENDING_SERVICES, which is only set after services are sent)
|
|
||||||
if (this->state_ != espbt::ClientState::INIT && (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
|
||||||
this->send_service_ == DONE_SENDING_SERVICES)) {
|
|
||||||
this->disable_loop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BluetoothConnection::reset_connection_(esp_err_t reason) {
|
void BluetoothConnection::reset_connection_(esp_err_t reason) {
|
||||||
@@ -152,7 +125,7 @@ void BluetoothConnection::reset_connection_(esp_err_t reason) {
|
|||||||
// to detect incomplete service discovery rather than relying on us to
|
// to detect incomplete service discovery rather than relying on us to
|
||||||
// tell them about a partial list.
|
// tell them about a partial list.
|
||||||
this->set_address(0);
|
this->set_address(0);
|
||||||
this->send_service_ = INIT_SENDING_SERVICES;
|
this->send_service_ = DONE_SENDING_SERVICES;
|
||||||
this->proxy_->send_connections_free();
|
this->proxy_->send_connections_free();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +133,10 @@ void BluetoothConnection::send_service_for_discovery_() {
|
|||||||
if (this->send_service_ >= this->service_count_) {
|
if (this->send_service_ >= this->service_count_) {
|
||||||
this->send_service_ = DONE_SENDING_SERVICES;
|
this->send_service_ = DONE_SENDING_SERVICES;
|
||||||
this->proxy_->send_gatt_services_done(this->address_);
|
this->proxy_->send_gatt_services_done(this->address_);
|
||||||
this->release_services();
|
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
||||||
|
this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||||
|
this->release_services();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,7 +185,8 @@ void BluetoothConnection::send_service_for_discovery_() {
|
|||||||
service_result.start_handle, service_result.end_handle, 0, &total_char_count);
|
service_result.start_handle, service_result.end_handle, 0, &total_char_count);
|
||||||
|
|
||||||
if (char_count_status != ESP_GATT_OK) {
|
if (char_count_status != ESP_GATT_OK) {
|
||||||
this->log_connection_error_("esp_ble_gattc_get_attr_count", char_count_status);
|
ESP_LOGE(TAG, "[%d] [%s] Error getting characteristic count, status=%d", this->connection_index_,
|
||||||
|
this->address_str().c_str(), char_count_status);
|
||||||
this->send_service_ = DONE_SENDING_SERVICES;
|
this->send_service_ = DONE_SENDING_SERVICES;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -243,7 +220,8 @@ void BluetoothConnection::send_service_for_discovery_() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (char_status != ESP_GATT_OK) {
|
if (char_status != ESP_GATT_OK) {
|
||||||
this->log_connection_error_("esp_ble_gattc_get_all_char", char_status);
|
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->connection_index_,
|
||||||
|
this->address_str().c_str(), char_status);
|
||||||
this->send_service_ = DONE_SENDING_SERVICES;
|
this->send_service_ = DONE_SENDING_SERVICES;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -266,7 +244,8 @@ void BluetoothConnection::send_service_for_discovery_() {
|
|||||||
this->gattc_if_, this->conn_id_, ESP_GATT_DB_DESCRIPTOR, 0, 0, char_result.char_handle, &total_desc_count);
|
this->gattc_if_, this->conn_id_, ESP_GATT_DB_DESCRIPTOR, 0, 0, char_result.char_handle, &total_desc_count);
|
||||||
|
|
||||||
if (desc_count_status != ESP_GATT_OK) {
|
if (desc_count_status != ESP_GATT_OK) {
|
||||||
this->log_connection_error_("esp_ble_gattc_get_attr_count", desc_count_status);
|
ESP_LOGE(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d",
|
||||||
|
this->connection_index_, this->address_str().c_str(), char_result.char_handle, desc_count_status);
|
||||||
this->send_service_ = DONE_SENDING_SERVICES;
|
this->send_service_ = DONE_SENDING_SERVICES;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -287,7 +266,8 @@ void BluetoothConnection::send_service_for_discovery_() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (desc_status != ESP_GATT_OK) {
|
if (desc_status != ESP_GATT_OK) {
|
||||||
this->log_connection_error_("esp_ble_gattc_get_all_descr", desc_status);
|
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d", this->connection_index_,
|
||||||
|
this->address_str().c_str(), desc_status);
|
||||||
this->send_service_ = DONE_SENDING_SERVICES;
|
this->send_service_ = DONE_SENDING_SERVICES;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -341,33 +321,6 @@ void BluetoothConnection::send_service_for_discovery_() {
|
|||||||
api_conn->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE);
|
api_conn->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BluetoothConnection::log_connection_error_(const char *operation, esp_gatt_status_t status) {
|
|
||||||
ESP_LOGE(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str().c_str(), operation,
|
|
||||||
status);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BluetoothConnection::log_connection_warning_(const char *operation, esp_err_t err) {
|
|
||||||
ESP_LOGW(TAG, "[%d] [%s] %s failed, err=%d", this->connection_index_, this->address_str().c_str(), operation, err);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BluetoothConnection::log_gatt_not_connected_(const char *action, const char *type) {
|
|
||||||
ESP_LOGW(TAG, "[%d] [%s] Cannot %s GATT %s, not connected.", this->connection_index_, this->address_str().c_str(),
|
|
||||||
action, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BluetoothConnection::log_gatt_operation_error_(const char *operation, uint16_t handle, esp_gatt_status_t status) {
|
|
||||||
ESP_LOGW(TAG, "[%d] [%s] Error %s for handle 0x%2X, status=%d", this->connection_index_, this->address_str().c_str(),
|
|
||||||
operation, handle, status);
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t BluetoothConnection::check_and_log_error_(const char *operation, esp_err_t err) {
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
this->log_connection_warning_(operation, err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||||
esp_ble_gattc_cb_param_t *param) {
|
esp_ble_gattc_cb_param_t *param) {
|
||||||
if (!BLEClientBase::gattc_event_handler(event, gattc_if, param))
|
if (!BLEClientBase::gattc_event_handler(event, gattc_if, param))
|
||||||
@@ -408,7 +361,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
|||||||
case ESP_GATTC_READ_DESCR_EVT:
|
case ESP_GATTC_READ_DESCR_EVT:
|
||||||
case ESP_GATTC_READ_CHAR_EVT: {
|
case ESP_GATTC_READ_CHAR_EVT: {
|
||||||
if (param->read.status != ESP_GATT_OK) {
|
if (param->read.status != ESP_GATT_OK) {
|
||||||
this->log_gatt_operation_error_("reading char/descriptor", param->read.handle, param->read.status);
|
ESP_LOGW(TAG, "[%d] [%s] Error reading char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), param->read.handle, param->read.status);
|
||||||
this->proxy_->send_gatt_error(this->address_, param->read.handle, param->read.status);
|
this->proxy_->send_gatt_error(this->address_, param->read.handle, param->read.status);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -422,7 +376,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
|||||||
case ESP_GATTC_WRITE_CHAR_EVT:
|
case ESP_GATTC_WRITE_CHAR_EVT:
|
||||||
case ESP_GATTC_WRITE_DESCR_EVT: {
|
case ESP_GATTC_WRITE_DESCR_EVT: {
|
||||||
if (param->write.status != ESP_GATT_OK) {
|
if (param->write.status != ESP_GATT_OK) {
|
||||||
this->log_gatt_operation_error_("writing char/descriptor", param->write.handle, param->write.status);
|
ESP_LOGW(TAG, "[%d] [%s] Error writing char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), param->write.handle, param->write.status);
|
||||||
this->proxy_->send_gatt_error(this->address_, param->write.handle, param->write.status);
|
this->proxy_->send_gatt_error(this->address_, param->write.handle, param->write.status);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -434,8 +389,9 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
|||||||
}
|
}
|
||||||
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
|
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
|
||||||
if (param->unreg_for_notify.status != ESP_GATT_OK) {
|
if (param->unreg_for_notify.status != ESP_GATT_OK) {
|
||||||
this->log_gatt_operation_error_("unregistering notifications", param->unreg_for_notify.handle,
|
ESP_LOGW(TAG, "[%d] [%s] Error unregistering notifications for handle 0x%2X, status=%d",
|
||||||
param->unreg_for_notify.status);
|
this->connection_index_, this->address_str_.c_str(), param->unreg_for_notify.handle,
|
||||||
|
param->unreg_for_notify.status);
|
||||||
this->proxy_->send_gatt_error(this->address_, param->unreg_for_notify.handle, param->unreg_for_notify.status);
|
this->proxy_->send_gatt_error(this->address_, param->unreg_for_notify.handle, param->unreg_for_notify.status);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -447,8 +403,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
|||||||
}
|
}
|
||||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||||
if (param->reg_for_notify.status != ESP_GATT_OK) {
|
if (param->reg_for_notify.status != ESP_GATT_OK) {
|
||||||
this->log_gatt_operation_error_("registering notifications", param->reg_for_notify.handle,
|
ESP_LOGW(TAG, "[%d] [%s] Error registering notifications for handle 0x%2X, status=%d", this->connection_index_,
|
||||||
param->reg_for_notify.status);
|
this->address_str_.c_str(), param->reg_for_notify.handle, param->reg_for_notify.status);
|
||||||
this->proxy_->send_gatt_error(this->address_, param->reg_for_notify.handle, param->reg_for_notify.status);
|
this->proxy_->send_gatt_error(this->address_, param->reg_for_notify.handle, param->reg_for_notify.status);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -494,7 +450,8 @@ void BluetoothConnection::gap_event_handler(esp_gap_ble_cb_event_t event, esp_bl
|
|||||||
|
|
||||||
esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) {
|
esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) {
|
||||||
if (!this->connected()) {
|
if (!this->connected()) {
|
||||||
this->log_gatt_not_connected_("read", "characteristic");
|
ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT characteristic, not connected.", this->connection_index_,
|
||||||
|
this->address_str_.c_str());
|
||||||
return ESP_GATT_NOT_CONNECTED;
|
return ESP_GATT_NOT_CONNECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,12 +459,18 @@ esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) {
|
|||||||
handle);
|
handle);
|
||||||
|
|
||||||
esp_err_t err = esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE);
|
esp_err_t err = esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE);
|
||||||
return this->check_and_log_error_("esp_ble_gattc_read_char", err);
|
if (err != ERR_OK) {
|
||||||
|
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char error, err=%d", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std::string &data, bool response) {
|
esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std::string &data, bool response) {
|
||||||
if (!this->connected()) {
|
if (!this->connected()) {
|
||||||
this->log_gatt_not_connected_("write", "characteristic");
|
ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT characteristic, not connected.", this->connection_index_,
|
||||||
|
this->address_str_.c_str());
|
||||||
return ESP_GATT_NOT_CONNECTED;
|
return ESP_GATT_NOT_CONNECTED;
|
||||||
}
|
}
|
||||||
ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(),
|
ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(),
|
||||||
@@ -516,24 +479,36 @@ esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std::
|
|||||||
esp_err_t err =
|
esp_err_t err =
|
||||||
esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(),
|
esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(),
|
||||||
response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||||
return this->check_and_log_error_("esp_ble_gattc_write_char", err);
|
if (err != ERR_OK) {
|
||||||
|
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char error, err=%d", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) {
|
esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) {
|
||||||
if (!this->connected()) {
|
if (!this->connected()) {
|
||||||
this->log_gatt_not_connected_("read", "descriptor");
|
ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT descriptor, not connected.", this->connection_index_,
|
||||||
|
this->address_str_.c_str());
|
||||||
return ESP_GATT_NOT_CONNECTED;
|
return ESP_GATT_NOT_CONNECTED;
|
||||||
}
|
}
|
||||||
ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(),
|
ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(),
|
||||||
handle);
|
handle);
|
||||||
|
|
||||||
esp_err_t err = esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE);
|
esp_err_t err = esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE);
|
||||||
return this->check_and_log_error_("esp_ble_gattc_read_char_descr", err);
|
if (err != ERR_OK) {
|
||||||
|
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char_descr error, err=%d", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::string &data, bool response) {
|
esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::string &data, bool response) {
|
||||||
if (!this->connected()) {
|
if (!this->connected()) {
|
||||||
this->log_gatt_not_connected_("write", "descriptor");
|
ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT descriptor, not connected.", this->connection_index_,
|
||||||
|
this->address_str_.c_str());
|
||||||
return ESP_GATT_NOT_CONNECTED;
|
return ESP_GATT_NOT_CONNECTED;
|
||||||
}
|
}
|
||||||
ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(),
|
ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(),
|
||||||
@@ -542,12 +517,18 @@ esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::stri
|
|||||||
esp_err_t err = esp_ble_gattc_write_char_descr(
|
esp_err_t err = esp_ble_gattc_write_char_descr(
|
||||||
this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(),
|
this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(),
|
||||||
response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||||
return this->check_and_log_error_("esp_ble_gattc_write_char_descr", err);
|
if (err != ERR_OK) {
|
||||||
|
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, err=%d", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enable) {
|
esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enable) {
|
||||||
if (!this->connected()) {
|
if (!this->connected()) {
|
||||||
this->log_gatt_not_connected_("notify", "characteristic");
|
ESP_LOGW(TAG, "[%d] [%s] Cannot notify GATT characteristic, not connected.", this->connection_index_,
|
||||||
|
this->address_str_.c_str());
|
||||||
return ESP_GATT_NOT_CONNECTED;
|
return ESP_GATT_NOT_CONNECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,13 +536,22 @@ esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enabl
|
|||||||
ESP_LOGV(TAG, "[%d] [%s] Registering for GATT characteristic notifications handle %d", this->connection_index_,
|
ESP_LOGV(TAG, "[%d] [%s] Registering for GATT characteristic notifications handle %d", this->connection_index_,
|
||||||
this->address_str_.c_str(), handle);
|
this->address_str_.c_str(), handle);
|
||||||
esp_err_t err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, handle);
|
esp_err_t err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, handle);
|
||||||
return this->check_and_log_error_("esp_ble_gattc_register_for_notify", err);
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_register_for_notify failed, err=%d", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ESP_LOGV(TAG, "[%d] [%s] Unregistering for GATT characteristic notifications handle %d", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), handle);
|
||||||
|
esp_err_t err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, handle);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_unregister_for_notify failed, err=%d", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return ESP_OK;
|
||||||
ESP_LOGV(TAG, "[%d] [%s] Unregistering for GATT characteristic notifications handle %d", this->connection_index_,
|
|
||||||
this->address_str_.c_str(), handle);
|
|
||||||
esp_err_t err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, handle);
|
|
||||||
return this->check_and_log_error_("esp_ble_gattc_unregister_for_notify", err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
esp32_ble_tracker::AdvertisementParserType BluetoothConnection::get_advertisement_parser_type() {
|
esp32_ble_tracker::AdvertisementParserType BluetoothConnection::get_advertisement_parser_type() {
|
||||||
|
@@ -33,18 +33,13 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase {
|
|||||||
void send_service_for_discovery_();
|
void send_service_for_discovery_();
|
||||||
void reset_connection_(esp_err_t reason);
|
void reset_connection_(esp_err_t reason);
|
||||||
void update_allocated_slot_(uint64_t find_value, uint64_t set_value);
|
void update_allocated_slot_(uint64_t find_value, uint64_t set_value);
|
||||||
void log_connection_error_(const char *operation, esp_gatt_status_t status);
|
|
||||||
void log_connection_warning_(const char *operation, esp_err_t err);
|
|
||||||
void log_gatt_not_connected_(const char *action, const char *type);
|
|
||||||
void log_gatt_operation_error_(const char *operation, uint16_t handle, esp_gatt_status_t status);
|
|
||||||
esp_err_t check_and_log_error_(const char *operation, esp_err_t err);
|
|
||||||
|
|
||||||
// Memory optimized layout for 32-bit systems
|
// Memory optimized layout for 32-bit systems
|
||||||
// Group 1: Pointers (4 bytes each, naturally aligned)
|
// Group 1: Pointers (4 bytes each, naturally aligned)
|
||||||
BluetoothProxy *proxy_;
|
BluetoothProxy *proxy_;
|
||||||
|
|
||||||
// Group 2: 2-byte types
|
// Group 2: 2-byte types
|
||||||
int16_t send_service_{-3}; // -3 = INIT_SENDING_SERVICES, -2 = DONE_SENDING_SERVICES, >=0 = service index
|
int16_t send_service_{-2}; // Needs to handle negative values and service count
|
||||||
|
|
||||||
// Group 3: 1-byte types
|
// Group 3: 1-byte types
|
||||||
bool seen_mtu_or_services_{false};
|
bool seen_mtu_or_services_{false};
|
||||||
|
@@ -11,8 +11,12 @@ namespace esphome::bluetooth_proxy {
|
|||||||
|
|
||||||
static const char *const TAG = "bluetooth_proxy";
|
static const char *const TAG = "bluetooth_proxy";
|
||||||
|
|
||||||
// BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE is defined during code generation
|
// Batch size for BLE advertisements to maximize WiFi efficiency
|
||||||
// It sets the batch size for BLE advertisements to maximize WiFi efficiency
|
// Each advertisement is up to 80 bytes when packaged (including protocol overhead)
|
||||||
|
// Most advertisements are 20-30 bytes, allowing even more to fit per packet
|
||||||
|
// 16 advertisements × 80 bytes (worst case) = 1280 bytes out of ~1320 bytes usable payload
|
||||||
|
// This achieves ~97% WiFi MTU utilization while staying under the limit
|
||||||
|
static constexpr size_t FLUSH_BATCH_SIZE = 16;
|
||||||
|
|
||||||
// Verify BLE advertisement data array size matches the BLE specification (31 bytes adv + 31 bytes scan response)
|
// Verify BLE advertisement data array size matches the BLE specification (31 bytes adv + 31 bytes scan response)
|
||||||
static_assert(sizeof(((api::BluetoothLERawAdvertisement *) nullptr)->data) == 62,
|
static_assert(sizeof(((api::BluetoothLERawAdvertisement *) nullptr)->data) == 62,
|
||||||
@@ -21,8 +25,18 @@ static_assert(sizeof(((api::BluetoothLERawAdvertisement *) nullptr)->data) == 62
|
|||||||
BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; }
|
BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; }
|
||||||
|
|
||||||
void BluetoothProxy::setup() {
|
void BluetoothProxy::setup() {
|
||||||
this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS;
|
// Pre-allocate response object
|
||||||
this->connections_free_response_.free = BLUETOOTH_PROXY_MAX_CONNECTIONS;
|
this->response_ = std::make_unique<api::BluetoothLERawAdvertisementsResponse>();
|
||||||
|
|
||||||
|
// Reserve capacity but start with size 0
|
||||||
|
// Reserve 50% since we'll grow naturally and flush at FLUSH_BATCH_SIZE
|
||||||
|
this->response_->advertisements.reserve(FLUSH_BATCH_SIZE / 2);
|
||||||
|
|
||||||
|
// Don't pre-allocate pool - let it grow only if needed in busy environments
|
||||||
|
// Many devices in quiet areas will never need the overflow pool
|
||||||
|
|
||||||
|
this->connections_free_response_.limit = this->connections_.size();
|
||||||
|
this->connections_free_response_.free = this->connections_.size();
|
||||||
|
|
||||||
this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
|
this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
|
||||||
if (this->api_connection_ != nullptr) {
|
if (this->api_connection_ != nullptr) {
|
||||||
@@ -39,26 +53,6 @@ void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerSta
|
|||||||
this->api_connection_->send_message(resp, api::BluetoothScannerStateResponse::MESSAGE_TYPE);
|
this->api_connection_->send_message(resp, api::BluetoothScannerStateResponse::MESSAGE_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BluetoothProxy::log_connection_request_ignored_(BluetoothConnection *connection, espbt::ClientState state) {
|
|
||||||
ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, state: %s", connection->get_connection_index(),
|
|
||||||
connection->address_str().c_str(), espbt::client_state_to_string(state));
|
|
||||||
}
|
|
||||||
|
|
||||||
void BluetoothProxy::log_connection_info_(BluetoothConnection *connection, const char *message) {
|
|
||||||
ESP_LOGI(TAG, "[%d] [%s] Connecting %s", connection->get_connection_index(), connection->address_str().c_str(),
|
|
||||||
message);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BluetoothProxy::log_not_connected_gatt_(const char *action, const char *type) {
|
|
||||||
ESP_LOGW(TAG, "Cannot %s GATT %s, not connected", action, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BluetoothProxy::handle_gatt_not_connected_(uint64_t address, uint16_t handle, const char *action,
|
|
||||||
const char *type) {
|
|
||||||
this->log_not_connected_gatt_(action, type);
|
|
||||||
this->send_gatt_error(address, handle, ESP_GATT_NOT_CONNECTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef USE_ESP32_BLE_DEVICE
|
#ifdef USE_ESP32_BLE_DEVICE
|
||||||
bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||||
// This method should never be called since bluetooth_proxy always uses raw advertisements
|
// This method should never be called since bluetooth_proxy always uses raw advertisements
|
||||||
@@ -71,27 +65,39 @@ bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results,
|
|||||||
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr)
|
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
auto &advertisements = this->response_.advertisements;
|
auto &advertisements = this->response_->advertisements;
|
||||||
|
|
||||||
for (size_t i = 0; i < count; i++) {
|
for (size_t i = 0; i < count; i++) {
|
||||||
auto &result = scan_results[i];
|
auto &result = scan_results[i];
|
||||||
uint8_t length = result.adv_data_len + result.scan_rsp_len;
|
uint8_t length = result.adv_data_len + result.scan_rsp_len;
|
||||||
|
|
||||||
|
// Check if we need to expand the vector
|
||||||
|
if (this->advertisement_count_ >= advertisements.size()) {
|
||||||
|
if (this->advertisement_pool_.empty()) {
|
||||||
|
// No room in pool, need to allocate
|
||||||
|
advertisements.emplace_back();
|
||||||
|
} else {
|
||||||
|
// Pull from pool
|
||||||
|
advertisements.push_back(std::move(this->advertisement_pool_.back()));
|
||||||
|
this->advertisement_pool_.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fill in the data directly at current position
|
// Fill in the data directly at current position
|
||||||
auto &adv = advertisements[this->response_.advertisements_len];
|
auto &adv = advertisements[this->advertisement_count_];
|
||||||
adv.address = esp32_ble::ble_addr_to_uint64(result.bda);
|
adv.address = esp32_ble::ble_addr_to_uint64(result.bda);
|
||||||
adv.rssi = result.rssi;
|
adv.rssi = result.rssi;
|
||||||
adv.address_type = result.ble_addr_type;
|
adv.address_type = result.ble_addr_type;
|
||||||
adv.data_len = length;
|
adv.data_len = length;
|
||||||
std::memcpy(adv.data, result.ble_adv, length);
|
std::memcpy(adv.data, result.ble_adv, length);
|
||||||
|
|
||||||
this->response_.advertisements_len++;
|
this->advertisement_count_++;
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Queuing raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0],
|
ESP_LOGV(TAG, "Queuing raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0],
|
||||||
result.bda[1], result.bda[2], result.bda[3], result.bda[4], result.bda[5], length, result.rssi);
|
result.bda[1], result.bda[2], result.bda[3], result.bda[4], result.bda[5], length, result.rssi);
|
||||||
|
|
||||||
// Flush if we have reached BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE
|
// Flush if we have reached FLUSH_BATCH_SIZE
|
||||||
if (this->response_.advertisements_len >= BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE) {
|
if (this->advertisement_count_ >= FLUSH_BATCH_SIZE) {
|
||||||
this->flush_pending_advertisements();
|
this->flush_pending_advertisements();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,31 +106,40 @@ bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BluetoothProxy::flush_pending_advertisements() {
|
void BluetoothProxy::flush_pending_advertisements() {
|
||||||
if (this->response_.advertisements_len == 0 || !api::global_api_server->is_connected() ||
|
if (this->advertisement_count_ == 0 || !api::global_api_server->is_connected() || this->api_connection_ == nullptr)
|
||||||
this->api_connection_ == nullptr)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
auto &advertisements = this->response_->advertisements;
|
||||||
|
|
||||||
|
// Return any items beyond advertisement_count_ to the pool
|
||||||
|
if (advertisements.size() > this->advertisement_count_) {
|
||||||
|
// Move unused items back to pool
|
||||||
|
this->advertisement_pool_.insert(this->advertisement_pool_.end(),
|
||||||
|
std::make_move_iterator(advertisements.begin() + this->advertisement_count_),
|
||||||
|
std::make_move_iterator(advertisements.end()));
|
||||||
|
|
||||||
|
// Resize to actual count
|
||||||
|
advertisements.resize(this->advertisement_count_);
|
||||||
|
}
|
||||||
|
|
||||||
// Send the message
|
// Send the message
|
||||||
this->api_connection_->send_message(this->response_, api::BluetoothLERawAdvertisementsResponse::MESSAGE_TYPE);
|
this->api_connection_->send_message(*this->response_, api::BluetoothLERawAdvertisementsResponse::MESSAGE_TYPE);
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Sent batch of %u BLE advertisements", this->response_.advertisements_len);
|
// Reset count - existing items will be overwritten in next batch
|
||||||
|
this->advertisement_count_ = 0;
|
||||||
// Reset the length for the next batch
|
|
||||||
this->response_.advertisements_len = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BluetoothProxy::dump_config() {
|
void BluetoothProxy::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Bluetooth Proxy:");
|
||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
"Bluetooth Proxy:\n"
|
|
||||||
" Active: %s\n"
|
" Active: %s\n"
|
||||||
" Connections: %d",
|
" Connections: %d",
|
||||||
YESNO(this->active_), this->connection_count_);
|
YESNO(this->active_), this->connections_.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void BluetoothProxy::loop() {
|
void BluetoothProxy::loop() {
|
||||||
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) {
|
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) {
|
||||||
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
for (auto *connection : this->connections_) {
|
||||||
auto *connection = this->connections_[i];
|
|
||||||
if (connection->get_address() != 0 && !connection->disconnect_pending()) {
|
if (connection->get_address() != 0 && !connection->disconnect_pending()) {
|
||||||
connection->disconnect();
|
connection->disconnect();
|
||||||
}
|
}
|
||||||
@@ -147,8 +162,7 @@ esp32_ble_tracker::AdvertisementParserType BluetoothProxy::get_advertisement_par
|
|||||||
}
|
}
|
||||||
|
|
||||||
BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) {
|
BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) {
|
||||||
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
for (auto *connection : this->connections_) {
|
||||||
auto *connection = this->connections_[i];
|
|
||||||
if (connection->get_address() == address)
|
if (connection->get_address() == address)
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
@@ -156,10 +170,9 @@ BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool rese
|
|||||||
if (!reserve)
|
if (!reserve)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
for (auto *connection : this->connections_) {
|
||||||
auto *connection = this->connections_[i];
|
|
||||||
if (connection->get_address() == 0) {
|
if (connection->get_address() == 0) {
|
||||||
connection->send_service_ = INIT_SENDING_SERVICES;
|
connection->send_service_ = DONE_SENDING_SERVICES;
|
||||||
connection->set_address(address);
|
connection->set_address(address);
|
||||||
// All connections must start at INIT
|
// All connections must start at INIT
|
||||||
// We only set the state if we allocate the connection
|
// We only set the state if we allocate the connection
|
||||||
@@ -176,7 +189,8 @@ BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool rese
|
|||||||
void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest &msg) {
|
void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest &msg) {
|
||||||
switch (msg.request_type) {
|
switch (msg.request_type) {
|
||||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE:
|
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE:
|
||||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE: {
|
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE:
|
||||||
|
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: {
|
||||||
auto *connection = this->get_connection_(msg.address, true);
|
auto *connection = this->get_connection_(msg.address, true);
|
||||||
if (connection == nullptr) {
|
if (connection == nullptr) {
|
||||||
ESP_LOGW(TAG, "No free connections available");
|
ESP_LOGW(TAG, "No free connections available");
|
||||||
@@ -185,10 +199,23 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
|
|||||||
}
|
}
|
||||||
if (connection->state() == espbt::ClientState::CONNECTED ||
|
if (connection->state() == espbt::ClientState::CONNECTED ||
|
||||||
connection->state() == espbt::ClientState::ESTABLISHED) {
|
connection->state() == espbt::ClientState::ESTABLISHED) {
|
||||||
this->log_connection_request_ignored_(connection, connection->state());
|
ESP_LOGW(TAG, "[%d] [%s] Connection already established", connection->get_connection_index(),
|
||||||
|
connection->address_str().c_str());
|
||||||
this->send_device_connection(msg.address, true);
|
this->send_device_connection(msg.address, true);
|
||||||
this->send_connections_free();
|
this->send_connections_free();
|
||||||
return;
|
return;
|
||||||
|
} else if (connection->state() == espbt::ClientState::SEARCHING) {
|
||||||
|
ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, already searching for device",
|
||||||
|
connection->get_connection_index(), connection->address_str().c_str());
|
||||||
|
return;
|
||||||
|
} else if (connection->state() == espbt::ClientState::DISCOVERED) {
|
||||||
|
ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, device already discovered",
|
||||||
|
connection->get_connection_index(), connection->address_str().c_str());
|
||||||
|
return;
|
||||||
|
} else if (connection->state() == espbt::ClientState::READY_TO_CONNECT) {
|
||||||
|
ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, waiting in line to connect",
|
||||||
|
connection->get_connection_index(), connection->address_str().c_str());
|
||||||
|
return;
|
||||||
} else if (connection->state() == espbt::ClientState::CONNECTING) {
|
} else if (connection->state() == espbt::ClientState::CONNECTING) {
|
||||||
if (connection->disconnect_pending()) {
|
if (connection->disconnect_pending()) {
|
||||||
ESP_LOGW(TAG, "[%d] [%s] Connection request while pending disconnect, cancelling pending disconnect",
|
ESP_LOGW(TAG, "[%d] [%s] Connection request while pending disconnect, cancelling pending disconnect",
|
||||||
@@ -196,18 +223,29 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
|
|||||||
connection->cancel_pending_disconnect();
|
connection->cancel_pending_disconnect();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->log_connection_request_ignored_(connection, connection->state());
|
ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, already connecting", connection->get_connection_index(),
|
||||||
|
connection->address_str().c_str());
|
||||||
|
return;
|
||||||
|
} else if (connection->state() == espbt::ClientState::DISCONNECTING) {
|
||||||
|
ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, device is disconnecting",
|
||||||
|
connection->get_connection_index(), connection->address_str().c_str());
|
||||||
return;
|
return;
|
||||||
} else if (connection->state() != espbt::ClientState::INIT) {
|
} else if (connection->state() != espbt::ClientState::INIT) {
|
||||||
this->log_connection_request_ignored_(connection, connection->state());
|
ESP_LOGW(TAG, "[%d] [%s] Connection already in progress", connection->get_connection_index(),
|
||||||
|
connection->address_str().c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (msg.request_type == api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE) {
|
if (msg.request_type == api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE) {
|
||||||
connection->set_connection_type(espbt::ConnectionType::V3_WITH_CACHE);
|
connection->set_connection_type(espbt::ConnectionType::V3_WITH_CACHE);
|
||||||
this->log_connection_info_(connection, "v3 with cache");
|
ESP_LOGI(TAG, "[%d] [%s] Connecting v3 with cache", connection->get_connection_index(),
|
||||||
} else { // BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE
|
connection->address_str().c_str());
|
||||||
|
} else if (msg.request_type == api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE) {
|
||||||
connection->set_connection_type(espbt::ConnectionType::V3_WITHOUT_CACHE);
|
connection->set_connection_type(espbt::ConnectionType::V3_WITHOUT_CACHE);
|
||||||
this->log_connection_info_(connection, "v3 without cache");
|
ESP_LOGI(TAG, "[%d] [%s] Connecting v3 without cache", connection->get_connection_index(),
|
||||||
|
connection->address_str().c_str());
|
||||||
|
} else {
|
||||||
|
connection->set_connection_type(espbt::ConnectionType::V1);
|
||||||
|
ESP_LOGI(TAG, "[%d] [%s] Connecting v1", connection->get_connection_index(), connection->address_str().c_str());
|
||||||
}
|
}
|
||||||
if (msg.has_address_type) {
|
if (msg.has_address_type) {
|
||||||
uint64_to_bd_addr(msg.address, connection->remote_bda_);
|
uint64_to_bd_addr(msg.address, connection->remote_bda_);
|
||||||
@@ -269,18 +307,14 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: {
|
|
||||||
ESP_LOGE(TAG, "V1 connections removed");
|
|
||||||
this->send_device_connection(msg.address, false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) {
|
void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) {
|
||||||
auto *connection = this->get_connection_(msg.address, false);
|
auto *connection = this->get_connection_(msg.address, false);
|
||||||
if (connection == nullptr) {
|
if (connection == nullptr) {
|
||||||
this->handle_gatt_not_connected_(msg.address, msg.handle, "read", "characteristic");
|
ESP_LOGW(TAG, "Cannot read GATT characteristic, not connected");
|
||||||
|
this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +327,8 @@ void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &ms
|
|||||||
void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg) {
|
void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg) {
|
||||||
auto *connection = this->get_connection_(msg.address, false);
|
auto *connection = this->get_connection_(msg.address, false);
|
||||||
if (connection == nullptr) {
|
if (connection == nullptr) {
|
||||||
this->handle_gatt_not_connected_(msg.address, msg.handle, "write", "characteristic");
|
ESP_LOGW(TAG, "Cannot write GATT characteristic, not connected");
|
||||||
|
this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,7 +341,8 @@ void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &
|
|||||||
void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg) {
|
void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg) {
|
||||||
auto *connection = this->get_connection_(msg.address, false);
|
auto *connection = this->get_connection_(msg.address, false);
|
||||||
if (connection == nullptr) {
|
if (connection == nullptr) {
|
||||||
this->handle_gatt_not_connected_(msg.address, msg.handle, "read", "descriptor");
|
ESP_LOGW(TAG, "Cannot read GATT descriptor, not connected");
|
||||||
|
this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,7 +355,8 @@ void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTRead
|
|||||||
void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg) {
|
void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg) {
|
||||||
auto *connection = this->get_connection_(msg.address, false);
|
auto *connection = this->get_connection_(msg.address, false);
|
||||||
if (connection == nullptr) {
|
if (connection == nullptr) {
|
||||||
this->handle_gatt_not_connected_(msg.address, msg.handle, "write", "descriptor");
|
ESP_LOGW(TAG, "Cannot write GATT descriptor, not connected");
|
||||||
|
this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,7 +369,8 @@ void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWri
|
|||||||
void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg) {
|
void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg) {
|
||||||
auto *connection = this->get_connection_(msg.address, false);
|
auto *connection = this->get_connection_(msg.address, false);
|
||||||
if (connection == nullptr || !connection->connected()) {
|
if (connection == nullptr || !connection->connected()) {
|
||||||
this->handle_gatt_not_connected_(msg.address, 0, "get", "services");
|
ESP_LOGW(TAG, "Cannot get GATT services, not connected");
|
||||||
|
this->send_gatt_error(msg.address, 0, ESP_GATT_NOT_CONNECTED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!connection->service_count_) {
|
if (!connection->service_count_) {
|
||||||
@@ -340,14 +378,16 @@ void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetSer
|
|||||||
this->send_gatt_services_done(msg.address);
|
this->send_gatt_services_done(msg.address);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (connection->send_service_ == INIT_SENDING_SERVICES) // Start sending services if not started yet
|
if (connection->send_service_ ==
|
||||||
|
DONE_SENDING_SERVICES) // Only start sending services if we're not already sending them
|
||||||
connection->send_service_ = 0;
|
connection->send_service_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BluetoothProxy::bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg) {
|
void BluetoothProxy::bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg) {
|
||||||
auto *connection = this->get_connection_(msg.address, false);
|
auto *connection = this->get_connection_(msg.address, false);
|
||||||
if (connection == nullptr) {
|
if (connection == nullptr) {
|
||||||
this->handle_gatt_not_connected_(msg.address, msg.handle, "notify", "characteristic");
|
ESP_LOGW(TAG, "Cannot notify GATT characteristic, not connected");
|
||||||
|
this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -23,7 +22,6 @@ namespace esphome::bluetooth_proxy {
|
|||||||
|
|
||||||
static const esp_err_t ESP_GATT_NOT_CONNECTED = -1;
|
static const esp_err_t ESP_GATT_NOT_CONNECTED = -1;
|
||||||
static const int DONE_SENDING_SERVICES = -2;
|
static const int DONE_SENDING_SERVICES = -2;
|
||||||
static const int INIT_SENDING_SERVICES = -3;
|
|
||||||
|
|
||||||
using namespace esp32_ble_client;
|
using namespace esp32_ble_client;
|
||||||
|
|
||||||
@@ -65,10 +63,8 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
|||||||
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
|
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
|
||||||
|
|
||||||
void register_connection(BluetoothConnection *connection) {
|
void register_connection(BluetoothConnection *connection) {
|
||||||
if (this->connection_count_ < BLUETOOTH_PROXY_MAX_CONNECTIONS) {
|
this->connections_.push_back(connection);
|
||||||
this->connections_[this->connection_count_++] = connection;
|
connection->proxy_ = this;
|
||||||
connection->proxy_ = this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void bluetooth_device_request(const api::BluetoothDeviceRequest &msg);
|
void bluetooth_device_request(const api::BluetoothDeviceRequest &msg);
|
||||||
@@ -137,20 +133,17 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
|||||||
void send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state);
|
void send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state);
|
||||||
|
|
||||||
BluetoothConnection *get_connection_(uint64_t address, bool reserve);
|
BluetoothConnection *get_connection_(uint64_t address, bool reserve);
|
||||||
void log_connection_request_ignored_(BluetoothConnection *connection, espbt::ClientState state);
|
|
||||||
void log_connection_info_(BluetoothConnection *connection, const char *message);
|
|
||||||
void log_not_connected_gatt_(const char *action, const char *type);
|
|
||||||
void handle_gatt_not_connected_(uint64_t address, uint16_t handle, const char *action, const char *type);
|
|
||||||
|
|
||||||
// Memory optimized layout for 32-bit systems
|
// Memory optimized layout for 32-bit systems
|
||||||
// Group 1: Pointers (4 bytes each, naturally aligned)
|
// Group 1: Pointers (4 bytes each, naturally aligned)
|
||||||
api::APIConnection *api_connection_{nullptr};
|
api::APIConnection *api_connection_{nullptr};
|
||||||
|
|
||||||
// Group 2: Fixed-size array of connection pointers
|
// Group 2: Container types (typically 12 bytes on 32-bit)
|
||||||
std::array<BluetoothConnection *, BLUETOOTH_PROXY_MAX_CONNECTIONS> connections_{};
|
std::vector<BluetoothConnection *> connections_{};
|
||||||
|
|
||||||
// BLE advertisement batching
|
// BLE advertisement batching
|
||||||
api::BluetoothLERawAdvertisementsResponse response_;
|
std::vector<api::BluetoothLERawAdvertisement> advertisement_pool_;
|
||||||
|
std::unique_ptr<api::BluetoothLERawAdvertisementsResponse> response_;
|
||||||
|
|
||||||
// Group 3: 4-byte types
|
// Group 3: 4-byte types
|
||||||
uint32_t last_advertisement_flush_time_{0};
|
uint32_t last_advertisement_flush_time_{0};
|
||||||
@@ -160,7 +153,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
|||||||
|
|
||||||
// Group 4: 1-byte types grouped together
|
// Group 4: 1-byte types grouped together
|
||||||
bool active_;
|
bool active_;
|
||||||
uint8_t connection_count_{0};
|
uint8_t advertisement_count_{0};
|
||||||
// 2 bytes used, 2 bytes padding
|
// 2 bytes used, 2 bytes padding
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -7,8 +7,6 @@
|
|||||||
#include <esphome/components/sensor/sensor.h>
|
#include <esphome/components/sensor/sensor.h>
|
||||||
#include <esphome/core/component.h>
|
#include <esphome/core/component.h>
|
||||||
|
|
||||||
#define BME280_ERROR_WRONG_CHIP_ID "Wrong chip ID"
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace bme280_base {
|
namespace bme280_base {
|
||||||
|
|
||||||
@@ -100,18 +98,18 @@ void BME280Component::setup() {
|
|||||||
|
|
||||||
if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) {
|
if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) {
|
||||||
this->error_code_ = COMMUNICATION_FAILED;
|
this->error_code_ = COMMUNICATION_FAILED;
|
||||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (chip_id != 0x60) {
|
if (chip_id != 0x60) {
|
||||||
this->error_code_ = WRONG_CHIP_ID;
|
this->error_code_ = WRONG_CHIP_ID;
|
||||||
this->mark_failed(BME280_ERROR_WRONG_CHIP_ID);
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a soft reset.
|
// Send a soft reset.
|
||||||
if (!this->write_byte(BME280_REGISTER_RESET, BME280_SOFT_RESET)) {
|
if (!this->write_byte(BME280_REGISTER_RESET, BME280_SOFT_RESET)) {
|
||||||
this->mark_failed("Reset failed");
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Wait until the NVM data has finished loading.
|
// Wait until the NVM data has finished loading.
|
||||||
@@ -120,12 +118,14 @@ void BME280Component::setup() {
|
|||||||
do { // NOLINT
|
do { // NOLINT
|
||||||
delay(2);
|
delay(2);
|
||||||
if (!this->read_byte(BME280_REGISTER_STATUS, &status)) {
|
if (!this->read_byte(BME280_REGISTER_STATUS, &status)) {
|
||||||
this->mark_failed("Error reading status register");
|
ESP_LOGW(TAG, "Error reading status register.");
|
||||||
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} while ((status & BME280_STATUS_IM_UPDATE) && (--retry));
|
} while ((status & BME280_STATUS_IM_UPDATE) && (--retry));
|
||||||
if (status & BME280_STATUS_IM_UPDATE) {
|
if (status & BME280_STATUS_IM_UPDATE) {
|
||||||
this->mark_failed("Timeout loading NVM");
|
ESP_LOGW(TAG, "Timeout loading NVM.");
|
||||||
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,26 +153,26 @@ void BME280Component::setup() {
|
|||||||
|
|
||||||
uint8_t humid_control_val = 0;
|
uint8_t humid_control_val = 0;
|
||||||
if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_control_val)) {
|
if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_control_val)) {
|
||||||
this->mark_failed("Read humidity control");
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
humid_control_val &= ~0b00000111;
|
humid_control_val &= ~0b00000111;
|
||||||
humid_control_val |= this->humidity_oversampling_ & 0b111;
|
humid_control_val |= this->humidity_oversampling_ & 0b111;
|
||||||
if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_control_val)) {
|
if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_control_val)) {
|
||||||
this->mark_failed("Write humidity control");
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t config_register = 0;
|
uint8_t config_register = 0;
|
||||||
if (!this->read_byte(BME280_REGISTER_CONFIG, &config_register)) {
|
if (!this->read_byte(BME280_REGISTER_CONFIG, &config_register)) {
|
||||||
this->mark_failed("Read config");
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
config_register &= ~0b11111100;
|
config_register &= ~0b11111100;
|
||||||
config_register |= 0b101 << 5; // 1000 ms standby time
|
config_register |= 0b101 << 5; // 1000 ms standby time
|
||||||
config_register |= (this->iir_filter_ & 0b111) << 2;
|
config_register |= (this->iir_filter_ & 0b111) << 2;
|
||||||
if (!this->write_byte(BME280_REGISTER_CONFIG, config_register)) {
|
if (!this->write_byte(BME280_REGISTER_CONFIG, config_register)) {
|
||||||
this->mark_failed("Write config");
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,7 +183,7 @@ void BME280Component::dump_config() {
|
|||||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||||
break;
|
break;
|
||||||
case WRONG_CHIP_ID:
|
case WRONG_CHIP_ID:
|
||||||
ESP_LOGE(TAG, BME280_ERROR_WRONG_CHIP_ID);
|
ESP_LOGE(TAG, "BME280 has wrong chip ID! Is it a BME280?");
|
||||||
break;
|
break;
|
||||||
case NONE:
|
case NONE:
|
||||||
default:
|
default:
|
||||||
@@ -223,21 +223,21 @@ void BME280Component::update() {
|
|||||||
this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
|
this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
|
||||||
uint8_t data[8];
|
uint8_t data[8];
|
||||||
if (!this->read_bytes(BME280_REGISTER_MEASUREMENTS, data, 8)) {
|
if (!this->read_bytes(BME280_REGISTER_MEASUREMENTS, data, 8)) {
|
||||||
ESP_LOGW(TAG, "Error reading registers");
|
ESP_LOGW(TAG, "Error reading registers.");
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int32_t t_fine = 0;
|
int32_t t_fine = 0;
|
||||||
float const temperature = this->read_temperature_(data, &t_fine);
|
float const temperature = this->read_temperature_(data, &t_fine);
|
||||||
if (std::isnan(temperature)) {
|
if (std::isnan(temperature)) {
|
||||||
ESP_LOGW(TAG, "Invalid temperature");
|
ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values.");
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
float const pressure = this->read_pressure_(data, t_fine);
|
float const pressure = this->read_pressure_(data, t_fine);
|
||||||
float const humidity = this->read_humidity_(data, t_fine);
|
float const humidity = this->read_humidity_(data, t_fine);
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Temperature=%.1f°C Pressure=%.1fhPa Humidity=%.1f%%", temperature, pressure, humidity);
|
ESP_LOGV(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity);
|
||||||
if (this->temperature_sensor_ != nullptr)
|
if (this->temperature_sensor_ != nullptr)
|
||||||
this->temperature_sensor_->publish_state(temperature);
|
this->temperature_sensor_->publish_state(temperature);
|
||||||
if (this->pressure_sensor_ != nullptr)
|
if (this->pressure_sensor_ != nullptr)
|
||||||
|
@@ -28,7 +28,7 @@ const float BME680_GAS_LOOKUP_TABLE_1[16] PROGMEM = {0.0, 0.0, 0.0, 0.0, 0.0,
|
|||||||
const float BME680_GAS_LOOKUP_TABLE_2[16] PROGMEM = {0.0, 0.0, 0.0, 0.0, 0.1, 0.7, 0.0, -0.8,
|
const float BME680_GAS_LOOKUP_TABLE_2[16] PROGMEM = {0.0, 0.0, 0.0, 0.0, 0.1, 0.7, 0.0, -0.8,
|
||||||
-0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
|
-0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
|
||||||
|
|
||||||
[[maybe_unused]] static const char *oversampling_to_str(BME680Oversampling oversampling) {
|
static const char *oversampling_to_str(BME680Oversampling oversampling) {
|
||||||
switch (oversampling) {
|
switch (oversampling) {
|
||||||
case BME680_OVERSAMPLING_NONE:
|
case BME680_OVERSAMPLING_NONE:
|
||||||
return "None";
|
return "None";
|
||||||
@@ -47,7 +47,7 @@ const float BME680_GAS_LOOKUP_TABLE_2[16] PROGMEM = {0.0, 0.0, 0.0, 0.0, 0.1, 0
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[[maybe_unused]] static const char *iir_filter_to_str(BME680IIRFilter filter) {
|
static const char *iir_filter_to_str(BME680IIRFilter filter) {
|
||||||
switch (filter) {
|
switch (filter) {
|
||||||
case BME680_IIR_FILTER_OFF:
|
case BME680_IIR_FILTER_OFF:
|
||||||
return "OFF";
|
return "OFF";
|
||||||
|
@@ -2,8 +2,6 @@
|
|||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
#define BMP280_ERROR_WRONG_CHIP_ID "Wrong chip ID"
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace bmp280_base {
|
namespace bmp280_base {
|
||||||
|
|
||||||
@@ -65,23 +63,23 @@ void BMP280Component::setup() {
|
|||||||
// https://community.st.com/t5/stm32-mcus-products/issue-with-reading-bmp280-chip-id-using-spi/td-p/691855
|
// https://community.st.com/t5/stm32-mcus-products/issue-with-reading-bmp280-chip-id-using-spi/td-p/691855
|
||||||
if (!this->read_byte(0xD0, &chip_id)) {
|
if (!this->read_byte(0xD0, &chip_id)) {
|
||||||
this->error_code_ = COMMUNICATION_FAILED;
|
this->error_code_ = COMMUNICATION_FAILED;
|
||||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this->read_byte(0xD0, &chip_id)) {
|
if (!this->read_byte(0xD0, &chip_id)) {
|
||||||
this->error_code_ = COMMUNICATION_FAILED;
|
this->error_code_ = COMMUNICATION_FAILED;
|
||||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (chip_id != 0x58) {
|
if (chip_id != 0x58) {
|
||||||
this->error_code_ = WRONG_CHIP_ID;
|
this->error_code_ = WRONG_CHIP_ID;
|
||||||
this->mark_failed(BMP280_ERROR_WRONG_CHIP_ID);
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a soft reset.
|
// Send a soft reset.
|
||||||
if (!this->write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) {
|
if (!this->write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) {
|
||||||
this->mark_failed("Reset failed");
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Wait until the NVM data has finished loading.
|
// Wait until the NVM data has finished loading.
|
||||||
@@ -90,12 +88,14 @@ void BMP280Component::setup() {
|
|||||||
do {
|
do {
|
||||||
delay(2);
|
delay(2);
|
||||||
if (!this->read_byte(BMP280_REGISTER_STATUS, &status)) {
|
if (!this->read_byte(BMP280_REGISTER_STATUS, &status)) {
|
||||||
this->mark_failed("Error reading status register");
|
ESP_LOGW(TAG, "Error reading status register.");
|
||||||
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} while ((status & BMP280_STATUS_IM_UPDATE) && (--retry));
|
} while ((status & BMP280_STATUS_IM_UPDATE) && (--retry));
|
||||||
if (status & BMP280_STATUS_IM_UPDATE) {
|
if (status & BMP280_STATUS_IM_UPDATE) {
|
||||||
this->mark_failed("Timeout loading NVM");
|
ESP_LOGW(TAG, "Timeout loading NVM.");
|
||||||
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,14 +116,14 @@ void BMP280Component::setup() {
|
|||||||
|
|
||||||
uint8_t config_register = 0;
|
uint8_t config_register = 0;
|
||||||
if (!this->read_byte(BMP280_REGISTER_CONFIG, &config_register)) {
|
if (!this->read_byte(BMP280_REGISTER_CONFIG, &config_register)) {
|
||||||
this->mark_failed("Read config");
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
config_register &= ~0b11111100;
|
config_register &= ~0b11111100;
|
||||||
config_register |= 0b000 << 5; // 0.5 ms standby time
|
config_register |= 0b000 << 5; // 0.5 ms standby time
|
||||||
config_register |= (this->iir_filter_ & 0b111) << 2;
|
config_register |= (this->iir_filter_ & 0b111) << 2;
|
||||||
if (!this->write_byte(BMP280_REGISTER_CONFIG, config_register)) {
|
if (!this->write_byte(BMP280_REGISTER_CONFIG, config_register)) {
|
||||||
this->mark_failed("Write config");
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,7 +134,7 @@ void BMP280Component::dump_config() {
|
|||||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||||
break;
|
break;
|
||||||
case WRONG_CHIP_ID:
|
case WRONG_CHIP_ID:
|
||||||
ESP_LOGE(TAG, BMP280_ERROR_WRONG_CHIP_ID);
|
ESP_LOGE(TAG, "BMP280 has wrong chip ID! Is it a BME280?");
|
||||||
break;
|
break;
|
||||||
case NONE:
|
case NONE:
|
||||||
default:
|
default:
|
||||||
@@ -172,13 +172,13 @@ void BMP280Component::update() {
|
|||||||
int32_t t_fine = 0;
|
int32_t t_fine = 0;
|
||||||
float temperature = this->read_temperature_(&t_fine);
|
float temperature = this->read_temperature_(&t_fine);
|
||||||
if (std::isnan(temperature)) {
|
if (std::isnan(temperature)) {
|
||||||
ESP_LOGW(TAG, "Invalid temperature");
|
ESP_LOGW(TAG, "Invalid temperature, cannot read pressure values.");
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
float pressure = this->read_pressure_(t_fine);
|
float pressure = this->read_pressure_(t_fine);
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Temperature=%.1f°C Pressure=%.1fhPa", temperature, pressure);
|
ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa", temperature, pressure);
|
||||||
if (this->temperature_sensor_ != nullptr)
|
if (this->temperature_sensor_ != nullptr)
|
||||||
this->temperature_sensor_->publish_state(temperature);
|
this->temperature_sensor_->publish_state(temperature);
|
||||||
if (this->pressure_sensor_ != nullptr)
|
if (this->pressure_sensor_ != nullptr)
|
||||||
|
@@ -14,7 +14,7 @@ from esphome.core import CORE, coroutine_with_priority
|
|||||||
|
|
||||||
AUTO_LOAD = ["web_server_base", "ota.web_server"]
|
AUTO_LOAD = ["web_server_base", "ota.web_server"]
|
||||||
DEPENDENCIES = ["wifi"]
|
DEPENDENCIES = ["wifi"]
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@OttoWinter"]
|
||||||
|
|
||||||
captive_portal_ns = cg.esphome_ns.namespace("captive_portal")
|
captive_portal_ns = cg.esphome_ns.namespace("captive_portal")
|
||||||
CaptivePortal = captive_portal_ns.class_("CaptivePortal", cg.Component)
|
CaptivePortal = captive_portal_ns.class_("CaptivePortal", cg.Component)
|
||||||
|
@@ -99,39 +99,43 @@ const optional<float> &CoverCall::get_tilt() const { return this->tilt_; }
|
|||||||
const optional<bool> &CoverCall::get_toggle() const { return this->toggle_; }
|
const optional<bool> &CoverCall::get_toggle() const { return this->toggle_; }
|
||||||
void CoverCall::validate_() {
|
void CoverCall::validate_() {
|
||||||
auto traits = this->parent_->get_traits();
|
auto traits = this->parent_->get_traits();
|
||||||
const char *name = this->parent_->get_name().c_str();
|
|
||||||
|
|
||||||
if (this->position_.has_value()) {
|
if (this->position_.has_value()) {
|
||||||
auto pos = *this->position_;
|
auto pos = *this->position_;
|
||||||
if (!traits.get_supports_position() && pos != COVER_OPEN && pos != COVER_CLOSED) {
|
if (!traits.get_supports_position() && pos != COVER_OPEN && pos != COVER_CLOSED) {
|
||||||
ESP_LOGW(TAG, "'%s': position unsupported", name);
|
ESP_LOGW(TAG, "'%s' - This cover device does not support setting position!", this->parent_->get_name().c_str());
|
||||||
this->position_.reset();
|
this->position_.reset();
|
||||||
} else if (pos < 0.0f || pos > 1.0f) {
|
} else if (pos < 0.0f || pos > 1.0f) {
|
||||||
ESP_LOGW(TAG, "'%s': position %.2f out of range", name, pos);
|
ESP_LOGW(TAG, "'%s' - Position %.2f is out of range [0.0 - 1.0]", this->parent_->get_name().c_str(), pos);
|
||||||
this->position_ = clamp(pos, 0.0f, 1.0f);
|
this->position_ = clamp(pos, 0.0f, 1.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this->tilt_.has_value()) {
|
if (this->tilt_.has_value()) {
|
||||||
auto tilt = *this->tilt_;
|
auto tilt = *this->tilt_;
|
||||||
if (!traits.get_supports_tilt()) {
|
if (!traits.get_supports_tilt()) {
|
||||||
ESP_LOGW(TAG, "'%s': tilt unsupported", name);
|
ESP_LOGW(TAG, "'%s' - This cover device does not support tilt!", this->parent_->get_name().c_str());
|
||||||
this->tilt_.reset();
|
this->tilt_.reset();
|
||||||
} else if (tilt < 0.0f || tilt > 1.0f) {
|
} else if (tilt < 0.0f || tilt > 1.0f) {
|
||||||
ESP_LOGW(TAG, "'%s': tilt %.2f out of range", name, tilt);
|
ESP_LOGW(TAG, "'%s' - Tilt %.2f is out of range [0.0 - 1.0]", this->parent_->get_name().c_str(), tilt);
|
||||||
this->tilt_ = clamp(tilt, 0.0f, 1.0f);
|
this->tilt_ = clamp(tilt, 0.0f, 1.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this->toggle_.has_value()) {
|
if (this->toggle_.has_value()) {
|
||||||
if (!traits.get_supports_toggle()) {
|
if (!traits.get_supports_toggle()) {
|
||||||
ESP_LOGW(TAG, "'%s': toggle unsupported", name);
|
ESP_LOGW(TAG, "'%s' - This cover device does not support toggle!", this->parent_->get_name().c_str());
|
||||||
this->toggle_.reset();
|
this->toggle_.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this->stop_) {
|
if (this->stop_) {
|
||||||
if (this->position_.has_value() || this->tilt_.has_value() || this->toggle_.has_value()) {
|
if (this->position_.has_value()) {
|
||||||
ESP_LOGW(TAG, "'%s': cannot position/tilt/toggle when stopping", name);
|
ESP_LOGW(TAG, "Cannot set position when stopping a cover!");
|
||||||
this->position_.reset();
|
this->position_.reset();
|
||||||
|
}
|
||||||
|
if (this->tilt_.has_value()) {
|
||||||
|
ESP_LOGW(TAG, "Cannot set tilt when stopping a cover!");
|
||||||
this->tilt_.reset();
|
this->tilt_.reset();
|
||||||
|
}
|
||||||
|
if (this->toggle_.has_value()) {
|
||||||
|
ESP_LOGW(TAG, "Cannot set toggle when stopping a cover!");
|
||||||
this->toggle_.reset();
|
this->toggle_.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,7 @@ from esphome.const import (
|
|||||||
)
|
)
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@OttoWinter"]
|
||||||
DEPENDENCIES = ["logger"]
|
DEPENDENCIES = ["logger"]
|
||||||
|
|
||||||
CONF_DEBUG_ID = "debug_id"
|
CONF_DEBUG_ID = "debug_id"
|
||||||
@@ -48,15 +48,6 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
if CORE.using_zephyr:
|
if CORE.using_zephyr:
|
||||||
zephyr_add_prj_conf("HWINFO", True)
|
zephyr_add_prj_conf("HWINFO", True)
|
||||||
# gdb thread support
|
|
||||||
zephyr_add_prj_conf("DEBUG_THREAD_INFO", True)
|
|
||||||
# RTT
|
|
||||||
zephyr_add_prj_conf("USE_SEGGER_RTT", True)
|
|
||||||
zephyr_add_prj_conf("RTT_CONSOLE", True)
|
|
||||||
zephyr_add_prj_conf("LOG", True)
|
|
||||||
zephyr_add_prj_conf("LOG_BLOCK_IN_THREAD", True)
|
|
||||||
zephyr_add_prj_conf("LOG_BUFFER_SIZE", 4096)
|
|
||||||
zephyr_add_prj_conf("SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL", True)
|
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#include "driver/gpio.h"
|
|
||||||
#include "deep_sleep_component.h"
|
#include "deep_sleep_component.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
@@ -75,20 +74,11 @@ void DeepSleepComponent::deep_sleep_() {
|
|||||||
if (this->sleep_duration_.has_value())
|
if (this->sleep_duration_.has_value())
|
||||||
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
|
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
|
||||||
if (this->wakeup_pin_ != nullptr) {
|
if (this->wakeup_pin_ != nullptr) {
|
||||||
const auto gpio_pin = gpio_num_t(this->wakeup_pin_->get_pin());
|
|
||||||
if (this->wakeup_pin_->get_flags() & gpio::FLAG_PULLUP) {
|
|
||||||
gpio_sleep_set_pull_mode(gpio_pin, GPIO_PULLUP_ONLY);
|
|
||||||
} else if (this->wakeup_pin_->get_flags() & gpio::FLAG_PULLDOWN) {
|
|
||||||
gpio_sleep_set_pull_mode(gpio_pin, GPIO_PULLDOWN_ONLY);
|
|
||||||
}
|
|
||||||
gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT);
|
|
||||||
gpio_hold_en(gpio_pin);
|
|
||||||
gpio_deep_sleep_hold_en();
|
|
||||||
bool level = !this->wakeup_pin_->is_inverted();
|
bool level = !this->wakeup_pin_->is_inverted();
|
||||||
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
|
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
|
||||||
level = !level;
|
level = !level;
|
||||||
}
|
}
|
||||||
esp_sleep_enable_ext0_wakeup(gpio_pin, level);
|
esp_sleep_enable_ext0_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level);
|
||||||
}
|
}
|
||||||
if (this->ext1_wakeup_.has_value()) {
|
if (this->ext1_wakeup_.has_value()) {
|
||||||
esp_sleep_enable_ext1_wakeup(this->ext1_wakeup_->mask, this->ext1_wakeup_->wakeup_mode);
|
esp_sleep_enable_ext1_wakeup(this->ext1_wakeup_->mask, this->ext1_wakeup_->wakeup_mode);
|
||||||
@@ -112,15 +102,6 @@ void DeepSleepComponent::deep_sleep_() {
|
|||||||
if (this->sleep_duration_.has_value())
|
if (this->sleep_duration_.has_value())
|
||||||
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
|
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
|
||||||
if (this->wakeup_pin_ != nullptr) {
|
if (this->wakeup_pin_ != nullptr) {
|
||||||
const auto gpio_pin = gpio_num_t(this->wakeup_pin_->get_pin());
|
|
||||||
if (this->wakeup_pin_->get_flags() && gpio::FLAG_PULLUP) {
|
|
||||||
gpio_sleep_set_pull_mode(gpio_pin, GPIO_PULLUP_ONLY);
|
|
||||||
} else if (this->wakeup_pin_->get_flags() && gpio::FLAG_PULLDOWN) {
|
|
||||||
gpio_sleep_set_pull_mode(gpio_pin, GPIO_PULLDOWN_ONLY);
|
|
||||||
}
|
|
||||||
gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT);
|
|
||||||
gpio_hold_en(gpio_pin);
|
|
||||||
gpio_deep_sleep_hold_en();
|
|
||||||
bool level = !this->wakeup_pin_->is_inverted();
|
bool level = !this->wakeup_pin_->is_inverted();
|
||||||
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
|
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
|
||||||
level = !level;
|
level = !level;
|
||||||
|
@@ -12,8 +12,6 @@ from esphome.const import (
|
|||||||
CONF_ROTATION,
|
CONF_ROTATION,
|
||||||
CONF_TO,
|
CONF_TO,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_UPDATE_INTERVAL,
|
|
||||||
SCHEDULER_DONT_RUN,
|
|
||||||
)
|
)
|
||||||
from esphome.core import coroutine_with_priority
|
from esphome.core import coroutine_with_priority
|
||||||
|
|
||||||
@@ -69,18 +67,6 @@ BASIC_DISPLAY_SCHEMA = cv.Schema(
|
|||||||
}
|
}
|
||||||
).extend(cv.polling_component_schema("1s"))
|
).extend(cv.polling_component_schema("1s"))
|
||||||
|
|
||||||
|
|
||||||
def _validate_test_card(config):
|
|
||||||
if (
|
|
||||||
config.get(CONF_SHOW_TEST_CARD, False)
|
|
||||||
and config.get(CONF_UPDATE_INTERVAL, False) == SCHEDULER_DONT_RUN
|
|
||||||
):
|
|
||||||
raise cv.Invalid(
|
|
||||||
f"`{CONF_SHOW_TEST_CARD}: True` cannot be used with `{CONF_UPDATE_INTERVAL}: never` because this combination will not show a test_card."
|
|
||||||
)
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend(
|
FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
cv.Optional(CONF_ROTATION): validate_rotation,
|
cv.Optional(CONF_ROTATION): validate_rotation,
|
||||||
@@ -108,7 +94,6 @@ FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend(
|
|||||||
cv.Optional(CONF_SHOW_TEST_CARD): cv.boolean,
|
cv.Optional(CONF_SHOW_TEST_CARD): cv.boolean,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
FULL_DISPLAY_SCHEMA.add_extra(_validate_test_card)
|
|
||||||
|
|
||||||
|
|
||||||
async def setup_display_core_(var, config):
|
async def setup_display_core_(var, config):
|
||||||
@@ -215,6 +200,7 @@ async def display_is_displaying_page_to_code(config, condition_id, template_arg,
|
|||||||
page = await cg.get_variable(config[CONF_PAGE_ID])
|
page = await cg.get_variable(config[CONF_PAGE_ID])
|
||||||
var = cg.new_Pvariable(condition_id, template_arg, paren)
|
var = cg.new_Pvariable(condition_id, template_arg, paren)
|
||||||
cg.add(var.set_page(page))
|
cg.add(var.set_page(page))
|
||||||
|
|
||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
@@ -15,7 +15,6 @@ from esphome.const import (
|
|||||||
CONF_FRAMEWORK,
|
CONF_FRAMEWORK,
|
||||||
CONF_IGNORE_EFUSE_CUSTOM_MAC,
|
CONF_IGNORE_EFUSE_CUSTOM_MAC,
|
||||||
CONF_IGNORE_EFUSE_MAC_CRC,
|
CONF_IGNORE_EFUSE_MAC_CRC,
|
||||||
CONF_LOG_LEVEL,
|
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
CONF_PATH,
|
CONF_PATH,
|
||||||
CONF_PLATFORM_VERSION,
|
CONF_PLATFORM_VERSION,
|
||||||
@@ -80,15 +79,6 @@ CONF_ENABLE_LWIP_ASSERT = "enable_lwip_assert"
|
|||||||
CONF_EXECUTE_FROM_PSRAM = "execute_from_psram"
|
CONF_EXECUTE_FROM_PSRAM = "execute_from_psram"
|
||||||
CONF_RELEASE = "release"
|
CONF_RELEASE = "release"
|
||||||
|
|
||||||
LOG_LEVELS_IDF = [
|
|
||||||
"NONE",
|
|
||||||
"ERROR",
|
|
||||||
"WARN",
|
|
||||||
"INFO",
|
|
||||||
"DEBUG",
|
|
||||||
"VERBOSE",
|
|
||||||
]
|
|
||||||
|
|
||||||
ASSERTION_LEVELS = {
|
ASSERTION_LEVELS = {
|
||||||
"DISABLE": "CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE",
|
"DISABLE": "CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE",
|
||||||
"ENABLE": "CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE",
|
"ENABLE": "CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE",
|
||||||
@@ -633,9 +623,6 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
|
|||||||
cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): {
|
cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): {
|
||||||
cv.string_strict: cv.string_strict
|
cv.string_strict: cv.string_strict
|
||||||
},
|
},
|
||||||
cv.Optional(CONF_LOG_LEVEL, default="ERROR"): cv.one_of(
|
|
||||||
*LOG_LEVELS_IDF, upper=True
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_ADVANCED, default={}): cv.Schema(
|
cv.Optional(CONF_ADVANCED, default={}): cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Optional(CONF_ASSERTION_LEVEL): cv.one_of(
|
cv.Optional(CONF_ASSERTION_LEVEL): cv.one_of(
|
||||||
@@ -693,64 +680,6 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class _FrameworkMigrationWarning:
|
|
||||||
shown = False
|
|
||||||
|
|
||||||
|
|
||||||
def _show_framework_migration_message(name: str, variant: str) -> None:
|
|
||||||
"""Show a friendly message about framework migration when defaulting to Arduino."""
|
|
||||||
if _FrameworkMigrationWarning.shown:
|
|
||||||
return
|
|
||||||
_FrameworkMigrationWarning.shown = True
|
|
||||||
|
|
||||||
from esphome.log import AnsiFore, color
|
|
||||||
|
|
||||||
message = (
|
|
||||||
color(
|
|
||||||
AnsiFore.BOLD_CYAN,
|
|
||||||
f"💡 IMPORTANT: {name} doesn't have a framework specified!",
|
|
||||||
)
|
|
||||||
+ "\n\n"
|
|
||||||
+ f"Currently, {variant} defaults to the Arduino framework.\n"
|
|
||||||
+ color(AnsiFore.YELLOW, "This will change to ESP-IDF in ESPHome 2026.1.0.\n")
|
|
||||||
+ "\n"
|
|
||||||
+ "Note: Newer ESP32 variants (C6, H2, P4, etc.) already use ESP-IDF by default.\n"
|
|
||||||
+ "\n"
|
|
||||||
+ "Why change? ESP-IDF offers:\n"
|
|
||||||
+ color(AnsiFore.GREEN, " ✨ Up to 40% smaller binaries\n")
|
|
||||||
+ color(AnsiFore.GREEN, " 🚀 Better performance and optimization\n")
|
|
||||||
+ color(AnsiFore.GREEN, " 📦 Custom-built firmware for your exact needs\n")
|
|
||||||
+ color(
|
|
||||||
AnsiFore.GREEN,
|
|
||||||
" 🔧 Active development and testing by ESPHome developers\n",
|
|
||||||
)
|
|
||||||
+ "\n"
|
|
||||||
+ "Trade-offs:\n"
|
|
||||||
+ color(AnsiFore.YELLOW, " ⏱️ Compile times are ~25% longer\n")
|
|
||||||
+ color(AnsiFore.YELLOW, " 🔄 Some components need migration\n")
|
|
||||||
+ "\n"
|
|
||||||
+ "What should I do?\n"
|
|
||||||
+ color(AnsiFore.CYAN, " Option 1")
|
|
||||||
+ ": Migrate to ESP-IDF (recommended)\n"
|
|
||||||
+ " Add this to your YAML under 'esp32:':\n"
|
|
||||||
+ color(AnsiFore.WHITE, " framework:\n")
|
|
||||||
+ color(AnsiFore.WHITE, " type: esp-idf\n")
|
|
||||||
+ "\n"
|
|
||||||
+ color(AnsiFore.CYAN, " Option 2")
|
|
||||||
+ ": Keep using Arduino (still supported)\n"
|
|
||||||
+ " Add this to your YAML under 'esp32:':\n"
|
|
||||||
+ color(AnsiFore.WHITE, " framework:\n")
|
|
||||||
+ color(AnsiFore.WHITE, " type: arduino\n")
|
|
||||||
+ "\n"
|
|
||||||
+ "Need help? Check out the migration guide:\n"
|
|
||||||
+ color(
|
|
||||||
AnsiFore.BLUE,
|
|
||||||
"https://esphome.io/guides/esp32_arduino_to_idf.html",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
_LOGGER.warning(message)
|
|
||||||
|
|
||||||
|
|
||||||
def _set_default_framework(config):
|
def _set_default_framework(config):
|
||||||
if CONF_FRAMEWORK not in config:
|
if CONF_FRAMEWORK not in config:
|
||||||
config = config.copy()
|
config = config.copy()
|
||||||
@@ -759,10 +688,6 @@ def _set_default_framework(config):
|
|||||||
if variant in ARDUINO_ALLOWED_VARIANTS:
|
if variant in ARDUINO_ALLOWED_VARIANTS:
|
||||||
config[CONF_FRAMEWORK] = ARDUINO_FRAMEWORK_SCHEMA({})
|
config[CONF_FRAMEWORK] = ARDUINO_FRAMEWORK_SCHEMA({})
|
||||||
config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ARDUINO
|
config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ARDUINO
|
||||||
# Show the migration message
|
|
||||||
_show_framework_migration_message(
|
|
||||||
config.get(CONF_NAME, "This device"), variant
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
config[CONF_FRAMEWORK] = ESP_IDF_FRAMEWORK_SCHEMA({})
|
config[CONF_FRAMEWORK] = ESP_IDF_FRAMEWORK_SCHEMA({})
|
||||||
config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ESP_IDF
|
config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ESP_IDF
|
||||||
@@ -950,10 +875,6 @@ async def to_code(config):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
add_idf_sdkconfig_option(
|
|
||||||
f"CONFIG_LOG_DEFAULT_LEVEL_{conf[CONF_LOG_LEVEL]}", True
|
|
||||||
)
|
|
||||||
|
|
||||||
for name, value in conf[CONF_SDKCONFIG_OPTIONS].items():
|
for name, value in conf[CONF_SDKCONFIG_OPTIONS].items():
|
||||||
add_idf_sdkconfig_option(name, RawSdkconfigValue(value))
|
add_idf_sdkconfig_option(name, RawSdkconfigValue(value))
|
||||||
|
|
||||||
|
@@ -93,8 +93,8 @@ def merge_factory_bin(source, target, env):
|
|||||||
"esptool",
|
"esptool",
|
||||||
"--chip",
|
"--chip",
|
||||||
chip,
|
chip,
|
||||||
"merge-bin",
|
"merge_bin",
|
||||||
"--flash-size",
|
"--flash_size",
|
||||||
flash_size,
|
flash_size,
|
||||||
"--output",
|
"--output",
|
||||||
str(output_path),
|
str(output_path),
|
||||||
@@ -110,7 +110,7 @@ def merge_factory_bin(source, target, env):
|
|||||||
if result == 0:
|
if result == 0:
|
||||||
print(f"Successfully created {output_path}")
|
print(f"Successfully created {output_path}")
|
||||||
else:
|
else:
|
||||||
print(f"Error: esptool merge-bin failed with code {result}")
|
print(f"Error: esptool merge_bin failed with code {result}")
|
||||||
|
|
||||||
|
|
||||||
def esp32_copy_ota_bin(source, target, env):
|
def esp32_copy_ota_bin(source, target, env):
|
||||||
|
@@ -6,13 +6,12 @@ import esphome.codegen as cg
|
|||||||
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
|
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ESPHOME, CONF_ID, CONF_NAME
|
from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ESPHOME, CONF_ID, CONF_NAME
|
||||||
from esphome.core import CORE, TimePeriod
|
from esphome.core import CORE
|
||||||
from esphome.core.config import CONF_NAME_ADD_MAC_SUFFIX
|
from esphome.core.config import CONF_NAME_ADD_MAC_SUFFIX
|
||||||
import esphome.final_validate as fv
|
import esphome.final_validate as fv
|
||||||
|
|
||||||
DEPENDENCIES = ["esp32"]
|
DEPENDENCIES = ["esp32"]
|
||||||
CODEOWNERS = ["@jesserockz", "@Rapsssito", "@bdraco"]
|
CODEOWNERS = ["@jesserockz", "@Rapsssito"]
|
||||||
DOMAIN = "esp32_ble"
|
|
||||||
|
|
||||||
|
|
||||||
class BTLoggers(Enum):
|
class BTLoggers(Enum):
|
||||||
@@ -116,11 +115,8 @@ def register_bt_logger(*loggers: BTLoggers) -> None:
|
|||||||
|
|
||||||
CONF_BLE_ID = "ble_id"
|
CONF_BLE_ID = "ble_id"
|
||||||
CONF_IO_CAPABILITY = "io_capability"
|
CONF_IO_CAPABILITY = "io_capability"
|
||||||
CONF_ADVERTISING = "advertising"
|
|
||||||
CONF_ADVERTISING_CYCLE_TIME = "advertising_cycle_time"
|
CONF_ADVERTISING_CYCLE_TIME = "advertising_cycle_time"
|
||||||
CONF_DISABLE_BT_LOGS = "disable_bt_logs"
|
CONF_DISABLE_BT_LOGS = "disable_bt_logs"
|
||||||
CONF_CONNECTION_TIMEOUT = "connection_timeout"
|
|
||||||
CONF_MAX_NOTIFICATIONS = "max_notifications"
|
|
||||||
|
|
||||||
NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2]
|
NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2]
|
||||||
|
|
||||||
@@ -165,23 +161,12 @@ CONFIG_SCHEMA = cv.Schema(
|
|||||||
IO_CAPABILITY, lower=True
|
IO_CAPABILITY, lower=True
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean,
|
cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean,
|
||||||
cv.Optional(CONF_ADVERTISING, default=False): cv.boolean,
|
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_ADVERTISING_CYCLE_TIME, default="10s"
|
CONF_ADVERTISING_CYCLE_TIME, default="10s"
|
||||||
): cv.positive_time_period_milliseconds,
|
): cv.positive_time_period_milliseconds,
|
||||||
cv.SplitDefault(CONF_DISABLE_BT_LOGS, esp32_idf=True): cv.All(
|
cv.SplitDefault(CONF_DISABLE_BT_LOGS, esp32_idf=True): cv.All(
|
||||||
cv.only_with_esp_idf, cv.boolean
|
cv.only_with_esp_idf, cv.boolean
|
||||||
),
|
),
|
||||||
cv.SplitDefault(CONF_CONNECTION_TIMEOUT, esp32_idf="20s"): cv.All(
|
|
||||||
cv.only_with_esp_idf,
|
|
||||||
cv.positive_time_period_seconds,
|
|
||||||
cv.Range(min=TimePeriod(seconds=10), max=TimePeriod(seconds=180)),
|
|
||||||
),
|
|
||||||
cv.SplitDefault(CONF_MAX_NOTIFICATIONS, esp32_idf=12): cv.All(
|
|
||||||
cv.only_with_esp_idf,
|
|
||||||
cv.positive_int,
|
|
||||||
cv.Range(min=1, max=64),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA)
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
@@ -270,32 +255,8 @@ async def to_code(config):
|
|||||||
if logger not in _required_loggers:
|
if logger not in _required_loggers:
|
||||||
add_idf_sdkconfig_option(f"{logger.value}_NONE", True)
|
add_idf_sdkconfig_option(f"{logger.value}_NONE", True)
|
||||||
|
|
||||||
# Set BLE connection establishment timeout to match aioesphomeapi/bleak-retry-connector
|
|
||||||
# Default is 20 seconds instead of ESP-IDF's 30 seconds. Because there is no way to
|
|
||||||
# cancel a BLE connection in progress, when aioesphomeapi times out at 20 seconds,
|
|
||||||
# the connection slot remains occupied for the remaining time, preventing new connection
|
|
||||||
# attempts and wasting valuable connection slots.
|
|
||||||
if CONF_CONNECTION_TIMEOUT in config:
|
|
||||||
timeout_seconds = int(config[CONF_CONNECTION_TIMEOUT].total_seconds)
|
|
||||||
add_idf_sdkconfig_option(
|
|
||||||
"CONFIG_BT_BLE_ESTAB_LINK_CONN_TOUT", timeout_seconds
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set the maximum number of notification registrations
|
|
||||||
# This controls how many BLE characteristics can have notifications enabled
|
|
||||||
# across all connections for a single GATT client interface
|
|
||||||
# https://github.com/esphome/issues/issues/6808
|
|
||||||
if CONF_MAX_NOTIFICATIONS in config:
|
|
||||||
add_idf_sdkconfig_option(
|
|
||||||
"CONFIG_BT_GATTC_NOTIF_REG_MAX", config[CONF_MAX_NOTIFICATIONS]
|
|
||||||
)
|
|
||||||
|
|
||||||
cg.add_define("USE_ESP32_BLE")
|
cg.add_define("USE_ESP32_BLE")
|
||||||
|
|
||||||
if config[CONF_ADVERTISING]:
|
|
||||||
cg.add_define("USE_ESP32_BLE_ADVERTISING")
|
|
||||||
cg.add_define("USE_ESP32_BLE_UUID")
|
|
||||||
|
|
||||||
|
|
||||||
@automation.register_condition("ble.enabled", BLEEnabledCondition, cv.Schema({}))
|
@automation.register_condition("ble.enabled", BLEEnabledCondition, cv.Schema({}))
|
||||||
async def ble_enabled_to_code(config, condition_id, template_arg, args):
|
async def ble_enabled_to_code(config, condition_id, template_arg, args):
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
#include "ble.h"
|
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
|
#include "ble.h"
|
||||||
|
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
@@ -53,7 +53,6 @@ void ESP32BLE::disable() {
|
|||||||
|
|
||||||
bool ESP32BLE::is_active() { return this->state_ == BLE_COMPONENT_STATE_ACTIVE; }
|
bool ESP32BLE::is_active() { return this->state_ == BLE_COMPONENT_STATE_ACTIVE; }
|
||||||
|
|
||||||
#ifdef USE_ESP32_BLE_ADVERTISING
|
|
||||||
void ESP32BLE::advertising_start() {
|
void ESP32BLE::advertising_start() {
|
||||||
this->advertising_init_();
|
this->advertising_init_();
|
||||||
if (!this->is_active())
|
if (!this->is_active())
|
||||||
@@ -89,7 +88,6 @@ void ESP32BLE::advertising_remove_service_uuid(ESPBTUUID uuid) {
|
|||||||
this->advertising_->remove_service_uuid(uuid);
|
this->advertising_->remove_service_uuid(uuid);
|
||||||
this->advertising_start();
|
this->advertising_start();
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
bool ESP32BLE::ble_pre_setup_() {
|
bool ESP32BLE::ble_pre_setup_() {
|
||||||
esp_err_t err = nvs_flash_init();
|
esp_err_t err = nvs_flash_init();
|
||||||
@@ -100,7 +98,6 @@ bool ESP32BLE::ble_pre_setup_() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_ESP32_BLE_ADVERTISING
|
|
||||||
void ESP32BLE::advertising_init_() {
|
void ESP32BLE::advertising_init_() {
|
||||||
if (this->advertising_ != nullptr)
|
if (this->advertising_ != nullptr)
|
||||||
return;
|
return;
|
||||||
@@ -110,7 +107,6 @@ void ESP32BLE::advertising_init_() {
|
|||||||
this->advertising_->set_min_preferred_interval(0x06);
|
this->advertising_->set_min_preferred_interval(0x06);
|
||||||
this->advertising_->set_appearance(this->appearance_);
|
this->advertising_->set_appearance(this->appearance_);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
bool ESP32BLE::ble_setup_() {
|
bool ESP32BLE::ble_setup_() {
|
||||||
esp_err_t err;
|
esp_err_t err;
|
||||||
@@ -398,11 +394,9 @@ void ESP32BLE::loop() {
|
|||||||
this->ble_event_pool_.release(ble_event);
|
this->ble_event_pool_.release(ble_event);
|
||||||
ble_event = this->ble_events_.pop();
|
ble_event = this->ble_events_.pop();
|
||||||
}
|
}
|
||||||
#ifdef USE_ESP32_BLE_ADVERTISING
|
|
||||||
if (this->advertising_ != nullptr) {
|
if (this->advertising_ != nullptr) {
|
||||||
this->advertising_->loop();
|
this->advertising_->loop();
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
// Log dropped events periodically
|
// Log dropped events periodically
|
||||||
uint16_t dropped = this->ble_events_.get_and_reset_dropped_count();
|
uint16_t dropped = this->ble_events_.get_and_reset_dropped_count();
|
||||||
|
@@ -1,17 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/core/defines.h" // Must be included before conditional includes
|
#include "ble_advertising.h"
|
||||||
|
|
||||||
#include "ble_uuid.h"
|
#include "ble_uuid.h"
|
||||||
#include "ble_scan_result.h"
|
#include "ble_scan_result.h"
|
||||||
#ifdef USE_ESP32_BLE_ADVERTISING
|
|
||||||
#include "ble_advertising.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
#include "ble_event.h"
|
#include "ble_event.h"
|
||||||
@@ -26,14 +23,21 @@
|
|||||||
|
|
||||||
namespace esphome::esp32_ble {
|
namespace esphome::esp32_ble {
|
||||||
|
|
||||||
// Maximum size of the BLE event queue
|
// Maximum number of BLE scan results to buffer
|
||||||
// Increased to absorb the ring buffer capacity from esp32_ble_tracker
|
// Sized to handle bursts of advertisements while allowing for processing delays
|
||||||
|
// With 16 advertisements per batch and some safety margin:
|
||||||
|
// - Without PSRAM: 24 entries (1.5× batch size)
|
||||||
|
// - With PSRAM: 36 entries (2.25× batch size)
|
||||||
|
// The reduced structure size (~80 bytes vs ~400 bytes) allows for larger buffers
|
||||||
#ifdef USE_PSRAM
|
#ifdef USE_PSRAM
|
||||||
static constexpr uint8_t MAX_BLE_QUEUE_SIZE = 100; // 64 + 36 (ring buffer size with PSRAM)
|
static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 36;
|
||||||
#else
|
#else
|
||||||
static constexpr uint8_t MAX_BLE_QUEUE_SIZE = 88; // 64 + 24 (ring buffer size without PSRAM)
|
static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 24;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Maximum size of the BLE event queue - must be power of 2 for lock-free queue
|
||||||
|
static constexpr size_t MAX_BLE_QUEUE_SIZE = 64;
|
||||||
|
|
||||||
uint64_t ble_addr_to_uint64(const esp_bd_addr_t address);
|
uint64_t ble_addr_to_uint64(const esp_bd_addr_t address);
|
||||||
|
|
||||||
// NOLINTNEXTLINE(modernize-use-using)
|
// NOLINTNEXTLINE(modernize-use-using)
|
||||||
@@ -109,7 +113,6 @@ class ESP32BLE : public Component {
|
|||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override;
|
||||||
void set_name(const std::string &name) { this->name_ = name; }
|
void set_name(const std::string &name) { this->name_ = name; }
|
||||||
|
|
||||||
#ifdef USE_ESP32_BLE_ADVERTISING
|
|
||||||
void advertising_start();
|
void advertising_start();
|
||||||
void advertising_set_service_data(const std::vector<uint8_t> &data);
|
void advertising_set_service_data(const std::vector<uint8_t> &data);
|
||||||
void advertising_set_manufacturer_data(const std::vector<uint8_t> &data);
|
void advertising_set_manufacturer_data(const std::vector<uint8_t> &data);
|
||||||
@@ -117,7 +120,6 @@ class ESP32BLE : public Component {
|
|||||||
void advertising_add_service_uuid(ESPBTUUID uuid);
|
void advertising_add_service_uuid(ESPBTUUID uuid);
|
||||||
void advertising_remove_service_uuid(ESPBTUUID uuid);
|
void advertising_remove_service_uuid(ESPBTUUID uuid);
|
||||||
void advertising_register_raw_advertisement_callback(std::function<void(bool)> &&callback);
|
void advertising_register_raw_advertisement_callback(std::function<void(bool)> &&callback);
|
||||||
#endif
|
|
||||||
|
|
||||||
void register_gap_event_handler(GAPEventHandler *handler) { this->gap_event_handlers_.push_back(handler); }
|
void register_gap_event_handler(GAPEventHandler *handler) { this->gap_event_handlers_.push_back(handler); }
|
||||||
void register_gap_scan_event_handler(GAPScanEventHandler *handler) {
|
void register_gap_scan_event_handler(GAPScanEventHandler *handler) {
|
||||||
@@ -138,9 +140,7 @@ class ESP32BLE : public Component {
|
|||||||
bool ble_setup_();
|
bool ble_setup_();
|
||||||
bool ble_dismantle_();
|
bool ble_dismantle_();
|
||||||
bool ble_pre_setup_();
|
bool ble_pre_setup_();
|
||||||
#ifdef USE_ESP32_BLE_ADVERTISING
|
|
||||||
void advertising_init_();
|
void advertising_init_();
|
||||||
#endif
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template<typename... Args> friend void enqueue_ble_event(Args... args);
|
template<typename... Args> friend void enqueue_ble_event(Args... args);
|
||||||
@@ -160,9 +160,7 @@ class ESP32BLE : public Component {
|
|||||||
optional<std::string> name_;
|
optional<std::string> name_;
|
||||||
|
|
||||||
// 4-byte aligned members
|
// 4-byte aligned members
|
||||||
#ifdef USE_ESP32_BLE_ADVERTISING
|
BLEAdvertising *advertising_{}; // 4 bytes (pointer)
|
||||||
BLEAdvertising *advertising_{}; // 4 bytes (pointer)
|
|
||||||
#endif
|
|
||||||
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; // 4 bytes (enum)
|
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; // 4 bytes (enum)
|
||||||
uint32_t advertising_cycle_time_{}; // 4 bytes
|
uint32_t advertising_cycle_time_{}; // 4 bytes
|
||||||
|
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
#include "ble_advertising.h"
|
#include "ble_advertising.h"
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#ifdef USE_ESP32_BLE_ADVERTISING
|
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -162,5 +161,4 @@ void BLEAdvertising::register_raw_advertisement_callback(std::function<void(bool
|
|||||||
|
|
||||||
} // namespace esphome::esp32_ble
|
} // namespace esphome::esp32_ble
|
||||||
|
|
||||||
#endif // USE_ESP32_BLE_ADVERTISING
|
#endif
|
||||||
#endif // USE_ESP32
|
|
||||||
|
@@ -1,13 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#ifdef USE_ESP32_BLE_ADVERTISING
|
|
||||||
|
|
||||||
#include <esp_bt.h>
|
#include <esp_bt.h>
|
||||||
#include <esp_gap_ble_api.h>
|
#include <esp_gap_ble_api.h>
|
||||||
@@ -59,5 +56,4 @@ class BLEAdvertising {
|
|||||||
|
|
||||||
} // namespace esphome::esp32_ble
|
} // namespace esphome::esp32_ble
|
||||||
|
|
||||||
#endif // USE_ESP32_BLE_ADVERTISING
|
#endif
|
||||||
#endif // USE_ESP32
|
|
||||||
|
@@ -3,7 +3,8 @@
|
|||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
#include <cstddef> // for offsetof
|
#include <cstddef> // for offsetof
|
||||||
#include <cstring> // for memcpy
|
#include <vector>
|
||||||
|
|
||||||
#include <esp_gap_ble_api.h>
|
#include <esp_gap_ble_api.h>
|
||||||
#include <esp_gattc_api.h>
|
#include <esp_gattc_api.h>
|
||||||
#include <esp_gatts_api.h>
|
#include <esp_gatts_api.h>
|
||||||
@@ -63,7 +64,7 @@ static_assert(offsetof(esp_ble_gap_cb_param_t, read_rssi_cmpl.remote_addr) == si
|
|||||||
|
|
||||||
// Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop().
|
// Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop().
|
||||||
// This class stores each event with minimal memory usage.
|
// This class stores each event with minimal memory usage.
|
||||||
// GAP events (99% of traffic) don't have the heap allocation overhead.
|
// GAP events (99% of traffic) don't have the vector overhead.
|
||||||
// GATTC/GATTS events use heap allocation for their param and data.
|
// GATTC/GATTS events use heap allocation for their param and data.
|
||||||
//
|
//
|
||||||
// Event flow:
|
// Event flow:
|
||||||
@@ -144,18 +145,16 @@ class BLEEvent {
|
|||||||
}
|
}
|
||||||
if (this->type_ == GATTC) {
|
if (this->type_ == GATTC) {
|
||||||
delete this->event_.gattc.gattc_param;
|
delete this->event_.gattc.gattc_param;
|
||||||
delete[] this->event_.gattc.data;
|
delete this->event_.gattc.data;
|
||||||
this->event_.gattc.gattc_param = nullptr;
|
this->event_.gattc.gattc_param = nullptr;
|
||||||
this->event_.gattc.data = nullptr;
|
this->event_.gattc.data = nullptr;
|
||||||
this->event_.gattc.data_len = 0;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this->type_ == GATTS) {
|
if (this->type_ == GATTS) {
|
||||||
delete this->event_.gatts.gatts_param;
|
delete this->event_.gatts.gatts_param;
|
||||||
delete[] this->event_.gatts.data;
|
delete this->event_.gatts.data;
|
||||||
this->event_.gatts.gatts_param = nullptr;
|
this->event_.gatts.gatts_param = nullptr;
|
||||||
this->event_.gatts.data = nullptr;
|
this->event_.gatts.data = nullptr;
|
||||||
this->event_.gatts.data_len = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,19 +209,17 @@ class BLEEvent {
|
|||||||
esp_gattc_cb_event_t gattc_event;
|
esp_gattc_cb_event_t gattc_event;
|
||||||
esp_gatt_if_t gattc_if;
|
esp_gatt_if_t gattc_if;
|
||||||
esp_ble_gattc_cb_param_t *gattc_param; // Heap-allocated
|
esp_ble_gattc_cb_param_t *gattc_param; // Heap-allocated
|
||||||
uint8_t *data; // Heap-allocated raw buffer (manually managed)
|
std::vector<uint8_t> *data; // Heap-allocated
|
||||||
uint16_t data_len; // Track size separately
|
} gattc; // 16 bytes (pointers only)
|
||||||
} gattc;
|
|
||||||
|
|
||||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||||
struct gatts_event {
|
struct gatts_event {
|
||||||
esp_gatts_cb_event_t gatts_event;
|
esp_gatts_cb_event_t gatts_event;
|
||||||
esp_gatt_if_t gatts_if;
|
esp_gatt_if_t gatts_if;
|
||||||
esp_ble_gatts_cb_param_t *gatts_param; // Heap-allocated
|
esp_ble_gatts_cb_param_t *gatts_param; // Heap-allocated
|
||||||
uint8_t *data; // Heap-allocated raw buffer (manually managed)
|
std::vector<uint8_t> *data; // Heap-allocated
|
||||||
uint16_t data_len; // Track size separately
|
} gatts; // 16 bytes (pointers only)
|
||||||
} gatts;
|
} event_; // 80 bytes
|
||||||
} event_; // 80 bytes
|
|
||||||
|
|
||||||
ble_event_t type_;
|
ble_event_t type_;
|
||||||
|
|
||||||
@@ -322,7 +319,6 @@ class BLEEvent {
|
|||||||
if (p == nullptr) {
|
if (p == nullptr) {
|
||||||
this->event_.gattc.gattc_param = nullptr;
|
this->event_.gattc.gattc_param = nullptr;
|
||||||
this->event_.gattc.data = nullptr;
|
this->event_.gattc.data = nullptr;
|
||||||
this->event_.gattc.data_len = 0;
|
|
||||||
return; // Invalid event, but we can't log in header file
|
return; // Invalid event, but we can't log in header file
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,29 +336,16 @@ class BLEEvent {
|
|||||||
// We must copy this data to ensure it remains valid when the event is processed later.
|
// We must copy this data to ensure it remains valid when the event is processed later.
|
||||||
switch (e) {
|
switch (e) {
|
||||||
case ESP_GATTC_NOTIFY_EVT:
|
case ESP_GATTC_NOTIFY_EVT:
|
||||||
this->event_.gattc.data_len = p->notify.value_len;
|
this->event_.gattc.data = new std::vector<uint8_t>(p->notify.value, p->notify.value + p->notify.value_len);
|
||||||
if (p->notify.value_len > 0) {
|
this->event_.gattc.gattc_param->notify.value = this->event_.gattc.data->data();
|
||||||
this->event_.gattc.data = new uint8_t[p->notify.value_len];
|
|
||||||
memcpy(this->event_.gattc.data, p->notify.value, p->notify.value_len);
|
|
||||||
} else {
|
|
||||||
this->event_.gattc.data = nullptr;
|
|
||||||
}
|
|
||||||
this->event_.gattc.gattc_param->notify.value = this->event_.gattc.data;
|
|
||||||
break;
|
break;
|
||||||
case ESP_GATTC_READ_CHAR_EVT:
|
case ESP_GATTC_READ_CHAR_EVT:
|
||||||
case ESP_GATTC_READ_DESCR_EVT:
|
case ESP_GATTC_READ_DESCR_EVT:
|
||||||
this->event_.gattc.data_len = p->read.value_len;
|
this->event_.gattc.data = new std::vector<uint8_t>(p->read.value, p->read.value + p->read.value_len);
|
||||||
if (p->read.value_len > 0) {
|
this->event_.gattc.gattc_param->read.value = this->event_.gattc.data->data();
|
||||||
this->event_.gattc.data = new uint8_t[p->read.value_len];
|
|
||||||
memcpy(this->event_.gattc.data, p->read.value, p->read.value_len);
|
|
||||||
} else {
|
|
||||||
this->event_.gattc.data = nullptr;
|
|
||||||
}
|
|
||||||
this->event_.gattc.gattc_param->read.value = this->event_.gattc.data;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this->event_.gattc.data = nullptr;
|
this->event_.gattc.data = nullptr;
|
||||||
this->event_.gattc.data_len = 0;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -375,7 +358,6 @@ class BLEEvent {
|
|||||||
if (p == nullptr) {
|
if (p == nullptr) {
|
||||||
this->event_.gatts.gatts_param = nullptr;
|
this->event_.gatts.gatts_param = nullptr;
|
||||||
this->event_.gatts.data = nullptr;
|
this->event_.gatts.data = nullptr;
|
||||||
this->event_.gatts.data_len = 0;
|
|
||||||
return; // Invalid event, but we can't log in header file
|
return; // Invalid event, but we can't log in header file
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,18 +375,11 @@ class BLEEvent {
|
|||||||
// We must copy this data to ensure it remains valid when the event is processed later.
|
// We must copy this data to ensure it remains valid when the event is processed later.
|
||||||
switch (e) {
|
switch (e) {
|
||||||
case ESP_GATTS_WRITE_EVT:
|
case ESP_GATTS_WRITE_EVT:
|
||||||
this->event_.gatts.data_len = p->write.len;
|
this->event_.gatts.data = new std::vector<uint8_t>(p->write.value, p->write.value + p->write.len);
|
||||||
if (p->write.len > 0) {
|
this->event_.gatts.gatts_param->write.value = this->event_.gatts.data->data();
|
||||||
this->event_.gatts.data = new uint8_t[p->write.len];
|
|
||||||
memcpy(this->event_.gatts.data, p->write.value, p->write.len);
|
|
||||||
} else {
|
|
||||||
this->event_.gatts.data = nullptr;
|
|
||||||
}
|
|
||||||
this->event_.gatts.gatts_param->write.value = this->event_.gatts.data;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this->event_.gatts.data = nullptr;
|
this->event_.gatts.data = nullptr;
|
||||||
this->event_.gatts.data_len = 0;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
#include "ble_uuid.h"
|
#include "ble_uuid.h"
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#ifdef USE_ESP32_BLE_UUID
|
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
@@ -191,5 +190,4 @@ std::string ESPBTUUID::to_string() const {
|
|||||||
|
|
||||||
} // namespace esphome::esp32_ble
|
} // namespace esphome::esp32_ble
|
||||||
|
|
||||||
#endif // USE_ESP32_BLE_UUID
|
#endif
|
||||||
#endif // USE_ESP32
|
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#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"
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#ifdef USE_ESP32_BLE_UUID
|
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <esp_bt_defs.h>
|
#include <esp_bt_defs.h>
|
||||||
@@ -44,5 +42,4 @@ class ESPBTUUID {
|
|||||||
|
|
||||||
} // namespace esphome::esp32_ble
|
} // namespace esphome::esp32_ble
|
||||||
|
|
||||||
#endif // USE_ESP32_BLE_UUID
|
#endif
|
||||||
#endif // USE_ESP32
|
|
||||||
|
@@ -65,8 +65,6 @@ FINAL_VALIDATE_SCHEMA = esp32_ble.validate_variant
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_define("USE_ESP32_BLE_UUID")
|
|
||||||
|
|
||||||
uuid = config[CONF_UUID].hex
|
uuid = config[CONF_UUID].hex
|
||||||
uuid_arr = [
|
uuid_arr = [
|
||||||
cg.RawExpression(f"0x{uuid[i : i + 2]}") for i in range(0, len(uuid), 2)
|
cg.RawExpression(f"0x{uuid[i : i + 2]}") for i in range(0, len(uuid), 2)
|
||||||
@@ -84,8 +82,6 @@ async def to_code(config):
|
|||||||
cg.add(var.set_measured_power(config[CONF_MEASURED_POWER]))
|
cg.add(var.set_measured_power(config[CONF_MEASURED_POWER]))
|
||||||
cg.add(var.set_tx_power(config[CONF_TX_POWER]))
|
cg.add(var.set_tx_power(config[CONF_TX_POWER]))
|
||||||
|
|
||||||
cg.add_define("USE_ESP32_BLE_ADVERTISING")
|
|
||||||
|
|
||||||
if CORE.using_esp_idf:
|
if CORE.using_esp_idf:
|
||||||
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
|
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
|
||||||
add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True)
|
add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True)
|
||||||
|
@@ -2,7 +2,7 @@ import esphome.codegen as cg
|
|||||||
from esphome.components import esp32_ble_tracker
|
from esphome.components import esp32_ble_tracker
|
||||||
|
|
||||||
AUTO_LOAD = ["esp32_ble_tracker"]
|
AUTO_LOAD = ["esp32_ble_tracker"]
|
||||||
CODEOWNERS = ["@jesserockz", "@bdraco"]
|
CODEOWNERS = ["@jesserockz"]
|
||||||
DEPENDENCIES = ["esp32"]
|
DEPENDENCIES = ["esp32"]
|
||||||
|
|
||||||
esp32_ble_client_ns = cg.esphome_ns.namespace("esp32_ble_client")
|
esp32_ble_client_ns = cg.esphome_ns.namespace("esp32_ble_client")
|
||||||
|
@@ -5,9 +5,9 @@
|
|||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#ifdef USE_ESP32_BLE_DEVICE
|
|
||||||
|
|
||||||
namespace esphome::esp32_ble_client {
|
namespace esphome {
|
||||||
|
namespace esp32_ble_client {
|
||||||
|
|
||||||
static const char *const TAG = "esp32_ble_client";
|
static const char *const TAG = "esp32_ble_client";
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ esp_err_t BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size)
|
|||||||
return write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP);
|
return write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::esp32_ble_client
|
} // namespace esp32_ble_client
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_ESP32_BLE_DEVICE
|
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
|
@@ -1,9 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#ifdef USE_ESP32_BLE_DEVICE
|
|
||||||
|
|
||||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||||
|
|
||||||
@@ -11,7 +8,8 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome::esp32_ble_client {
|
namespace esphome {
|
||||||
|
namespace esp32_ble_client {
|
||||||
|
|
||||||
namespace espbt = esphome::esp32_ble_tracker;
|
namespace espbt = esphome::esp32_ble_tracker;
|
||||||
|
|
||||||
@@ -35,7 +33,7 @@ class BLECharacteristic {
|
|||||||
BLEService *service;
|
BLEService *service;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::esp32_ble_client
|
} // namespace esp32_ble_client
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_ESP32_BLE_DEVICE
|
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
|
@@ -8,15 +8,16 @@
|
|||||||
#include <esp_gap_ble_api.h>
|
#include <esp_gap_ble_api.h>
|
||||||
#include <esp_gatt_defs.h>
|
#include <esp_gatt_defs.h>
|
||||||
|
|
||||||
namespace esphome::esp32_ble_client {
|
namespace esphome {
|
||||||
|
namespace esp32_ble_client {
|
||||||
|
|
||||||
static const char *const TAG = "esp32_ble_client";
|
static const char *const TAG = "esp32_ble_client";
|
||||||
|
|
||||||
// Intermediate connection parameters for standard operation
|
// Intermediate connection parameters for standard operation
|
||||||
// ESP-IDF defaults (12.5-15ms) are too slow for stable connections through WiFi-based BLE proxies,
|
// ESP-IDF defaults (12.5-15ms) are too slow for stable connections through WiFi-based BLE proxies,
|
||||||
// causing disconnections. These medium parameters balance responsiveness with bandwidth usage.
|
// causing disconnections. These medium parameters balance responsiveness with bandwidth usage.
|
||||||
static const uint16_t MEDIUM_MIN_CONN_INTERVAL = 0x07; // 7 * 1.25ms = 8.75ms
|
static const uint16_t MEDIUM_MIN_CONN_INTERVAL = 0x08; // 8 * 1.25ms = 10ms
|
||||||
static const uint16_t MEDIUM_MAX_CONN_INTERVAL = 0x09; // 9 * 1.25ms = 11.25ms
|
static const uint16_t MEDIUM_MAX_CONN_INTERVAL = 0x0A; // 10 * 1.25ms = 12.5ms
|
||||||
// The timeout value was increased from 6s to 8s to address stability issues observed
|
// The timeout value was increased from 6s to 8s to address stability issues observed
|
||||||
// in certain BLE devices when operating through WiFi-based BLE proxies. The longer
|
// in certain BLE devices when operating through WiFi-based BLE proxies. The longer
|
||||||
// timeout reduces the likelihood of disconnections during periods of high latency.
|
// timeout reduces the likelihood of disconnections during periods of high latency.
|
||||||
@@ -44,10 +45,8 @@ void BLEClientBase::set_state(espbt::ClientState st) {
|
|||||||
ESPBTClient::set_state(st);
|
ESPBTClient::set_state(st);
|
||||||
|
|
||||||
if (st == espbt::ClientState::READY_TO_CONNECT) {
|
if (st == espbt::ClientState::READY_TO_CONNECT) {
|
||||||
// Enable loop for state processing
|
// Enable loop when we need to connect
|
||||||
this->enable_loop();
|
this->enable_loop();
|
||||||
// Connect immediately instead of waiting for next loop
|
|
||||||
this->connect();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +63,11 @@ void BLEClientBase::loop() {
|
|||||||
}
|
}
|
||||||
this->set_state(espbt::ClientState::IDLE);
|
this->set_state(espbt::ClientState::IDLE);
|
||||||
}
|
}
|
||||||
|
// READY_TO_CONNECT means we have discovered the device
|
||||||
|
// and the scanner has been stopped by the tracker.
|
||||||
|
else if (this->state_ == espbt::ClientState::READY_TO_CONNECT) {
|
||||||
|
this->connect();
|
||||||
|
}
|
||||||
// If its idle, we can disable the loop as set_state
|
// If its idle, we can disable the loop as set_state
|
||||||
// will enable it again when we need to connect.
|
// will enable it again when we need to connect.
|
||||||
else if (this->state_ == espbt::ClientState::IDLE) {
|
else if (this->state_ == espbt::ClientState::IDLE) {
|
||||||
@@ -78,7 +82,40 @@ void BLEClientBase::dump_config() {
|
|||||||
" Address: %s\n"
|
" Address: %s\n"
|
||||||
" Auto-Connect: %s",
|
" Auto-Connect: %s",
|
||||||
this->address_str().c_str(), TRUEFALSE(this->auto_connect_));
|
this->address_str().c_str(), TRUEFALSE(this->auto_connect_));
|
||||||
ESP_LOGCONFIG(TAG, " State: %s", espbt::client_state_to_string(this->state()));
|
std::string state_name;
|
||||||
|
switch (this->state()) {
|
||||||
|
case espbt::ClientState::INIT:
|
||||||
|
state_name = "INIT";
|
||||||
|
break;
|
||||||
|
case espbt::ClientState::DISCONNECTING:
|
||||||
|
state_name = "DISCONNECTING";
|
||||||
|
break;
|
||||||
|
case espbt::ClientState::IDLE:
|
||||||
|
state_name = "IDLE";
|
||||||
|
break;
|
||||||
|
case espbt::ClientState::SEARCHING:
|
||||||
|
state_name = "SEARCHING";
|
||||||
|
break;
|
||||||
|
case espbt::ClientState::DISCOVERED:
|
||||||
|
state_name = "DISCOVERED";
|
||||||
|
break;
|
||||||
|
case espbt::ClientState::READY_TO_CONNECT:
|
||||||
|
state_name = "READY_TO_CONNECT";
|
||||||
|
break;
|
||||||
|
case espbt::ClientState::CONNECTING:
|
||||||
|
state_name = "CONNECTING";
|
||||||
|
break;
|
||||||
|
case espbt::ClientState::CONNECTED:
|
||||||
|
state_name = "CONNECTED";
|
||||||
|
break;
|
||||||
|
case espbt::ClientState::ESTABLISHED:
|
||||||
|
state_name = "ESTABLISHED";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
state_name = "UNKNOWN_STATE";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ESP_LOGCONFIG(TAG, " State: %s", state_name.c_str());
|
||||||
if (this->status_ == ESP_GATT_NO_RESOURCES) {
|
if (this->status_ == ESP_GATT_NO_RESOURCES) {
|
||||||
ESP_LOGE(TAG, " Failed due to no resources. Try to reduce number of BLE clients in config.");
|
ESP_LOGE(TAG, " Failed due to no resources. Try to reduce number of BLE clients in config.");
|
||||||
} else if (this->status_ != ESP_GATT_OK) {
|
} else if (this->status_ != ESP_GATT_OK) {
|
||||||
@@ -107,55 +144,59 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
void BLEClientBase::connect() {
|
void BLEClientBase::connect() {
|
||||||
ESP_LOGI(TAG, "[%d] [%s] 0x%02x Connecting", this->connection_index_, this->address_str_.c_str(),
|
ESP_LOGI(TAG, "[%d] [%s] 0x%02x Attempting BLE connection", this->connection_index_, this->address_str_.c_str(),
|
||||||
this->remote_addr_type_);
|
this->remote_addr_type_);
|
||||||
this->paired_ = false;
|
this->paired_ = false;
|
||||||
|
|
||||||
// Set preferred connection parameters before connecting
|
|
||||||
// Use FAST for all V3 connections (better latency and reliability)
|
|
||||||
// Use MEDIUM for V1/legacy connections (balanced performance)
|
|
||||||
uint16_t min_interval, max_interval, timeout;
|
|
||||||
const char *param_type;
|
|
||||||
|
|
||||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE ||
|
|
||||||
this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
|
||||||
min_interval = FAST_MIN_CONN_INTERVAL;
|
|
||||||
max_interval = FAST_MAX_CONN_INTERVAL;
|
|
||||||
timeout = FAST_CONN_TIMEOUT;
|
|
||||||
param_type = "fast";
|
|
||||||
} else {
|
|
||||||
min_interval = MEDIUM_MIN_CONN_INTERVAL;
|
|
||||||
max_interval = MEDIUM_MAX_CONN_INTERVAL;
|
|
||||||
timeout = MEDIUM_CONN_TIMEOUT;
|
|
||||||
param_type = "medium";
|
|
||||||
}
|
|
||||||
|
|
||||||
auto param_ret = esp_ble_gap_set_prefer_conn_params(this->remote_bda_, min_interval, max_interval,
|
|
||||||
0, // latency: 0
|
|
||||||
timeout);
|
|
||||||
if (param_ret != ESP_OK) {
|
|
||||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gap_set_prefer_conn_params failed: %d", this->connection_index_,
|
|
||||||
this->address_str_.c_str(), param_ret);
|
|
||||||
} else {
|
|
||||||
this->log_connection_params_(param_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now open the connection
|
|
||||||
auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
|
auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
this->log_gattc_warning_("esp_ble_gattc_open", ret);
|
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_open error, status=%d", this->connection_index_, this->address_str_.c_str(),
|
||||||
|
ret);
|
||||||
this->set_state(espbt::ClientState::IDLE);
|
this->set_state(espbt::ClientState::IDLE);
|
||||||
} else {
|
} else {
|
||||||
this->set_state(espbt::ClientState::CONNECTING);
|
this->set_state(espbt::ClientState::CONNECTING);
|
||||||
|
|
||||||
|
// Always set connection parameters to ensure stable operation
|
||||||
|
// Use FAST for V3_WITHOUT_CACHE (devices that need lowest latency)
|
||||||
|
// Use MEDIUM for all other connections (balanced performance)
|
||||||
|
uint16_t min_interval, max_interval, timeout;
|
||||||
|
const char *param_type;
|
||||||
|
|
||||||
|
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||||
|
min_interval = FAST_MIN_CONN_INTERVAL;
|
||||||
|
max_interval = FAST_MAX_CONN_INTERVAL;
|
||||||
|
timeout = FAST_CONN_TIMEOUT;
|
||||||
|
param_type = "fast";
|
||||||
|
} else {
|
||||||
|
min_interval = MEDIUM_MIN_CONN_INTERVAL;
|
||||||
|
max_interval = MEDIUM_MAX_CONN_INTERVAL;
|
||||||
|
timeout = MEDIUM_CONN_TIMEOUT;
|
||||||
|
param_type = "medium";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto param_ret = esp_ble_gap_set_prefer_conn_params(this->remote_bda_, min_interval, max_interval,
|
||||||
|
0, // latency: 0
|
||||||
|
timeout);
|
||||||
|
if (param_ret != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gap_set_prefer_conn_params failed: %d", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), param_ret);
|
||||||
|
} else {
|
||||||
|
ESP_LOGD(TAG, "[%d] [%s] Set %s conn params", this->connection_index_, this->address_str_.c_str(), param_type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t BLEClientBase::pair() { return esp_ble_set_encryption(this->remote_bda_, ESP_BLE_SEC_ENCRYPT); }
|
esp_err_t BLEClientBase::pair() { return esp_ble_set_encryption(this->remote_bda_, ESP_BLE_SEC_ENCRYPT); }
|
||||||
|
|
||||||
void BLEClientBase::disconnect() {
|
void BLEClientBase::disconnect() {
|
||||||
if (this->state_ == espbt::ClientState::IDLE || this->state_ == espbt::ClientState::DISCONNECTING) {
|
if (this->state_ == espbt::ClientState::IDLE) {
|
||||||
ESP_LOGI(TAG, "[%d] [%s] Disconnect requested, but already %s", this->connection_index_, this->address_str_.c_str(),
|
ESP_LOGI(TAG, "[%d] [%s] Disconnect requested, but already idle.", this->connection_index_,
|
||||||
espbt::client_state_to_string(this->state_));
|
this->address_str_.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this->state_ == espbt::ClientState::DISCONNECTING) {
|
||||||
|
ESP_LOGI(TAG, "[%d] [%s] Disconnect requested, but already disconnecting.", this->connection_index_,
|
||||||
|
this->address_str_.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this->state_ == espbt::ClientState::CONNECTING || this->conn_id_ == UNSET_CONN_ID) {
|
if (this->state_ == espbt::ClientState::CONNECTING || this->conn_id_ == UNSET_CONN_ID) {
|
||||||
@@ -190,7 +231,8 @@ void BLEClientBase::unconditional_disconnect() {
|
|||||||
// In the future we might consider App.reboot() here since
|
// In the future we might consider App.reboot() here since
|
||||||
// the BLE stack is in an indeterminate state.
|
// the BLE stack is in an indeterminate state.
|
||||||
//
|
//
|
||||||
this->log_gattc_warning_("esp_ble_gattc_close", err);
|
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_close error, err=%d", this->connection_index_, this->address_str_.c_str(),
|
||||||
|
err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->state_ == espbt::ClientState::SEARCHING || this->state_ == espbt::ClientState::READY_TO_CONNECT ||
|
if (this->state_ == espbt::ClientState::SEARCHING || this->state_ == espbt::ClientState::READY_TO_CONNECT ||
|
||||||
@@ -203,11 +245,9 @@ void BLEClientBase::unconditional_disconnect() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BLEClientBase::release_services() {
|
void BLEClientBase::release_services() {
|
||||||
#ifdef USE_ESP32_BLE_DEVICE
|
|
||||||
for (auto &svc : this->services_)
|
for (auto &svc : this->services_)
|
||||||
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
|
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
this->services_.clear();
|
this->services_.clear();
|
||||||
#endif
|
|
||||||
#ifndef CONFIG_BT_GATTC_CACHE_NVS_FLASH
|
#ifndef CONFIG_BT_GATTC_CACHE_NVS_FLASH
|
||||||
esp_ble_gattc_cache_clean(this->remote_bda_);
|
esp_ble_gattc_cache_clean(this->remote_bda_);
|
||||||
#endif
|
#endif
|
||||||
@@ -217,36 +257,6 @@ void BLEClientBase::log_event_(const char *name) {
|
|||||||
ESP_LOGD(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), name);
|
ESP_LOGD(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BLEClientBase::log_gattc_event_(const char *name) {
|
|
||||||
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_%s_EVT", this->connection_index_, this->address_str_.c_str(), name);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLEClientBase::log_gattc_warning_(const char *operation, esp_gatt_status_t status) {
|
|
||||||
ESP_LOGW(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str_.c_str(), operation,
|
|
||||||
status);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLEClientBase::log_gattc_warning_(const char *operation, esp_err_t err) {
|
|
||||||
ESP_LOGW(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str_.c_str(), operation, err);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLEClientBase::log_connection_params_(const char *param_type) {
|
|
||||||
ESP_LOGD(TAG, "[%d] [%s] %s conn params", this->connection_index_, this->address_str_.c_str(), param_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLEClientBase::restore_medium_conn_params_() {
|
|
||||||
// Restore to medium connection parameters after initial connection phase
|
|
||||||
// This balances performance with bandwidth usage for normal operation
|
|
||||||
esp_ble_conn_update_params_t conn_params = {{0}};
|
|
||||||
memcpy(conn_params.bda, this->remote_bda_, sizeof(esp_bd_addr_t));
|
|
||||||
conn_params.min_int = MEDIUM_MIN_CONN_INTERVAL;
|
|
||||||
conn_params.max_int = MEDIUM_MAX_CONN_INTERVAL;
|
|
||||||
conn_params.latency = 0;
|
|
||||||
conn_params.timeout = MEDIUM_CONN_TIMEOUT;
|
|
||||||
this->log_connection_params_("medium");
|
|
||||||
esp_ble_gap_update_conn_params(&conn_params);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
|
bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
|
||||||
esp_ble_gattc_cb_param_t *param) {
|
esp_ble_gattc_cb_param_t *param) {
|
||||||
if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id)
|
if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id)
|
||||||
@@ -274,18 +284,30 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
case ESP_GATTC_OPEN_EVT: {
|
case ESP_GATTC_OPEN_EVT: {
|
||||||
if (!this->check_addr(param->open.remote_bda))
|
if (!this->check_addr(param->open.remote_bda))
|
||||||
return false;
|
return false;
|
||||||
this->log_gattc_event_("OPEN");
|
this->log_event_("ESP_GATTC_OPEN_EVT");
|
||||||
// conn_id was already set in ESP_GATTC_CONNECT_EVT
|
this->conn_id_ = param->open.conn_id;
|
||||||
this->service_count_ = 0;
|
this->service_count_ = 0;
|
||||||
if (this->state_ != espbt::ClientState::CONNECTING) {
|
if (this->state_ != espbt::ClientState::CONNECTING) {
|
||||||
// This should not happen but lets log it in case it does
|
// This should not happen but lets log it in case it does
|
||||||
// because it means we have a bad assumption about how the
|
// because it means we have a bad assumption about how the
|
||||||
// ESP BT stack works.
|
// ESP BT stack works.
|
||||||
ESP_LOGE(TAG, "[%d] [%s] Got ESP_GATTC_OPEN_EVT while in %s state, status=%d", this->connection_index_,
|
if (this->state_ == espbt::ClientState::CONNECTED) {
|
||||||
this->address_str_.c_str(), espbt::client_state_to_string(this->state_), param->open.status);
|
ESP_LOGE(TAG, "[%d] [%s] Got ESP_GATTC_OPEN_EVT while already connected, status=%d", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), param->open.status);
|
||||||
|
} else if (this->state_ == espbt::ClientState::ESTABLISHED) {
|
||||||
|
ESP_LOGE(TAG, "[%d] [%s] Got ESP_GATTC_OPEN_EVT while already established, status=%d",
|
||||||
|
this->connection_index_, this->address_str_.c_str(), param->open.status);
|
||||||
|
} else if (this->state_ == espbt::ClientState::DISCONNECTING) {
|
||||||
|
ESP_LOGE(TAG, "[%d] [%s] Got ESP_GATTC_OPEN_EVT while disconnecting, status=%d", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), param->open.status);
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "[%d] [%s] Got ESP_GATTC_OPEN_EVT while not in connecting state, status=%d",
|
||||||
|
this->connection_index_, this->address_str_.c_str(), param->open.status);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
|
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
|
||||||
this->log_gattc_warning_("Connection open", param->open.status);
|
ESP_LOGW(TAG, "[%d] [%s] Connection failed, status=%d", this->connection_index_, this->address_str_.c_str(),
|
||||||
|
param->open.status);
|
||||||
this->set_state(espbt::ClientState::IDLE);
|
this->set_state(espbt::ClientState::IDLE);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -297,12 +319,15 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
this->conn_id_ = UNSET_CONN_ID;
|
this->conn_id_ = UNSET_CONN_ID;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// MTU negotiation already started in ESP_GATTC_CONNECT_EVT
|
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->open.conn_id);
|
||||||
|
if (ret) {
|
||||||
|
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_send_mtu_req failed, status=%x", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), ret);
|
||||||
|
}
|
||||||
this->set_state(espbt::ClientState::CONNECTED);
|
this->set_state(espbt::ClientState::CONNECTED);
|
||||||
ESP_LOGI(TAG, "[%d] [%s] Connection open", this->connection_index_, this->address_str_.c_str());
|
ESP_LOGI(TAG, "[%d] [%s] Connection open", this->connection_index_, this->address_str_.c_str());
|
||||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
||||||
// Restore to medium connection parameters for cached connections too
|
ESP_LOGI(TAG, "[%d] [%s] Using cached services", this->connection_index_, this->address_str_.c_str());
|
||||||
this->restore_medium_conn_params_();
|
|
||||||
// only set our state, subclients might have more stuff to do yet.
|
// only set our state, subclients might have more stuff to do yet.
|
||||||
this->state_ = espbt::ClientState::ESTABLISHED;
|
this->state_ = espbt::ClientState::ESTABLISHED;
|
||||||
break;
|
break;
|
||||||
@@ -314,16 +339,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
case ESP_GATTC_CONNECT_EVT: {
|
case ESP_GATTC_CONNECT_EVT: {
|
||||||
if (!this->check_addr(param->connect.remote_bda))
|
if (!this->check_addr(param->connect.remote_bda))
|
||||||
return false;
|
return false;
|
||||||
this->log_gattc_event_("CONNECT");
|
this->log_event_("ESP_GATTC_CONNECT_EVT");
|
||||||
this->conn_id_ = param->connect.conn_id;
|
|
||||||
// Start MTU negotiation immediately as recommended by ESP-IDF examples
|
|
||||||
// (gatt_client, ble_throughput) which call esp_ble_gattc_send_mtu_req in
|
|
||||||
// ESP_GATTC_CONNECT_EVT instead of waiting for ESP_GATTC_OPEN_EVT.
|
|
||||||
// This saves ~3ms in the connection process.
|
|
||||||
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->connect.conn_id);
|
|
||||||
if (ret) {
|
|
||||||
this->log_gattc_warning_("esp_ble_gattc_send_mtu_req", ret);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_DISCONNECT_EVT: {
|
case ESP_GATTC_DISCONNECT_EVT: {
|
||||||
@@ -360,7 +376,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
case ESP_GATTC_CLOSE_EVT: {
|
case ESP_GATTC_CLOSE_EVT: {
|
||||||
if (this->conn_id_ != param->close.conn_id)
|
if (this->conn_id_ != param->close.conn_id)
|
||||||
return false;
|
return false;
|
||||||
this->log_gattc_event_("CLOSE");
|
this->log_event_("ESP_GATTC_CLOSE_EVT");
|
||||||
this->release_services();
|
this->release_services();
|
||||||
this->set_state(espbt::ClientState::IDLE);
|
this->set_state(espbt::ClientState::IDLE);
|
||||||
this->conn_id_ = UNSET_CONN_ID;
|
this->conn_id_ = UNSET_CONN_ID;
|
||||||
@@ -372,74 +388,78 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
this->service_count_++;
|
this->service_count_++;
|
||||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||||
// V3 clients don't need services initialized since
|
// V3 clients don't need services initialized since
|
||||||
// as they use the ESP APIs to get services.
|
// they only request by handle after receiving the services.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#ifdef USE_ESP32_BLE_DEVICE
|
|
||||||
BLEService *ble_service = new BLEService(); // NOLINT(cppcoreguidelines-owning-memory)
|
BLEService *ble_service = new BLEService(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid);
|
ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid);
|
||||||
ble_service->start_handle = param->search_res.start_handle;
|
ble_service->start_handle = param->search_res.start_handle;
|
||||||
ble_service->end_handle = param->search_res.end_handle;
|
ble_service->end_handle = param->search_res.end_handle;
|
||||||
ble_service->client = this;
|
ble_service->client = this;
|
||||||
this->services_.push_back(ble_service);
|
this->services_.push_back(ble_service);
|
||||||
#endif
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||||
if (this->conn_id_ != param->search_cmpl.conn_id)
|
if (this->conn_id_ != param->search_cmpl.conn_id)
|
||||||
return false;
|
return false;
|
||||||
this->log_gattc_event_("SEARCH_CMPL");
|
this->log_event_("ESP_GATTC_SEARCH_CMPL_EVT");
|
||||||
// For V3 connections, restore to medium connection parameters after service discovery
|
for (auto &svc : this->services_) {
|
||||||
// This balances performance with bandwidth usage after the critical discovery phase
|
ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_.c_str(),
|
||||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE ||
|
svc->uuid.to_string().c_str());
|
||||||
this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
ESP_LOGV(TAG, "[%d] [%s] start_handle: 0x%x end_handle: 0x%x", this->connection_index_,
|
||||||
this->restore_medium_conn_params_();
|
this->address_str_.c_str(), svc->start_handle, svc->end_handle);
|
||||||
} else {
|
|
||||||
#ifdef USE_ESP32_BLE_DEVICE
|
|
||||||
for (auto &svc : this->services_) {
|
|
||||||
ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_.c_str(),
|
|
||||||
svc->uuid.to_string().c_str());
|
|
||||||
ESP_LOGV(TAG, "[%d] [%s] start_handle: 0x%x end_handle: 0x%x", this->connection_index_,
|
|
||||||
this->address_str_.c_str(), svc->start_handle, svc->end_handle);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
ESP_LOGI(TAG, "[%d] [%s] Service discovery complete", this->connection_index_, this->address_str_.c_str());
|
ESP_LOGI(TAG, "[%d] [%s] Service discovery complete", this->connection_index_, this->address_str_.c_str());
|
||||||
|
|
||||||
|
// For non-cached connections, restore to medium connection parameters after service discovery
|
||||||
|
// This balances performance with bandwidth usage after the critical discovery phase
|
||||||
|
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||||
|
esp_ble_conn_update_params_t conn_params = {{0}};
|
||||||
|
memcpy(conn_params.bda, this->remote_bda_, sizeof(esp_bd_addr_t));
|
||||||
|
conn_params.min_int = MEDIUM_MIN_CONN_INTERVAL;
|
||||||
|
conn_params.max_int = MEDIUM_MAX_CONN_INTERVAL;
|
||||||
|
conn_params.latency = 0;
|
||||||
|
conn_params.timeout = MEDIUM_CONN_TIMEOUT;
|
||||||
|
ESP_LOGD(TAG, "[%d] [%s] Restored medium conn params after service discovery", this->connection_index_,
|
||||||
|
this->address_str_.c_str());
|
||||||
|
esp_ble_gap_update_conn_params(&conn_params);
|
||||||
|
}
|
||||||
|
|
||||||
this->state_ = espbt::ClientState::ESTABLISHED;
|
this->state_ = espbt::ClientState::ESTABLISHED;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_READ_DESCR_EVT: {
|
case ESP_GATTC_READ_DESCR_EVT: {
|
||||||
if (this->conn_id_ != param->write.conn_id)
|
if (this->conn_id_ != param->write.conn_id)
|
||||||
return false;
|
return false;
|
||||||
this->log_gattc_event_("READ_DESCR");
|
this->log_event_("ESP_GATTC_READ_DESCR_EVT");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_WRITE_DESCR_EVT: {
|
case ESP_GATTC_WRITE_DESCR_EVT: {
|
||||||
if (this->conn_id_ != param->write.conn_id)
|
if (this->conn_id_ != param->write.conn_id)
|
||||||
return false;
|
return false;
|
||||||
this->log_gattc_event_("WRITE_DESCR");
|
this->log_event_("ESP_GATTC_WRITE_DESCR_EVT");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_WRITE_CHAR_EVT: {
|
case ESP_GATTC_WRITE_CHAR_EVT: {
|
||||||
if (this->conn_id_ != param->write.conn_id)
|
if (this->conn_id_ != param->write.conn_id)
|
||||||
return false;
|
return false;
|
||||||
this->log_gattc_event_("WRITE_CHAR");
|
this->log_event_("ESP_GATTC_WRITE_CHAR_EVT");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_READ_CHAR_EVT: {
|
case ESP_GATTC_READ_CHAR_EVT: {
|
||||||
if (this->conn_id_ != param->read.conn_id)
|
if (this->conn_id_ != param->read.conn_id)
|
||||||
return false;
|
return false;
|
||||||
this->log_gattc_event_("READ_CHAR");
|
this->log_event_("ESP_GATTC_READ_CHAR_EVT");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_NOTIFY_EVT: {
|
case ESP_GATTC_NOTIFY_EVT: {
|
||||||
if (this->conn_id_ != param->notify.conn_id)
|
if (this->conn_id_ != param->notify.conn_id)
|
||||||
return false;
|
return false;
|
||||||
this->log_gattc_event_("NOTIFY");
|
this->log_event_("ESP_GATTC_NOTIFY_EVT");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||||
this->log_gattc_event_("REG_FOR_NOTIFY");
|
this->log_event_("ESP_GATTC_REG_FOR_NOTIFY_EVT");
|
||||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
||||||
this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||||
// Client is responsible for flipping the descriptor value
|
// Client is responsible for flipping the descriptor value
|
||||||
@@ -451,7 +471,8 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
esp_gatt_status_t descr_status = esp_ble_gattc_get_descr_by_char_handle(
|
esp_gatt_status_t descr_status = esp_ble_gattc_get_descr_by_char_handle(
|
||||||
this->gattc_if_, this->conn_id_, param->reg_for_notify.handle, NOTIFY_DESC_UUID, &desc_result, &count);
|
this->gattc_if_, this->conn_id_, param->reg_for_notify.handle, NOTIFY_DESC_UUID, &desc_result, &count);
|
||||||
if (descr_status != ESP_GATT_OK) {
|
if (descr_status != ESP_GATT_OK) {
|
||||||
this->log_gattc_warning_("esp_ble_gattc_get_descr_by_char_handle", descr_status);
|
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_descr_by_char_handle error, status=%d", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), descr_status);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
esp_gattc_char_elem_t char_result;
|
esp_gattc_char_elem_t char_result;
|
||||||
@@ -459,7 +480,8 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
esp_ble_gattc_get_all_char(this->gattc_if_, this->conn_id_, param->reg_for_notify.handle,
|
esp_ble_gattc_get_all_char(this->gattc_if_, this->conn_id_, param->reg_for_notify.handle,
|
||||||
param->reg_for_notify.handle, &char_result, &count, 0);
|
param->reg_for_notify.handle, &char_result, &count, 0);
|
||||||
if (char_status != ESP_GATT_OK) {
|
if (char_status != ESP_GATT_OK) {
|
||||||
this->log_gattc_warning_("esp_ble_gattc_get_all_char", char_status);
|
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), char_status);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,7 +495,8 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
(uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
|
(uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||||
ESP_LOGD(TAG, "Wrote notify descriptor %d, properties=%d", notify_en, char_result.properties);
|
ESP_LOGD(TAG, "Wrote notify descriptor %d, properties=%d", notify_en, char_result.properties);
|
||||||
if (status) {
|
if (status) {
|
||||||
this->log_gattc_warning_("esp_ble_gattc_write_char_descr", status);
|
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, status=%d", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), status);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -581,7 +604,6 @@ float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) {
|
|||||||
return NAN;
|
return NAN;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_ESP32_BLE_DEVICE
|
|
||||||
BLEService *BLEClientBase::get_service(espbt::ESPBTUUID uuid) {
|
BLEService *BLEClientBase::get_service(espbt::ESPBTUUID uuid) {
|
||||||
for (auto *svc : this->services_) {
|
for (auto *svc : this->services_) {
|
||||||
if (svc->uuid == uuid)
|
if (svc->uuid == uuid)
|
||||||
@@ -658,8 +680,8 @@ BLEDescriptor *BLEClientBase::get_descriptor(uint16_t handle) {
|
|||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
#endif // USE_ESP32_BLE_DEVICE
|
|
||||||
|
|
||||||
} // namespace esphome::esp32_ble_client
|
} // namespace esp32_ble_client
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
|
@@ -5,9 +5,7 @@
|
|||||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
|
|
||||||
#ifdef USE_ESP32_BLE_DEVICE
|
|
||||||
#include "ble_service.h"
|
#include "ble_service.h"
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -18,7 +16,8 @@
|
|||||||
#include <esp_gatt_common_api.h>
|
#include <esp_gatt_common_api.h>
|
||||||
#include <esp_gattc_api.h>
|
#include <esp_gattc_api.h>
|
||||||
|
|
||||||
namespace esphome::esp32_ble_client {
|
namespace esphome {
|
||||||
|
namespace esp32_ble_client {
|
||||||
|
|
||||||
namespace espbt = esphome::esp32_ble_tracker;
|
namespace espbt = esphome::esp32_ble_tracker;
|
||||||
|
|
||||||
@@ -67,9 +66,8 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
|
|||||||
(uint8_t) (this->address_ >> 0) & 0xff);
|
(uint8_t) (this->address_ >> 0) & 0xff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const std::string &address_str() const { return this->address_str_; }
|
std::string address_str() const { return this->address_str_; }
|
||||||
|
|
||||||
#ifdef USE_ESP32_BLE_DEVICE
|
|
||||||
BLEService *get_service(espbt::ESPBTUUID uuid);
|
BLEService *get_service(espbt::ESPBTUUID uuid);
|
||||||
BLEService *get_service(uint16_t uuid);
|
BLEService *get_service(uint16_t uuid);
|
||||||
BLECharacteristic *get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr);
|
BLECharacteristic *get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr);
|
||||||
@@ -80,7 +78,6 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
|
|||||||
BLEDescriptor *get_descriptor(uint16_t handle);
|
BLEDescriptor *get_descriptor(uint16_t handle);
|
||||||
// Get the configuration descriptor for the given characteristic handle.
|
// Get the configuration descriptor for the given characteristic handle.
|
||||||
BLEDescriptor *get_config_descriptor(uint16_t handle);
|
BLEDescriptor *get_config_descriptor(uint16_t handle);
|
||||||
#endif
|
|
||||||
|
|
||||||
float parse_char_value(uint8_t *value, uint16_t length);
|
float parse_char_value(uint8_t *value, uint16_t length);
|
||||||
|
|
||||||
@@ -107,9 +104,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
|
|||||||
|
|
||||||
// Group 2: Container types (grouped for memory optimization)
|
// Group 2: Container types (grouped for memory optimization)
|
||||||
std::string address_str_{};
|
std::string address_str_{};
|
||||||
#ifdef USE_ESP32_BLE_DEVICE
|
|
||||||
std::vector<BLEService *> services_;
|
std::vector<BLEService *> services_;
|
||||||
#endif
|
|
||||||
|
|
||||||
// Group 3: 4-byte types
|
// Group 3: 4-byte types
|
||||||
int gattc_if_;
|
int gattc_if_;
|
||||||
@@ -132,13 +127,9 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
|
|||||||
// 6 bytes used, 2 bytes padding
|
// 6 bytes used, 2 bytes padding
|
||||||
|
|
||||||
void log_event_(const char *name);
|
void log_event_(const char *name);
|
||||||
void log_gattc_event_(const char *name);
|
|
||||||
void restore_medium_conn_params_();
|
|
||||||
void log_gattc_warning_(const char *operation, esp_gatt_status_t status);
|
|
||||||
void log_gattc_warning_(const char *operation, esp_err_t err);
|
|
||||||
void log_connection_params_(const char *param_type);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::esp32_ble_client
|
} // namespace esp32_ble_client
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
|
@@ -1,13 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#ifdef USE_ESP32_BLE_DEVICE
|
|
||||||
|
|
||||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||||
|
|
||||||
namespace esphome::esp32_ble_client {
|
namespace esphome {
|
||||||
|
namespace esp32_ble_client {
|
||||||
|
|
||||||
namespace espbt = esphome::esp32_ble_tracker;
|
namespace espbt = esphome::esp32_ble_tracker;
|
||||||
|
|
||||||
@@ -21,7 +19,7 @@ class BLEDescriptor {
|
|||||||
BLECharacteristic *characteristic;
|
BLECharacteristic *characteristic;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::esp32_ble_client
|
} // namespace esp32_ble_client
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_ESP32_BLE_DEVICE
|
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
|
@@ -4,9 +4,9 @@
|
|||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#ifdef USE_ESP32_BLE_DEVICE
|
|
||||||
|
|
||||||
namespace esphome::esp32_ble_client {
|
namespace esphome {
|
||||||
|
namespace esp32_ble_client {
|
||||||
|
|
||||||
static const char *const TAG = "esp32_ble_client";
|
static const char *const TAG = "esp32_ble_client";
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ void BLEService::parse_characteristics() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::esp32_ble_client
|
} // namespace esp32_ble_client
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_ESP32_BLE_DEVICE
|
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
|
@@ -1,9 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#ifdef USE_ESP32_BLE_DEVICE
|
|
||||||
|
|
||||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||||
|
|
||||||
@@ -11,7 +8,8 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome::esp32_ble_client {
|
namespace esphome {
|
||||||
|
namespace esp32_ble_client {
|
||||||
|
|
||||||
namespace espbt = esphome::esp32_ble_tracker;
|
namespace espbt = esphome::esp32_ble_tracker;
|
||||||
|
|
||||||
@@ -32,7 +30,7 @@ class BLEService {
|
|||||||
BLECharacteristic *get_characteristic(uint16_t uuid);
|
BLECharacteristic *get_characteristic(uint16_t uuid);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::esp32_ble_client
|
} // namespace esp32_ble_client
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_ESP32_BLE_DEVICE
|
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
|
@@ -529,7 +529,6 @@ async def to_code_characteristic(service_var, char_conf):
|
|||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
# Register the loggers this component needs
|
# Register the loggers this component needs
|
||||||
esp32_ble.register_bt_logger(BTLoggers.GATT, BTLoggers.SMP)
|
esp32_ble.register_bt_logger(BTLoggers.GATT, BTLoggers.SMP)
|
||||||
cg.add_define("USE_ESP32_BLE_UUID")
|
|
||||||
|
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
|
||||||
@@ -572,7 +571,6 @@ async def to_code(config):
|
|||||||
config[CONF_ON_DISCONNECT],
|
config[CONF_ON_DISCONNECT],
|
||||||
)
|
)
|
||||||
cg.add_define("USE_ESP32_BLE_SERVER")
|
cg.add_define("USE_ESP32_BLE_SERVER")
|
||||||
cg.add_define("USE_ESP32_BLE_ADVERTISING")
|
|
||||||
if CORE.using_esp_idf:
|
if CORE.using_esp_idf:
|
||||||
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
|
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
|
||||||
|
|
||||||
|
@@ -36,7 +36,6 @@ from esphome.types import ConfigType
|
|||||||
|
|
||||||
AUTO_LOAD = ["esp32_ble"]
|
AUTO_LOAD = ["esp32_ble"]
|
||||||
DEPENDENCIES = ["esp32"]
|
DEPENDENCIES = ["esp32"]
|
||||||
CODEOWNERS = ["@bdraco"]
|
|
||||||
|
|
||||||
KEY_ESP32_BLE_TRACKER = "esp32_ble_tracker"
|
KEY_ESP32_BLE_TRACKER = "esp32_ble_tracker"
|
||||||
KEY_USED_CONNECTION_SLOTS = "used_connection_slots"
|
KEY_USED_CONNECTION_SLOTS = "used_connection_slots"
|
||||||
@@ -355,6 +354,11 @@ async def to_code(config):
|
|||||||
add_idf_sdkconfig_option(
|
add_idf_sdkconfig_option(
|
||||||
"CONFIG_BTDM_CTRL_BLE_MAX_CONN", config[CONF_MAX_CONNECTIONS]
|
"CONFIG_BTDM_CTRL_BLE_MAX_CONN", config[CONF_MAX_CONNECTIONS]
|
||||||
)
|
)
|
||||||
|
# CONFIG_BT_GATTC_NOTIF_REG_MAX controls the number of
|
||||||
|
# max notifications in 5.x, setting CONFIG_BT_ACL_CONNECTIONS
|
||||||
|
# is enough in 4.x
|
||||||
|
# https://github.com/esphome/issues/issues/6808
|
||||||
|
add_idf_sdkconfig_option("CONFIG_BT_GATTC_NOTIF_REG_MAX", 9)
|
||||||
|
|
||||||
cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts
|
cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts
|
||||||
cg.add_define("USE_ESP32_BLE_CLIENT")
|
cg.add_define("USE_ESP32_BLE_CLIENT")
|
||||||
@@ -373,7 +377,6 @@ async def _add_ble_features():
|
|||||||
# Add feature-specific defines based on what's needed
|
# Add feature-specific defines based on what's needed
|
||||||
if BLEFeatures.ESP_BT_DEVICE in _required_features:
|
if BLEFeatures.ESP_BT_DEVICE in _required_features:
|
||||||
cg.add_define("USE_ESP32_BLE_DEVICE")
|
cg.add_define("USE_ESP32_BLE_DEVICE")
|
||||||
cg.add_define("USE_ESP32_BLE_UUID")
|
|
||||||
|
|
||||||
|
|
||||||
ESP32_BLE_START_SCAN_ACTION_SCHEMA = cv.Schema(
|
ESP32_BLE_START_SCAN_ACTION_SCHEMA = cv.Schema(
|
||||||
|
@@ -41,31 +41,6 @@ static const char *const TAG = "esp32_ble_tracker";
|
|||||||
|
|
||||||
ESP32BLETracker *global_esp32_ble_tracker = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
ESP32BLETracker *global_esp32_ble_tracker = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
|
||||||
const char *client_state_to_string(ClientState state) {
|
|
||||||
switch (state) {
|
|
||||||
case ClientState::INIT:
|
|
||||||
return "INIT";
|
|
||||||
case ClientState::DISCONNECTING:
|
|
||||||
return "DISCONNECTING";
|
|
||||||
case ClientState::IDLE:
|
|
||||||
return "IDLE";
|
|
||||||
case ClientState::SEARCHING:
|
|
||||||
return "SEARCHING";
|
|
||||||
case ClientState::DISCOVERED:
|
|
||||||
return "DISCOVERED";
|
|
||||||
case ClientState::READY_TO_CONNECT:
|
|
||||||
return "READY_TO_CONNECT";
|
|
||||||
case ClientState::CONNECTING:
|
|
||||||
return "CONNECTING";
|
|
||||||
case ClientState::CONNECTED:
|
|
||||||
return "CONNECTED";
|
|
||||||
case ClientState::ESTABLISHED:
|
|
||||||
return "ESTABLISHED";
|
|
||||||
default:
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float ESP32BLETracker::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
|
float ESP32BLETracker::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
|
||||||
|
|
||||||
void ESP32BLETracker::setup() {
|
void ESP32BLETracker::setup() {
|
||||||
@@ -74,6 +49,13 @@ void ESP32BLETracker::setup() {
|
|||||||
ESP_LOGE(TAG, "BLE Tracker was marked failed by ESP32BLE");
|
ESP_LOGE(TAG, "BLE Tracker was marked failed by ESP32BLE");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
RAMAllocator<BLEScanResult> allocator;
|
||||||
|
this->scan_ring_buffer_ = allocator.allocate(SCAN_RESULT_BUFFER_SIZE);
|
||||||
|
|
||||||
|
if (this->scan_ring_buffer_ == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Could not allocate ring buffer for BLE Tracker!");
|
||||||
|
this->mark_failed();
|
||||||
|
}
|
||||||
|
|
||||||
global_esp32_ble_tracker = this;
|
global_esp32_ble_tracker = this;
|
||||||
|
|
||||||
@@ -101,49 +83,127 @@ void ESP32BLETracker::loop() {
|
|||||||
this->start_scan();
|
this->start_scan();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
int connecting = 0;
|
||||||
// Check for scan timeout - moved here from scheduler to avoid false reboots
|
int discovered = 0;
|
||||||
// when the loop is blocked
|
int searching = 0;
|
||||||
if (this->scanner_state_ == ScannerState::RUNNING) {
|
int disconnecting = 0;
|
||||||
switch (this->scan_timeout_state_) {
|
for (auto *client : this->clients_) {
|
||||||
case ScanTimeoutState::MONITORING: {
|
switch (client->state()) {
|
||||||
uint32_t now = App.get_loop_component_start_time();
|
case ClientState::DISCONNECTING:
|
||||||
uint32_t timeout_ms = this->scan_duration_ * 2000;
|
disconnecting++;
|
||||||
// Robust time comparison that handles rollover correctly
|
|
||||||
// This works because unsigned arithmetic wraps around predictably
|
|
||||||
if ((now - this->scan_start_time_) > timeout_ms) {
|
|
||||||
// First time we've seen the timeout exceeded - wait one more loop iteration
|
|
||||||
// This ensures all components have had a chance to process pending events
|
|
||||||
// This is because esp32_ble may not have run yet and called
|
|
||||||
// gap_scan_event_handler yet when the loop unblocks
|
|
||||||
ESP_LOGW(TAG, "Scan timeout exceeded");
|
|
||||||
this->scan_timeout_state_ = ScanTimeoutState::EXCEEDED_WAIT;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
case ClientState::DISCOVERED:
|
||||||
case ScanTimeoutState::EXCEEDED_WAIT:
|
discovered++;
|
||||||
// We've waited at least one full loop iteration, and scan is still running
|
|
||||||
ESP_LOGE(TAG, "Scan never terminated, rebooting");
|
|
||||||
App.reboot();
|
|
||||||
break;
|
break;
|
||||||
|
case ClientState::SEARCHING:
|
||||||
case ScanTimeoutState::INACTIVE:
|
searching++;
|
||||||
// This case should be unreachable - scanner and timeout states are always synchronized
|
break;
|
||||||
|
case ClientState::CONNECTING:
|
||||||
|
case ClientState::READY_TO_CONNECT:
|
||||||
|
connecting++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (connecting != connecting_ || discovered != discovered_ || searching != searching_ ||
|
||||||
ClientStateCounts counts = this->count_client_states_();
|
disconnecting != disconnecting_) {
|
||||||
if (counts != this->client_state_counts_) {
|
connecting_ = connecting;
|
||||||
this->client_state_counts_ = counts;
|
discovered_ = discovered;
|
||||||
ESP_LOGD(TAG, "connecting: %d, discovered: %d, searching: %d, disconnecting: %d",
|
searching_ = searching;
|
||||||
this->client_state_counts_.connecting, this->client_state_counts_.discovered,
|
disconnecting_ = disconnecting;
|
||||||
this->client_state_counts_.searching, this->client_state_counts_.disconnecting);
|
ESP_LOGD(TAG, "connecting: %d, discovered: %d, searching: %d, disconnecting: %d", connecting_, discovered_,
|
||||||
|
searching_, disconnecting_);
|
||||||
}
|
}
|
||||||
|
bool promote_to_connecting = discovered && !searching && !connecting;
|
||||||
|
|
||||||
|
// Process scan results from lock-free SPSC ring buffer
|
||||||
|
// Consumer side: This runs in the main loop thread
|
||||||
|
if (this->scanner_state_ == ScannerState::RUNNING) {
|
||||||
|
// Load our own index with relaxed ordering (we're the only writer)
|
||||||
|
uint8_t read_idx = this->ring_read_index_.load(std::memory_order_relaxed);
|
||||||
|
|
||||||
|
// Load producer's index with acquire to see their latest writes
|
||||||
|
uint8_t write_idx = this->ring_write_index_.load(std::memory_order_acquire);
|
||||||
|
|
||||||
|
while (read_idx != write_idx) {
|
||||||
|
// Calculate how many contiguous results we can process in one batch
|
||||||
|
// If write > read: process all results from read to write
|
||||||
|
// If write <= read (wraparound): process from read to end of buffer first
|
||||||
|
size_t batch_size = (write_idx > read_idx) ? (write_idx - read_idx) : (SCAN_RESULT_BUFFER_SIZE - read_idx);
|
||||||
|
|
||||||
|
// Process the batch for raw advertisements
|
||||||
|
if (this->raw_advertisements_) {
|
||||||
|
for (auto *listener : this->listeners_) {
|
||||||
|
listener->parse_devices(&this->scan_ring_buffer_[read_idx], batch_size);
|
||||||
|
}
|
||||||
|
for (auto *client : this->clients_) {
|
||||||
|
client->parse_devices(&this->scan_ring_buffer_[read_idx], batch_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process individual results for parsed advertisements
|
||||||
|
if (this->parse_advertisements_) {
|
||||||
|
#ifdef USE_ESP32_BLE_DEVICE
|
||||||
|
for (size_t i = 0; i < batch_size; i++) {
|
||||||
|
BLEScanResult &scan_result = this->scan_ring_buffer_[read_idx + i];
|
||||||
|
ESPBTDevice device;
|
||||||
|
device.parse_scan_rst(scan_result);
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
for (auto *listener : this->listeners_) {
|
||||||
|
if (listener->parse_device(device))
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto *client : this->clients_) {
|
||||||
|
if (client->parse_device(device)) {
|
||||||
|
found = true;
|
||||||
|
if (!connecting && client->state() == ClientState::DISCOVERED) {
|
||||||
|
promote_to_connecting = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found && !this->scan_continuous_) {
|
||||||
|
this->print_bt_device_info(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // USE_ESP32_BLE_DEVICE
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update read index for entire batch
|
||||||
|
read_idx = (read_idx + batch_size) % SCAN_RESULT_BUFFER_SIZE;
|
||||||
|
|
||||||
|
// Store with release to ensure reads complete before index update
|
||||||
|
this->ring_read_index_.store(read_idx, std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log dropped results periodically
|
||||||
|
size_t dropped = this->scan_results_dropped_.exchange(0, std::memory_order_relaxed);
|
||||||
|
if (dropped > 0) {
|
||||||
|
ESP_LOGW(TAG, "Dropped %zu BLE scan results due to buffer overflow", dropped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this->scanner_state_ == ScannerState::STOPPED) {
|
||||||
|
this->end_of_scan_(); // Change state to IDLE
|
||||||
|
}
|
||||||
if (this->scanner_state_ == ScannerState::FAILED ||
|
if (this->scanner_state_ == ScannerState::FAILED ||
|
||||||
(this->scan_set_param_failed_ && this->scanner_state_ == ScannerState::RUNNING)) {
|
(this->scan_set_param_failed_ && this->scanner_state_ == ScannerState::RUNNING)) {
|
||||||
this->handle_scanner_failure_();
|
this->stop_scan_();
|
||||||
|
if (this->scan_start_fail_count_ == std::numeric_limits<uint8_t>::max()) {
|
||||||
|
ESP_LOGE(TAG, "Scan could not restart after %d attempts, rebooting to restore stack (IDF)",
|
||||||
|
std::numeric_limits<uint8_t>::max());
|
||||||
|
App.reboot();
|
||||||
|
}
|
||||||
|
if (this->scan_start_failed_) {
|
||||||
|
ESP_LOGE(TAG, "Scan start failed: %d", this->scan_start_failed_);
|
||||||
|
this->scan_start_failed_ = ESP_BT_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
if (this->scan_set_param_failed_) {
|
||||||
|
ESP_LOGE(TAG, "Scan set param failed: %d", this->scan_set_param_failed_);
|
||||||
|
this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
|
|
||||||
@@ -158,12 +218,13 @@ void ESP32BLETracker::loop() {
|
|||||||
https://github.com/espressif/esp-idf/issues/6688
|
https://github.com/espressif/esp-idf/issues/6688
|
||||||
|
|
||||||
*/
|
*/
|
||||||
bool promote_to_connecting = counts.discovered && !counts.searching && !counts.connecting;
|
if (this->scanner_state_ == ScannerState::IDLE && !connecting && !disconnecting && !promote_to_connecting) {
|
||||||
|
|
||||||
if (this->scanner_state_ == ScannerState::IDLE && !counts.connecting && !counts.disconnecting &&
|
|
||||||
!promote_to_connecting) {
|
|
||||||
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
|
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
|
||||||
this->update_coex_preference_(false);
|
if (this->coex_prefer_ble_) {
|
||||||
|
this->coex_prefer_ble_ = false;
|
||||||
|
ESP_LOGD(TAG, "Setting coexistence preference to balanced.");
|
||||||
|
esp_coex_preference_set(ESP_COEX_PREFER_BALANCE); // Reset to default
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
if (this->scan_continuous_) {
|
if (this->scan_continuous_) {
|
||||||
this->start_scan_(false); // first = false
|
this->start_scan_(false); // first = false
|
||||||
@@ -171,13 +232,31 @@ void ESP32BLETracker::loop() {
|
|||||||
}
|
}
|
||||||
// If there is a discovered client and no connecting
|
// If there is a discovered client and no connecting
|
||||||
// clients and no clients using the scanner to search for
|
// clients and no clients using the scanner to search for
|
||||||
// devices, then promote the discovered client to ready to connect.
|
// devices, then stop scanning and promote the discovered
|
||||||
// We check both RUNNING and IDLE states because:
|
// client to ready to connect.
|
||||||
// - RUNNING: gap_scan_event_handler initiates stop_scan_() but promotion can happen immediately
|
|
||||||
// - IDLE: Scanner has already stopped (naturally or by gap_scan_event_handler)
|
|
||||||
if (promote_to_connecting &&
|
if (promote_to_connecting &&
|
||||||
(this->scanner_state_ == ScannerState::RUNNING || this->scanner_state_ == ScannerState::IDLE)) {
|
(this->scanner_state_ == ScannerState::RUNNING || this->scanner_state_ == ScannerState::IDLE)) {
|
||||||
this->try_promote_discovered_clients_();
|
for (auto *client : this->clients_) {
|
||||||
|
if (client->state() == ClientState::DISCOVERED) {
|
||||||
|
if (this->scanner_state_ == ScannerState::RUNNING) {
|
||||||
|
ESP_LOGD(TAG, "Stopping scan to make connection");
|
||||||
|
this->stop_scan_();
|
||||||
|
} else if (this->scanner_state_ == ScannerState::IDLE) {
|
||||||
|
ESP_LOGD(TAG, "Promoting client to connect");
|
||||||
|
// We only want to promote one client at a time.
|
||||||
|
// once the scanner is fully stopped.
|
||||||
|
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
|
||||||
|
ESP_LOGD(TAG, "Setting coexistence to Bluetooth to make connection.");
|
||||||
|
if (!this->coex_prefer_ble_) {
|
||||||
|
this->coex_prefer_ble_ = true;
|
||||||
|
esp_coex_preference_set(ESP_COEX_PREFER_BT); // Prioritize Bluetooth
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
client->set_state(ClientState::READY_TO_CONNECT);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,11 +272,18 @@ void ESP32BLETracker::ble_before_disabled_event_handler() { this->stop_scan_();
|
|||||||
|
|
||||||
void ESP32BLETracker::stop_scan_() {
|
void ESP32BLETracker::stop_scan_() {
|
||||||
if (this->scanner_state_ != ScannerState::RUNNING && this->scanner_state_ != ScannerState::FAILED) {
|
if (this->scanner_state_ != ScannerState::RUNNING && this->scanner_state_ != ScannerState::FAILED) {
|
||||||
ESP_LOGE(TAG, "Cannot stop scan: %s", this->scanner_state_to_string_(this->scanner_state_));
|
if (this->scanner_state_ == ScannerState::IDLE) {
|
||||||
|
ESP_LOGE(TAG, "Scan is already stopped while trying to stop.");
|
||||||
|
} else if (this->scanner_state_ == ScannerState::STARTING) {
|
||||||
|
ESP_LOGE(TAG, "Scan is starting while trying to stop.");
|
||||||
|
} else if (this->scanner_state_ == ScannerState::STOPPING) {
|
||||||
|
ESP_LOGE(TAG, "Scan is already stopping while trying to stop.");
|
||||||
|
} else if (this->scanner_state_ == ScannerState::STOPPED) {
|
||||||
|
ESP_LOGE(TAG, "Scan is already stopped while trying to stop.");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Reset timeout state machine when stopping scan
|
this->cancel_timeout("scan");
|
||||||
this->scan_timeout_state_ = ScanTimeoutState::INACTIVE;
|
|
||||||
this->set_scanner_state_(ScannerState::STOPPING);
|
this->set_scanner_state_(ScannerState::STOPPING);
|
||||||
esp_err_t err = esp_ble_gap_stop_scanning();
|
esp_err_t err = esp_ble_gap_stop_scanning();
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
@@ -212,7 +298,17 @@ void ESP32BLETracker::start_scan_(bool first) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this->scanner_state_ != ScannerState::IDLE) {
|
if (this->scanner_state_ != ScannerState::IDLE) {
|
||||||
this->log_unexpected_state_("start scan", ScannerState::IDLE);
|
if (this->scanner_state_ == ScannerState::STARTING) {
|
||||||
|
ESP_LOGE(TAG, "Cannot start scan while already starting.");
|
||||||
|
} else if (this->scanner_state_ == ScannerState::RUNNING) {
|
||||||
|
ESP_LOGE(TAG, "Cannot start scan while already running.");
|
||||||
|
} else if (this->scanner_state_ == ScannerState::STOPPING) {
|
||||||
|
ESP_LOGE(TAG, "Cannot start scan while already stopping.");
|
||||||
|
} else if (this->scanner_state_ == ScannerState::FAILED) {
|
||||||
|
ESP_LOGE(TAG, "Cannot start scan while already failed.");
|
||||||
|
} else if (this->scanner_state_ == ScannerState::STOPPED) {
|
||||||
|
ESP_LOGE(TAG, "Cannot start scan while already stopped.");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->set_scanner_state_(ScannerState::STARTING);
|
this->set_scanner_state_(ScannerState::STARTING);
|
||||||
@@ -221,19 +317,18 @@ void ESP32BLETracker::start_scan_(bool first) {
|
|||||||
for (auto *listener : this->listeners_)
|
for (auto *listener : this->listeners_)
|
||||||
listener->on_scan_end();
|
listener->on_scan_end();
|
||||||
}
|
}
|
||||||
#ifdef USE_ESP32_BLE_DEVICE
|
|
||||||
this->already_discovered_.clear();
|
this->already_discovered_.clear();
|
||||||
#endif
|
|
||||||
this->scan_params_.scan_type = this->scan_active_ ? BLE_SCAN_TYPE_ACTIVE : BLE_SCAN_TYPE_PASSIVE;
|
this->scan_params_.scan_type = this->scan_active_ ? BLE_SCAN_TYPE_ACTIVE : BLE_SCAN_TYPE_PASSIVE;
|
||||||
this->scan_params_.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
|
this->scan_params_.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
|
||||||
this->scan_params_.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL;
|
this->scan_params_.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL;
|
||||||
this->scan_params_.scan_interval = this->scan_interval_;
|
this->scan_params_.scan_interval = this->scan_interval_;
|
||||||
this->scan_params_.scan_window = this->scan_window_;
|
this->scan_params_.scan_window = this->scan_window_;
|
||||||
|
|
||||||
// Start timeout monitoring in loop() instead of using scheduler
|
// Start timeout before scan is started. Otherwise scan never starts if any error.
|
||||||
// This prevents false reboots when the loop is blocked
|
this->set_timeout("scan", this->scan_duration_ * 2000, []() {
|
||||||
this->scan_start_time_ = App.get_loop_component_start_time();
|
ESP_LOGE(TAG, "Scan never terminated, rebooting to restore stack (IDF)");
|
||||||
this->scan_timeout_state_ = ScanTimeoutState::MONITORING;
|
App.reboot();
|
||||||
|
});
|
||||||
|
|
||||||
esp_err_t err = esp_ble_gap_set_scan_params(&this->scan_params_);
|
esp_err_t err = esp_ble_gap_set_scan_params(&this->scan_params_);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
@@ -247,6 +342,21 @@ void ESP32BLETracker::start_scan_(bool first) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ESP32BLETracker::end_of_scan_() {
|
||||||
|
// The lock must be held when calling this function.
|
||||||
|
if (this->scanner_state_ != ScannerState::STOPPED) {
|
||||||
|
ESP_LOGE(TAG, "end_of_scan_ called while scanner is not stopped.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ESP_LOGD(TAG, "End of scan, set scanner state to IDLE.");
|
||||||
|
this->already_discovered_.clear();
|
||||||
|
this->cancel_timeout("scan");
|
||||||
|
|
||||||
|
for (auto *listener : this->listeners_)
|
||||||
|
listener->on_scan_end();
|
||||||
|
this->set_scanner_state_(ScannerState::IDLE);
|
||||||
|
}
|
||||||
|
|
||||||
void ESP32BLETracker::register_client(ESPBTClient *client) {
|
void ESP32BLETracker::register_client(ESPBTClient *client) {
|
||||||
client->app_id = ++this->app_id_;
|
client->app_id = ++this->app_id_;
|
||||||
this->clients_.push_back(client);
|
this->clients_.push_back(client);
|
||||||
@@ -279,8 +389,6 @@ void ESP32BLETracker::recalculate_advertisement_parser_types() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||||
// Note: This handler is called from the main loop context, not directly from the BT task.
|
|
||||||
// The esp32_ble component queues events via enqueue_ble_event() and processes them in loop().
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
|
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
|
||||||
this->gap_scan_set_param_complete_(param->scan_param_cmpl);
|
this->gap_scan_set_param_complete_(param->scan_param_cmpl);
|
||||||
@@ -301,32 +409,51 @@ void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_ga
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ESP32BLETracker::gap_scan_event_handler(const BLEScanResult &scan_result) {
|
void ESP32BLETracker::gap_scan_event_handler(const BLEScanResult &scan_result) {
|
||||||
// Note: This handler is called from the main loop context via esp32_ble's event queue.
|
|
||||||
// We process advertisements immediately instead of buffering them.
|
|
||||||
ESP_LOGV(TAG, "gap_scan_result - event %d", scan_result.search_evt);
|
ESP_LOGV(TAG, "gap_scan_result - event %d", scan_result.search_evt);
|
||||||
|
|
||||||
if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
|
if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
|
||||||
// Process the scan result immediately
|
// Lock-free SPSC ring buffer write (Producer side)
|
||||||
bool found_discovered_client = this->process_scan_result_(scan_result);
|
// This runs in the ESP-IDF Bluetooth stack callback thread
|
||||||
|
// IMPORTANT: Only this thread writes to ring_write_index_
|
||||||
|
|
||||||
// If we found a discovered client that needs promotion, stop scanning
|
// Load our own index with relaxed ordering (we're the only writer)
|
||||||
// This replaces the promote_to_connecting logic from loop()
|
uint8_t write_idx = this->ring_write_index_.load(std::memory_order_relaxed);
|
||||||
if (found_discovered_client && this->scanner_state_ == ScannerState::RUNNING) {
|
uint8_t next_write_idx = (write_idx + 1) % SCAN_RESULT_BUFFER_SIZE;
|
||||||
ESP_LOGD(TAG, "Found discovered client, stopping scan for connection");
|
|
||||||
this->stop_scan_();
|
// Load consumer's index with acquire to see their latest updates
|
||||||
|
uint8_t read_idx = this->ring_read_index_.load(std::memory_order_acquire);
|
||||||
|
|
||||||
|
// Check if buffer is full
|
||||||
|
if (next_write_idx != read_idx) {
|
||||||
|
// Write to ring buffer
|
||||||
|
this->scan_ring_buffer_[write_idx] = scan_result;
|
||||||
|
|
||||||
|
// Store with release to ensure the write is visible before index update
|
||||||
|
this->ring_write_index_.store(next_write_idx, std::memory_order_release);
|
||||||
|
} else {
|
||||||
|
// Buffer full, track dropped results
|
||||||
|
this->scan_results_dropped_.fetch_add(1, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
} else if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) {
|
} else if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) {
|
||||||
// Scan finished on its own
|
// Scan finished on its own
|
||||||
if (this->scanner_state_ != ScannerState::RUNNING) {
|
if (this->scanner_state_ != ScannerState::RUNNING) {
|
||||||
this->log_unexpected_state_("scan complete", ScannerState::RUNNING);
|
if (this->scanner_state_ == ScannerState::STOPPING) {
|
||||||
|
ESP_LOGE(TAG, "Scan was not running when scan completed.");
|
||||||
|
} else if (this->scanner_state_ == ScannerState::STARTING) {
|
||||||
|
ESP_LOGE(TAG, "Scan was not started when scan completed.");
|
||||||
|
} else if (this->scanner_state_ == ScannerState::FAILED) {
|
||||||
|
ESP_LOGE(TAG, "Scan was in failed state when scan completed.");
|
||||||
|
} else if (this->scanner_state_ == ScannerState::IDLE) {
|
||||||
|
ESP_LOGE(TAG, "Scan was idle when scan completed.");
|
||||||
|
} else if (this->scanner_state_ == ScannerState::STOPPED) {
|
||||||
|
ESP_LOGE(TAG, "Scan was stopped when scan completed.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Scan completed naturally, perform cleanup and transition to IDLE
|
this->set_scanner_state_(ScannerState::STOPPED);
|
||||||
this->cleanup_scan_state_(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m) {
|
void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m) {
|
||||||
// Called from main loop context via gap_event_handler after being queued from BT task
|
|
||||||
ESP_LOGV(TAG, "gap_scan_set_param_complete - status %d", param.status);
|
ESP_LOGV(TAG, "gap_scan_set_param_complete - status %d", param.status);
|
||||||
if (param.status == ESP_BT_STATUS_DONE) {
|
if (param.status == ESP_BT_STATUS_DONE) {
|
||||||
this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
|
this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
|
||||||
@@ -336,11 +463,20 @@ void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param ¶m) {
|
void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param ¶m) {
|
||||||
// Called from main loop context via gap_event_handler after being queued from BT task
|
|
||||||
ESP_LOGV(TAG, "gap_scan_start_complete - status %d", param.status);
|
ESP_LOGV(TAG, "gap_scan_start_complete - status %d", param.status);
|
||||||
this->scan_start_failed_ = param.status;
|
this->scan_start_failed_ = param.status;
|
||||||
if (this->scanner_state_ != ScannerState::STARTING) {
|
if (this->scanner_state_ != ScannerState::STARTING) {
|
||||||
this->log_unexpected_state_("start complete", ScannerState::STARTING);
|
if (this->scanner_state_ == ScannerState::RUNNING) {
|
||||||
|
ESP_LOGE(TAG, "Scan was already running when start complete.");
|
||||||
|
} else if (this->scanner_state_ == ScannerState::STOPPING) {
|
||||||
|
ESP_LOGE(TAG, "Scan was stopping when start complete.");
|
||||||
|
} else if (this->scanner_state_ == ScannerState::FAILED) {
|
||||||
|
ESP_LOGE(TAG, "Scan was in failed state when start complete.");
|
||||||
|
} else if (this->scanner_state_ == ScannerState::IDLE) {
|
||||||
|
ESP_LOGE(TAG, "Scan was idle when start complete.");
|
||||||
|
} else if (this->scanner_state_ == ScannerState::STOPPED) {
|
||||||
|
ESP_LOGE(TAG, "Scan was stopped when start complete.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (param.status == ESP_BT_STATUS_SUCCESS) {
|
if (param.status == ESP_BT_STATUS_SUCCESS) {
|
||||||
this->scan_start_fail_count_ = 0;
|
this->scan_start_fail_count_ = 0;
|
||||||
@@ -354,15 +490,21 @@ void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m) {
|
void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m) {
|
||||||
// Called from main loop context via gap_event_handler after being queued from BT task
|
|
||||||
// This allows us to safely transition to IDLE state and perform cleanup without race conditions
|
|
||||||
ESP_LOGV(TAG, "gap_scan_stop_complete - status %d", param.status);
|
ESP_LOGV(TAG, "gap_scan_stop_complete - status %d", param.status);
|
||||||
if (this->scanner_state_ != ScannerState::STOPPING) {
|
if (this->scanner_state_ != ScannerState::STOPPING) {
|
||||||
this->log_unexpected_state_("stop complete", ScannerState::STOPPING);
|
if (this->scanner_state_ == ScannerState::RUNNING) {
|
||||||
|
ESP_LOGE(TAG, "Scan was not running when stop complete.");
|
||||||
|
} else if (this->scanner_state_ == ScannerState::STARTING) {
|
||||||
|
ESP_LOGE(TAG, "Scan was not started when stop complete.");
|
||||||
|
} else if (this->scanner_state_ == ScannerState::FAILED) {
|
||||||
|
ESP_LOGE(TAG, "Scan was in failed state when stop complete.");
|
||||||
|
} else if (this->scanner_state_ == ScannerState::IDLE) {
|
||||||
|
ESP_LOGE(TAG, "Scan was idle when stop complete.");
|
||||||
|
} else if (this->scanner_state_ == ScannerState::STOPPED) {
|
||||||
|
ESP_LOGE(TAG, "Scan was stopped when stop complete.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
this->set_scanner_state_(ScannerState::STOPPED);
|
||||||
// Perform cleanup and transition to IDLE
|
|
||||||
this->cleanup_scan_state_(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||||
@@ -639,10 +781,28 @@ void ESP32BLETracker::dump_config() {
|
|||||||
" Continuous Scanning: %s",
|
" Continuous Scanning: %s",
|
||||||
this->scan_duration_, this->scan_interval_ * 0.625f, this->scan_window_ * 0.625f,
|
this->scan_duration_, this->scan_interval_ * 0.625f, this->scan_window_ * 0.625f,
|
||||||
this->scan_active_ ? "ACTIVE" : "PASSIVE", YESNO(this->scan_continuous_));
|
this->scan_active_ ? "ACTIVE" : "PASSIVE", YESNO(this->scan_continuous_));
|
||||||
ESP_LOGCONFIG(TAG, " Scanner State: %s", this->scanner_state_to_string_(this->scanner_state_));
|
switch (this->scanner_state_) {
|
||||||
ESP_LOGCONFIG(TAG, " Connecting: %d, discovered: %d, searching: %d, disconnecting: %d",
|
case ScannerState::IDLE:
|
||||||
this->client_state_counts_.connecting, this->client_state_counts_.discovered,
|
ESP_LOGCONFIG(TAG, " Scanner State: IDLE");
|
||||||
this->client_state_counts_.searching, this->client_state_counts_.disconnecting);
|
break;
|
||||||
|
case ScannerState::STARTING:
|
||||||
|
ESP_LOGCONFIG(TAG, " Scanner State: STARTING");
|
||||||
|
break;
|
||||||
|
case ScannerState::RUNNING:
|
||||||
|
ESP_LOGCONFIG(TAG, " Scanner State: RUNNING");
|
||||||
|
break;
|
||||||
|
case ScannerState::STOPPING:
|
||||||
|
ESP_LOGCONFIG(TAG, " Scanner State: STOPPING");
|
||||||
|
break;
|
||||||
|
case ScannerState::STOPPED:
|
||||||
|
ESP_LOGCONFIG(TAG, " Scanner State: STOPPED");
|
||||||
|
break;
|
||||||
|
case ScannerState::FAILED:
|
||||||
|
ESP_LOGCONFIG(TAG, " Scanner State: FAILED");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ESP_LOGCONFIG(TAG, " Connecting: %d, discovered: %d, searching: %d, disconnecting: %d", connecting_, discovered_,
|
||||||
|
searching_, disconnecting_);
|
||||||
if (this->scan_start_fail_count_) {
|
if (this->scan_start_fail_count_) {
|
||||||
ESP_LOGCONFIG(TAG, " Scan Start Fail Count: %d", this->scan_start_fail_count_);
|
ESP_LOGCONFIG(TAG, " Scan Start Fail Count: %d", this->scan_start_fail_count_);
|
||||||
}
|
}
|
||||||
@@ -719,158 +879,8 @@ bool ESPBTDevice::resolve_irk(const uint8_t *irk) const {
|
|||||||
return ecb_ciphertext[15] == (addr64 & 0xff) && ecb_ciphertext[14] == ((addr64 >> 8) & 0xff) &&
|
return ecb_ciphertext[15] == (addr64 & 0xff) && ecb_ciphertext[14] == ((addr64 >> 8) & 0xff) &&
|
||||||
ecb_ciphertext[13] == ((addr64 >> 16) & 0xff);
|
ecb_ciphertext[13] == ((addr64 >> 16) & 0xff);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ESP32BLETracker::has_connecting_clients_() const {
|
|
||||||
for (auto *client : this->clients_) {
|
|
||||||
auto state = client->state();
|
|
||||||
if (state == ClientState::CONNECTING || state == ClientState::READY_TO_CONNECT) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif // USE_ESP32_BLE_DEVICE
|
#endif // USE_ESP32_BLE_DEVICE
|
||||||
|
|
||||||
bool ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) {
|
|
||||||
bool found_discovered_client = false;
|
|
||||||
|
|
||||||
// Process raw advertisements
|
|
||||||
if (this->raw_advertisements_) {
|
|
||||||
for (auto *listener : this->listeners_) {
|
|
||||||
listener->parse_devices(&scan_result, 1);
|
|
||||||
}
|
|
||||||
for (auto *client : this->clients_) {
|
|
||||||
client->parse_devices(&scan_result, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process parsed advertisements
|
|
||||||
if (this->parse_advertisements_) {
|
|
||||||
#ifdef USE_ESP32_BLE_DEVICE
|
|
||||||
ESPBTDevice device;
|
|
||||||
device.parse_scan_rst(scan_result);
|
|
||||||
|
|
||||||
bool found = false;
|
|
||||||
for (auto *listener : this->listeners_) {
|
|
||||||
if (listener->parse_device(device))
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto *client : this->clients_) {
|
|
||||||
if (client->parse_device(device)) {
|
|
||||||
found = true;
|
|
||||||
// Check if this client is discovered and needs promotion
|
|
||||||
if (client->state() == ClientState::DISCOVERED) {
|
|
||||||
// Only check for connecting clients if we found a discovered client
|
|
||||||
// This matches the original logic: !connecting && client->state() == DISCOVERED
|
|
||||||
if (!this->has_connecting_clients_()) {
|
|
||||||
found_discovered_client = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found && !this->scan_continuous_) {
|
|
||||||
this->print_bt_device_info(device);
|
|
||||||
}
|
|
||||||
#endif // USE_ESP32_BLE_DEVICE
|
|
||||||
}
|
|
||||||
|
|
||||||
return found_discovered_client;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ESP32BLETracker::cleanup_scan_state_(bool is_stop_complete) {
|
|
||||||
ESP_LOGD(TAG, "Scan %scomplete, set scanner state to IDLE.", is_stop_complete ? "stop " : "");
|
|
||||||
#ifdef USE_ESP32_BLE_DEVICE
|
|
||||||
this->already_discovered_.clear();
|
|
||||||
#endif
|
|
||||||
// Reset timeout state machine instead of cancelling scheduler timeout
|
|
||||||
this->scan_timeout_state_ = ScanTimeoutState::INACTIVE;
|
|
||||||
|
|
||||||
for (auto *listener : this->listeners_)
|
|
||||||
listener->on_scan_end();
|
|
||||||
|
|
||||||
this->set_scanner_state_(ScannerState::IDLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ESP32BLETracker::handle_scanner_failure_() {
|
|
||||||
this->stop_scan_();
|
|
||||||
if (this->scan_start_fail_count_ == std::numeric_limits<uint8_t>::max()) {
|
|
||||||
ESP_LOGE(TAG, "Scan could not restart after %d attempts, rebooting to restore stack (IDF)",
|
|
||||||
std::numeric_limits<uint8_t>::max());
|
|
||||||
App.reboot();
|
|
||||||
}
|
|
||||||
if (this->scan_start_failed_) {
|
|
||||||
ESP_LOGE(TAG, "Scan start failed: %d", this->scan_start_failed_);
|
|
||||||
this->scan_start_failed_ = ESP_BT_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
if (this->scan_set_param_failed_) {
|
|
||||||
ESP_LOGE(TAG, "Scan set param failed: %d", this->scan_set_param_failed_);
|
|
||||||
this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ESP32BLETracker::try_promote_discovered_clients_() {
|
|
||||||
// Only promote the first discovered client to avoid multiple simultaneous connections
|
|
||||||
for (auto *client : this->clients_) {
|
|
||||||
if (client->state() != ClientState::DISCOVERED) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->scanner_state_ == ScannerState::RUNNING) {
|
|
||||||
ESP_LOGD(TAG, "Stopping scan to make connection");
|
|
||||||
this->stop_scan_();
|
|
||||||
// Don't wait for scan stop complete - promote immediately.
|
|
||||||
// This is safe because ESP-IDF processes BLE commands sequentially through its internal mailbox queue.
|
|
||||||
// This guarantees that the stop scan command will be fully processed before any subsequent connect command,
|
|
||||||
// preventing race conditions or overlapping operations.
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Promoting client to connect");
|
|
||||||
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
|
|
||||||
this->update_coex_preference_(true);
|
|
||||||
#endif
|
|
||||||
client->set_state(ClientState::READY_TO_CONNECT);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *ESP32BLETracker::scanner_state_to_string_(ScannerState state) const {
|
|
||||||
switch (state) {
|
|
||||||
case ScannerState::IDLE:
|
|
||||||
return "IDLE";
|
|
||||||
case ScannerState::STARTING:
|
|
||||||
return "STARTING";
|
|
||||||
case ScannerState::RUNNING:
|
|
||||||
return "RUNNING";
|
|
||||||
case ScannerState::STOPPING:
|
|
||||||
return "STOPPING";
|
|
||||||
case ScannerState::FAILED:
|
|
||||||
return "FAILED";
|
|
||||||
default:
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ESP32BLETracker::log_unexpected_state_(const char *operation, ScannerState expected_state) const {
|
|
||||||
ESP_LOGE(TAG, "Unexpected state: %s on %s, expected: %s", this->scanner_state_to_string_(this->scanner_state_),
|
|
||||||
operation, this->scanner_state_to_string_(expected_state));
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
|
|
||||||
void ESP32BLETracker::update_coex_preference_(bool force_ble) {
|
|
||||||
if (force_ble && !this->coex_prefer_ble_) {
|
|
||||||
ESP_LOGD(TAG, "Setting coexistence to Bluetooth to make connection.");
|
|
||||||
this->coex_prefer_ble_ = true;
|
|
||||||
esp_coex_preference_set(ESP_COEX_PREFER_BT); // Prioritize Bluetooth
|
|
||||||
} else if (!force_ble && this->coex_prefer_ble_) {
|
|
||||||
ESP_LOGD(TAG, "Setting coexistence preference to balanced.");
|
|
||||||
this->coex_prefer_ble_ = false;
|
|
||||||
esp_coex_preference_set(ESP_COEX_PREFER_BALANCE); // Reset to default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace esphome::esp32_ble_tracker
|
} // namespace esphome::esp32_ble_tracker
|
||||||
|
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
|
@@ -6,6 +6,7 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <atomic>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -20,7 +21,6 @@
|
|||||||
|
|
||||||
#include "esphome/components/esp32_ble/ble.h"
|
#include "esphome/components/esp32_ble/ble.h"
|
||||||
#include "esphome/components/esp32_ble/ble_uuid.h"
|
#include "esphome/components/esp32_ble/ble_uuid.h"
|
||||||
#include "esphome/components/esp32_ble/ble_scan_result.h"
|
|
||||||
|
|
||||||
namespace esphome::esp32_ble_tracker {
|
namespace esphome::esp32_ble_tracker {
|
||||||
|
|
||||||
@@ -33,12 +33,10 @@ enum AdvertisementParserType {
|
|||||||
RAW_ADVERTISEMENTS,
|
RAW_ADVERTISEMENTS,
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef USE_ESP32_BLE_UUID
|
|
||||||
struct ServiceData {
|
struct ServiceData {
|
||||||
ESPBTUUID uuid;
|
ESPBTUUID uuid;
|
||||||
adv_data_t data;
|
adv_data_t data;
|
||||||
};
|
};
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_ESP32_BLE_DEVICE
|
#ifdef USE_ESP32_BLE_DEVICE
|
||||||
class ESPBLEiBeacon {
|
class ESPBLEiBeacon {
|
||||||
@@ -138,20 +136,6 @@ class ESPBTDeviceListener {
|
|||||||
ESP32BLETracker *parent_{nullptr};
|
ESP32BLETracker *parent_{nullptr};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ClientStateCounts {
|
|
||||||
uint8_t connecting = 0;
|
|
||||||
uint8_t discovered = 0;
|
|
||||||
uint8_t searching = 0;
|
|
||||||
uint8_t disconnecting = 0;
|
|
||||||
|
|
||||||
bool operator==(const ClientStateCounts &other) const {
|
|
||||||
return connecting == other.connecting && discovered == other.discovered && searching == other.searching &&
|
|
||||||
disconnecting == other.disconnecting;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator!=(const ClientStateCounts &other) const { return !(*this == other); }
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class ClientState : uint8_t {
|
enum class ClientState : uint8_t {
|
||||||
// Connection is allocated
|
// Connection is allocated
|
||||||
INIT,
|
INIT,
|
||||||
@@ -174,21 +158,20 @@ enum class ClientState : uint8_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
enum class ScannerState {
|
enum class ScannerState {
|
||||||
// Scanner is idle, init state
|
// Scanner is idle, init state, set from the main loop when processing STOPPED
|
||||||
IDLE,
|
IDLE,
|
||||||
// Scanner is starting
|
// Scanner is starting, set from the main loop only
|
||||||
STARTING,
|
STARTING,
|
||||||
// Scanner is running
|
// Scanner is running, set from the ESP callback only
|
||||||
RUNNING,
|
RUNNING,
|
||||||
// Scanner failed to start
|
// Scanner failed to start, set from the ESP callback only
|
||||||
FAILED,
|
FAILED,
|
||||||
// Scanner is stopping
|
// Scanner is stopping, set from the main loop only
|
||||||
STOPPING,
|
STOPPING,
|
||||||
|
// Scanner is stopped, set from the ESP callback only
|
||||||
|
STOPPED,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper function to convert ClientState to string
|
|
||||||
const char *client_state_to_string(ClientState state);
|
|
||||||
|
|
||||||
enum class ConnectionType : uint8_t {
|
enum class ConnectionType : uint8_t {
|
||||||
// The default connection type, we hold all the services in ram
|
// The default connection type, we hold all the services in ram
|
||||||
// for the duration of the connection.
|
// for the duration of the connection.
|
||||||
@@ -279,6 +262,8 @@ class ESP32BLETracker : public Component,
|
|||||||
void stop_scan_();
|
void stop_scan_();
|
||||||
/// Start a single scan by setting up the parameters and doing some esp-idf calls.
|
/// Start a single scan by setting up the parameters and doing some esp-idf calls.
|
||||||
void start_scan_(bool first);
|
void start_scan_(bool first);
|
||||||
|
/// Called when a scan ends
|
||||||
|
void end_of_scan_();
|
||||||
/// Called when a `ESP_GAP_BLE_SCAN_RESULT_EVT` event is received.
|
/// Called when a `ESP_GAP_BLE_SCAN_RESULT_EVT` event is received.
|
||||||
void gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m);
|
void gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m);
|
||||||
/// Called when a `ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT` event is received.
|
/// Called when a `ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT` event is received.
|
||||||
@@ -289,94 +274,47 @@ class ESP32BLETracker : public Component,
|
|||||||
void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m);
|
void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m);
|
||||||
/// Called to set the scanner state. Will also call callbacks to let listeners know when state is changed.
|
/// Called to set the scanner state. Will also call callbacks to let listeners know when state is changed.
|
||||||
void set_scanner_state_(ScannerState state);
|
void set_scanner_state_(ScannerState state);
|
||||||
/// Common cleanup logic when transitioning scanner to IDLE state
|
|
||||||
void cleanup_scan_state_(bool is_stop_complete);
|
|
||||||
/// Process a single scan result immediately
|
|
||||||
/// Returns true if a discovered client needs promotion to READY_TO_CONNECT
|
|
||||||
bool process_scan_result_(const BLEScanResult &scan_result);
|
|
||||||
#ifdef USE_ESP32_BLE_DEVICE
|
|
||||||
/// Check if any clients are in connecting or ready to connect state
|
|
||||||
bool has_connecting_clients_() const;
|
|
||||||
#endif
|
|
||||||
/// Handle scanner failure states
|
|
||||||
void handle_scanner_failure_();
|
|
||||||
/// Try to promote discovered clients to ready to connect
|
|
||||||
void try_promote_discovered_clients_();
|
|
||||||
/// Convert scanner state enum to string for logging
|
|
||||||
const char *scanner_state_to_string_(ScannerState state) const;
|
|
||||||
/// Log an unexpected scanner state
|
|
||||||
void log_unexpected_state_(const char *operation, ScannerState expected_state) const;
|
|
||||||
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
|
|
||||||
/// Update BLE coexistence preference
|
|
||||||
void update_coex_preference_(bool force_ble);
|
|
||||||
#endif
|
|
||||||
/// Count clients in each state
|
|
||||||
ClientStateCounts count_client_states_() const {
|
|
||||||
ClientStateCounts counts;
|
|
||||||
for (auto *client : this->clients_) {
|
|
||||||
switch (client->state()) {
|
|
||||||
case ClientState::DISCONNECTING:
|
|
||||||
counts.disconnecting++;
|
|
||||||
break;
|
|
||||||
case ClientState::DISCOVERED:
|
|
||||||
counts.discovered++;
|
|
||||||
break;
|
|
||||||
case ClientState::SEARCHING:
|
|
||||||
counts.searching++;
|
|
||||||
break;
|
|
||||||
case ClientState::CONNECTING:
|
|
||||||
case ClientState::READY_TO_CONNECT:
|
|
||||||
counts.connecting++;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return counts;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group 1: Large objects (12+ bytes) - vectors and callback manager
|
uint8_t app_id_{0};
|
||||||
std::vector<ESPBTDeviceListener *> listeners_;
|
|
||||||
std::vector<ESPBTClient *> clients_;
|
|
||||||
CallbackManager<void(ScannerState)> scanner_state_callbacks_;
|
|
||||||
#ifdef USE_ESP32_BLE_DEVICE
|
|
||||||
/// Vector of addresses that have already been printed in print_bt_device_info
|
/// Vector of addresses that have already been printed in print_bt_device_info
|
||||||
std::vector<uint64_t> already_discovered_;
|
std::vector<uint64_t> already_discovered_;
|
||||||
#endif
|
std::vector<ESPBTDeviceListener *> listeners_;
|
||||||
|
/// Client parameters.
|
||||||
// Group 2: Structs (aligned to 4 bytes)
|
std::vector<ESPBTClient *> clients_;
|
||||||
/// A structure holding the ESP BLE scan parameters.
|
/// A structure holding the ESP BLE scan parameters.
|
||||||
esp_ble_scan_params_t scan_params_;
|
esp_ble_scan_params_t scan_params_;
|
||||||
ClientStateCounts client_state_counts_;
|
|
||||||
|
|
||||||
// Group 3: 4-byte types
|
|
||||||
/// The interval in seconds to perform scans.
|
/// The interval in seconds to perform scans.
|
||||||
uint32_t scan_duration_;
|
uint32_t scan_duration_;
|
||||||
uint32_t scan_interval_;
|
uint32_t scan_interval_;
|
||||||
uint32_t scan_window_;
|
uint32_t scan_window_;
|
||||||
esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS};
|
|
||||||
esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS};
|
|
||||||
|
|
||||||
// Group 4: 1-byte types (enums, uint8_t, bool)
|
|
||||||
uint8_t app_id_{0};
|
|
||||||
uint8_t scan_start_fail_count_{0};
|
uint8_t scan_start_fail_count_{0};
|
||||||
ScannerState scanner_state_{ScannerState::IDLE};
|
|
||||||
bool scan_continuous_;
|
bool scan_continuous_;
|
||||||
bool scan_active_;
|
bool scan_active_;
|
||||||
|
ScannerState scanner_state_{ScannerState::IDLE};
|
||||||
|
CallbackManager<void(ScannerState)> scanner_state_callbacks_;
|
||||||
bool ble_was_disabled_{true};
|
bool ble_was_disabled_{true};
|
||||||
bool raw_advertisements_{false};
|
bool raw_advertisements_{false};
|
||||||
bool parse_advertisements_{false};
|
bool parse_advertisements_{false};
|
||||||
|
|
||||||
|
// Lock-free Single-Producer Single-Consumer (SPSC) ring buffer for scan results
|
||||||
|
// Producer: ESP-IDF Bluetooth stack callback (gap_scan_event_handler)
|
||||||
|
// Consumer: ESPHome main loop (loop() method)
|
||||||
|
// This design ensures zero blocking in the BT callback and prevents scan result loss
|
||||||
|
BLEScanResult *scan_ring_buffer_;
|
||||||
|
std::atomic<uint8_t> ring_write_index_{0}; // Written only by BT callback (producer)
|
||||||
|
std::atomic<uint8_t> ring_read_index_{0}; // Written only by main loop (consumer)
|
||||||
|
std::atomic<uint16_t> scan_results_dropped_{0}; // Tracks buffer overflow events
|
||||||
|
|
||||||
|
esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS};
|
||||||
|
esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS};
|
||||||
|
int connecting_{0};
|
||||||
|
int discovered_{0};
|
||||||
|
int searching_{0};
|
||||||
|
int disconnecting_{0};
|
||||||
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
|
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
|
||||||
bool coex_prefer_ble_{false};
|
bool coex_prefer_ble_{false};
|
||||||
#endif
|
#endif
|
||||||
// Scan timeout state machine
|
|
||||||
enum class ScanTimeoutState : uint8_t {
|
|
||||||
INACTIVE, // No timeout monitoring
|
|
||||||
MONITORING, // Actively monitoring for timeout
|
|
||||||
EXCEEDED_WAIT, // Timeout exceeded, waiting one loop before reboot
|
|
||||||
};
|
|
||||||
uint32_t scan_start_time_{0};
|
|
||||||
ScanTimeoutState scan_timeout_state_{ScanTimeoutState::INACTIVE};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// NOLINTNEXTLINE
|
// NOLINTNEXTLINE
|
||||||
|
@@ -345,7 +345,7 @@ async def to_code(config):
|
|||||||
cg.add_define("USE_CAMERA")
|
cg.add_define("USE_CAMERA")
|
||||||
|
|
||||||
if CORE.using_esp_idf:
|
if CORE.using_esp_idf:
|
||||||
add_idf_component(name="espressif/esp32-camera", ref="2.1.1")
|
add_idf_component(name="espressif/esp32-camera", ref="2.0.15")
|
||||||
|
|
||||||
for conf in config.get(CONF_ON_STREAM_START, []):
|
for conf in config.get(CONF_ON_STREAM_START, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
|
@@ -2,7 +2,11 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32) || defined(USE_ESP32_VARIANT_ESP32S2)
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
|
#ifdef USE_ARDUINO
|
||||||
|
#include <esp32-hal-dac.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace esp32_dac {
|
namespace esp32_dac {
|
||||||
@@ -19,12 +23,18 @@ void ESP32DAC::setup() {
|
|||||||
this->pin_->setup();
|
this->pin_->setup();
|
||||||
this->turn_off();
|
this->turn_off();
|
||||||
|
|
||||||
|
#ifdef USE_ESP_IDF
|
||||||
const dac_channel_t channel = this->pin_->get_pin() == DAC0_PIN ? DAC_CHAN_0 : DAC_CHAN_1;
|
const dac_channel_t channel = this->pin_->get_pin() == DAC0_PIN ? DAC_CHAN_0 : DAC_CHAN_1;
|
||||||
const dac_oneshot_config_t oneshot_cfg{channel};
|
const dac_oneshot_config_t oneshot_cfg{channel};
|
||||||
dac_oneshot_new_channel(&oneshot_cfg, &this->dac_handle_);
|
dac_oneshot_new_channel(&oneshot_cfg, &this->dac_handle_);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESP32DAC::on_safe_shutdown() { dac_oneshot_del_channel(this->dac_handle_); }
|
void ESP32DAC::on_safe_shutdown() {
|
||||||
|
#ifdef USE_ESP_IDF
|
||||||
|
dac_oneshot_del_channel(this->dac_handle_);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void ESP32DAC::dump_config() {
|
void ESP32DAC::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "ESP32 DAC:");
|
ESP_LOGCONFIG(TAG, "ESP32 DAC:");
|
||||||
@@ -38,10 +48,15 @@ void ESP32DAC::write_state(float state) {
|
|||||||
|
|
||||||
state = state * 255;
|
state = state * 255;
|
||||||
|
|
||||||
|
#ifdef USE_ESP_IDF
|
||||||
dac_oneshot_output_voltage(this->dac_handle_, state);
|
dac_oneshot_output_voltage(this->dac_handle_, state);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ARDUINO
|
||||||
|
dacWrite(this->pin_->get_pin(), state);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esp32_dac
|
} // namespace esp32_dac
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_ESP32_VARIANT_ESP32 || USE_ESP32_VARIANT_ESP32S2
|
#endif
|
||||||
|
@@ -1,13 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/components/output/float_output.h"
|
|
||||||
#include "esphome/core/automation.h"
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#include "esphome/components/output/float_output.h"
|
||||||
|
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32) || defined(USE_ESP32_VARIANT_ESP32S2)
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
|
#ifdef USE_ESP_IDF
|
||||||
#include <driver/dac_oneshot.h>
|
#include <driver/dac_oneshot.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace esp32_dac {
|
namespace esp32_dac {
|
||||||
@@ -27,10 +29,12 @@ class ESP32DAC : public output::FloatOutput, public Component {
|
|||||||
void write_state(float state) override;
|
void write_state(float state) override;
|
||||||
|
|
||||||
InternalGPIOPin *pin_;
|
InternalGPIOPin *pin_;
|
||||||
|
#ifdef USE_ESP_IDF
|
||||||
dac_oneshot_handle_t dac_handle_;
|
dac_oneshot_handle_t dac_handle_;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esp32_dac
|
} // namespace esp32_dac
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_ESP32_VARIANT_ESP32 || USE_ESP32_VARIANT_ESP32S2
|
#endif
|
||||||
|
@@ -42,6 +42,9 @@ static size_t IRAM_ATTR HOT encoder_callback(const void *data, size_t size, size
|
|||||||
symbols[i] = params->bit0;
|
symbols[i] = params->bit0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ((index + 1) >= size && params->reset.duration0 == 0 && params->reset.duration1 == 0) {
|
||||||
|
*done = true;
|
||||||
|
}
|
||||||
return RMT_SYMBOLS_PER_BYTE;
|
return RMT_SYMBOLS_PER_BYTE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +110,7 @@ void ESP32RMTLEDStripLightOutput::setup() {
|
|||||||
memset(&encoder, 0, sizeof(encoder));
|
memset(&encoder, 0, sizeof(encoder));
|
||||||
encoder.callback = encoder_callback;
|
encoder.callback = encoder_callback;
|
||||||
encoder.arg = &this->params_;
|
encoder.arg = &this->params_;
|
||||||
encoder.min_chunk_size = RMT_SYMBOLS_PER_BYTE;
|
encoder.min_chunk_size = 8;
|
||||||
if (rmt_new_simple_encoder(&encoder, &this->encoder_) != ESP_OK) {
|
if (rmt_new_simple_encoder(&encoder, &this->encoder_) != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "Encoder creation failed");
|
ESP_LOGE(TAG, "Encoder creation failed");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
|
@@ -171,8 +171,8 @@ class ESP32TouchComponent : public Component {
|
|||||||
// based on the filter configuration
|
// based on the filter configuration
|
||||||
uint32_t read_touch_value(touch_pad_t pad) const;
|
uint32_t read_touch_value(touch_pad_t pad) const;
|
||||||
|
|
||||||
// Helper to update touch state with a known state and value
|
// Helper to update touch state with a known state
|
||||||
void update_touch_state_(ESP32TouchBinarySensor *child, bool is_touched, uint32_t value);
|
void update_touch_state_(ESP32TouchBinarySensor *child, bool is_touched);
|
||||||
|
|
||||||
// Helper to read touch value and update state for a given child
|
// Helper to read touch value and update state for a given child
|
||||||
bool check_and_update_touch_state_(ESP32TouchBinarySensor *child);
|
bool check_and_update_touch_state_(ESP32TouchBinarySensor *child);
|
||||||
@@ -234,13 +234,9 @@ class ESP32TouchBinarySensor : public binary_sensor::BinarySensor {
|
|||||||
touch_pad_t get_touch_pad() const { return this->touch_pad_; }
|
touch_pad_t get_touch_pad() const { return this->touch_pad_; }
|
||||||
uint32_t get_threshold() const { return this->threshold_; }
|
uint32_t get_threshold() const { return this->threshold_; }
|
||||||
void set_threshold(uint32_t threshold) { this->threshold_ = threshold; }
|
void set_threshold(uint32_t threshold) { this->threshold_ = threshold; }
|
||||||
|
#ifdef USE_ESP32_VARIANT_ESP32
|
||||||
/// Get the raw touch measurement value.
|
|
||||||
/// @note Although this method may appear unused within the component, it is a public API
|
|
||||||
/// used by lambdas in user configurations for custom touch value processing.
|
|
||||||
/// @return The current raw touch sensor reading
|
|
||||||
uint32_t get_value() const { return this->value_; }
|
uint32_t get_value() const { return this->value_; }
|
||||||
|
#endif
|
||||||
uint32_t get_wakeup_threshold() const { return this->wakeup_threshold_; }
|
uint32_t get_wakeup_threshold() const { return this->wakeup_threshold_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -249,8 +245,9 @@ class ESP32TouchBinarySensor : public binary_sensor::BinarySensor {
|
|||||||
touch_pad_t touch_pad_{TOUCH_PAD_MAX};
|
touch_pad_t touch_pad_{TOUCH_PAD_MAX};
|
||||||
uint32_t threshold_{0};
|
uint32_t threshold_{0};
|
||||||
uint32_t benchmark_{};
|
uint32_t benchmark_{};
|
||||||
/// Stores the last raw touch measurement value.
|
#ifdef USE_ESP32_VARIANT_ESP32
|
||||||
uint32_t value_{0};
|
uint32_t value_{0};
|
||||||
|
#endif
|
||||||
bool last_state_{false};
|
bool last_state_{false};
|
||||||
const uint32_t wakeup_threshold_{0};
|
const uint32_t wakeup_threshold_{0};
|
||||||
|
|
||||||
|
@@ -100,8 +100,6 @@ void ESP32TouchComponent::process_setup_mode_logging_(uint32_t now) {
|
|||||||
#else
|
#else
|
||||||
// Read the value being used for touch detection
|
// Read the value being used for touch detection
|
||||||
uint32_t value = this->read_touch_value(child->get_touch_pad());
|
uint32_t value = this->read_touch_value(child->get_touch_pad());
|
||||||
// Store the value for get_value() access in lambdas
|
|
||||||
child->value_ = value;
|
|
||||||
ESP_LOGD(TAG, "Touch Pad '%s' (T%d): %d", child->get_name().c_str(), child->get_touch_pad(), value);
|
ESP_LOGD(TAG, "Touch Pad '%s' (T%d): %d", child->get_name().c_str(), child->get_touch_pad(), value);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@@ -10,11 +10,8 @@ namespace esp32_touch {
|
|||||||
|
|
||||||
static const char *const TAG = "esp32_touch";
|
static const char *const TAG = "esp32_touch";
|
||||||
|
|
||||||
// Helper to update touch state with a known state and value
|
// Helper to update touch state with a known state
|
||||||
void ESP32TouchComponent::update_touch_state_(ESP32TouchBinarySensor *child, bool is_touched, uint32_t value) {
|
void ESP32TouchComponent::update_touch_state_(ESP32TouchBinarySensor *child, bool is_touched) {
|
||||||
// Store the value for get_value() access in lambdas
|
|
||||||
child->value_ = value;
|
|
||||||
|
|
||||||
// Always update timer when touched
|
// Always update timer when touched
|
||||||
if (is_touched) {
|
if (is_touched) {
|
||||||
child->last_touch_time_ = App.get_loop_component_start_time();
|
child->last_touch_time_ = App.get_loop_component_start_time();
|
||||||
@@ -24,8 +21,9 @@ void ESP32TouchComponent::update_touch_state_(ESP32TouchBinarySensor *child, boo
|
|||||||
child->last_state_ = is_touched;
|
child->last_state_ = is_touched;
|
||||||
child->publish_state(is_touched);
|
child->publish_state(is_touched);
|
||||||
if (is_touched) {
|
if (is_touched) {
|
||||||
|
// ESP32-S2/S3 v2: touched when value > threshold
|
||||||
ESP_LOGV(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 " > threshold: %" PRIu32 ")", child->get_name().c_str(),
|
ESP_LOGV(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 " > threshold: %" PRIu32 ")", child->get_name().c_str(),
|
||||||
value, child->threshold_ + child->benchmark_);
|
this->read_touch_value(child->touch_pad_), child->threshold_ + child->benchmark_);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGV(TAG, "Touch Pad '%s' state: OFF", child->get_name().c_str());
|
ESP_LOGV(TAG, "Touch Pad '%s' state: OFF", child->get_name().c_str());
|
||||||
}
|
}
|
||||||
@@ -43,7 +41,7 @@ bool ESP32TouchComponent::check_and_update_touch_state_(ESP32TouchBinarySensor *
|
|||||||
child->get_name().c_str(), child->touch_pad_, value, child->threshold_, child->benchmark_);
|
child->get_name().c_str(), child->touch_pad_, value, child->threshold_, child->benchmark_);
|
||||||
bool is_touched = value > child->benchmark_ + child->threshold_;
|
bool is_touched = value > child->benchmark_ + child->threshold_;
|
||||||
|
|
||||||
this->update_touch_state_(child, is_touched, value);
|
this->update_touch_state_(child, is_touched);
|
||||||
return is_touched;
|
return is_touched;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,9 +296,7 @@ void ESP32TouchComponent::loop() {
|
|||||||
this->check_and_update_touch_state_(child);
|
this->check_and_update_touch_state_(child);
|
||||||
} else if (event.intr_mask & TOUCH_PAD_INTR_MASK_ACTIVE) {
|
} else if (event.intr_mask & TOUCH_PAD_INTR_MASK_ACTIVE) {
|
||||||
// We only get ACTIVE interrupts now, releases are detected by timeout
|
// We only get ACTIVE interrupts now, releases are detected by timeout
|
||||||
// Read the current value
|
this->update_touch_state_(child, true); // Always touched for ACTIVE interrupts
|
||||||
uint32_t value = this->read_touch_value(child->touch_pad_);
|
|
||||||
this->update_touch_state_(child, true, value); // Always touched for ACTIVE interrupts
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@@ -19,9 +19,7 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
|
||||||
static const char *const TAG = "esphome.ota";
|
static const char *const TAG = "esphome.ota";
|
||||||
static constexpr uint16_t OTA_BLOCK_SIZE = 8192;
|
static constexpr u_int16_t OTA_BLOCK_SIZE = 8192;
|
||||||
static constexpr uint32_t OTA_SOCKET_TIMEOUT_HANDSHAKE = 10000; // milliseconds for initial handshake
|
|
||||||
static constexpr uint32_t OTA_SOCKET_TIMEOUT_DATA = 90000; // milliseconds for data transfer
|
|
||||||
|
|
||||||
void ESPHomeOTAComponent::setup() {
|
void ESPHomeOTAComponent::setup() {
|
||||||
#ifdef USE_OTA_STATE_CALLBACK
|
#ifdef USE_OTA_STATE_CALLBACK
|
||||||
@@ -30,19 +28,19 @@ void ESPHomeOTAComponent::setup() {
|
|||||||
|
|
||||||
this->server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
|
this->server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
|
||||||
if (this->server_ == nullptr) {
|
if (this->server_ == nullptr) {
|
||||||
this->log_socket_error_("creation");
|
ESP_LOGW(TAG, "Could not create socket");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int enable = 1;
|
int enable = 1;
|
||||||
int err = this->server_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
|
int err = this->server_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
this->log_socket_error_("reuseaddr");
|
ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err);
|
||||||
// we can still continue
|
// we can still continue
|
||||||
}
|
}
|
||||||
err = this->server_->setblocking(false);
|
err = this->server_->setblocking(false);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
this->log_socket_error_("non-blocking");
|
ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err);
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -51,21 +49,21 @@ void ESPHomeOTAComponent::setup() {
|
|||||||
|
|
||||||
socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_);
|
socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_);
|
||||||
if (sl == 0) {
|
if (sl == 0) {
|
||||||
this->log_socket_error_("set sockaddr");
|
ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno);
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = this->server_->bind((struct sockaddr *) &server, sizeof(server));
|
err = this->server_->bind((struct sockaddr *) &server, sizeof(server));
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
this->log_socket_error_("bind");
|
ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno);
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = this->server_->listen(4);
|
err = this->server_->listen(4);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
this->log_socket_error_("listen");
|
ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -85,93 +83,17 @@ void ESPHomeOTAComponent::dump_config() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ESPHomeOTAComponent::loop() {
|
void ESPHomeOTAComponent::loop() {
|
||||||
// Skip handle_handshake_() call if no client connected and no incoming connections
|
// Skip handle_() call if no client connected and no incoming connections
|
||||||
// This optimization reduces idle loop overhead when OTA is not active
|
// This optimization reduces idle loop overhead when OTA is not active
|
||||||
// Note: No need to check server_ for null as the component is marked failed in setup()
|
// Note: No need to check server_ for null as the component is marked failed in setup() if server_ creation fails
|
||||||
// if server_ creation fails
|
|
||||||
if (this->client_ != nullptr || this->server_->ready()) {
|
if (this->client_ != nullptr || this->server_->ready()) {
|
||||||
this->handle_handshake_();
|
this->handle_();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const uint8_t FEATURE_SUPPORTS_COMPRESSION = 0x01;
|
static const uint8_t FEATURE_SUPPORTS_COMPRESSION = 0x01;
|
||||||
|
|
||||||
void ESPHomeOTAComponent::handle_handshake_() {
|
void ESPHomeOTAComponent::handle_() {
|
||||||
/// Handle the initial OTA handshake.
|
|
||||||
///
|
|
||||||
/// This method is non-blocking and will return immediately if no data is available.
|
|
||||||
/// It waits for the first magic byte (0x6C) before proceeding to handle_data_().
|
|
||||||
/// A 10-second timeout is enforced from initial connection.
|
|
||||||
|
|
||||||
if (this->client_ == nullptr) {
|
|
||||||
// We already checked server_->ready() in loop(), so we can accept directly
|
|
||||||
struct sockaddr_storage source_addr;
|
|
||||||
socklen_t addr_len = sizeof(source_addr);
|
|
||||||
int enable = 1;
|
|
||||||
|
|
||||||
this->client_ = this->server_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
|
|
||||||
if (this->client_ == nullptr)
|
|
||||||
return;
|
|
||||||
int err = this->client_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
|
|
||||||
if (err != 0) {
|
|
||||||
this->log_socket_error_("nodelay");
|
|
||||||
this->cleanup_connection_();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
err = this->client_->setblocking(false);
|
|
||||||
if (err != 0) {
|
|
||||||
this->log_socket_error_("non-blocking");
|
|
||||||
this->cleanup_connection_();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this->log_start_("handshake");
|
|
||||||
this->client_connect_time_ = App.get_loop_component_start_time();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for handshake timeout
|
|
||||||
uint32_t now = App.get_loop_component_start_time();
|
|
||||||
if (now - this->client_connect_time_ > OTA_SOCKET_TIMEOUT_HANDSHAKE) {
|
|
||||||
ESP_LOGW(TAG, "Handshake timeout");
|
|
||||||
this->cleanup_connection_();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to read first byte of magic bytes
|
|
||||||
uint8_t first_byte;
|
|
||||||
ssize_t read = this->client_->read(&first_byte, 1);
|
|
||||||
|
|
||||||
if (read == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
|
|
||||||
return; // No data yet, try again next loop
|
|
||||||
}
|
|
||||||
|
|
||||||
if (read <= 0) {
|
|
||||||
// Error or connection closed
|
|
||||||
if (read == -1) {
|
|
||||||
this->log_socket_error_("reading first byte");
|
|
||||||
} else {
|
|
||||||
ESP_LOGW(TAG, "Remote closed during handshake");
|
|
||||||
}
|
|
||||||
this->cleanup_connection_();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Got first byte, check if it's the magic byte
|
|
||||||
if (first_byte != 0x6C) {
|
|
||||||
ESP_LOGW(TAG, "Invalid initial byte: 0x%02X", first_byte);
|
|
||||||
this->cleanup_connection_();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// First byte is valid, continue with data handling
|
|
||||||
this->handle_data_();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ESPHomeOTAComponent::handle_data_() {
|
|
||||||
/// Handle the OTA data transfer and update process.
|
|
||||||
///
|
|
||||||
/// This method is blocking and will not return until the OTA update completes,
|
|
||||||
/// fails, or times out. It handles authentication, receives the firmware data,
|
|
||||||
/// writes it to flash, and reboots on success.
|
|
||||||
ota::OTAResponseTypes error_code = ota::OTA_RESPONSE_ERROR_UNKNOWN;
|
ota::OTAResponseTypes error_code = ota::OTA_RESPONSE_ERROR_UNKNOWN;
|
||||||
bool update_started = false;
|
bool update_started = false;
|
||||||
size_t total = 0;
|
size_t total = 0;
|
||||||
@@ -186,14 +108,38 @@ void ESPHomeOTAComponent::handle_data_() {
|
|||||||
size_t size_acknowledged = 0;
|
size_t size_acknowledged = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Read remaining 4 bytes of magic (we already read the first byte 0x6C in handle_handshake_)
|
if (this->client_ == nullptr) {
|
||||||
if (!this->readall_(buf, 4)) {
|
// We already checked server_->ready() in loop(), so we can accept directly
|
||||||
this->log_read_error_("magic bytes");
|
struct sockaddr_storage source_addr;
|
||||||
|
socklen_t addr_len = sizeof(source_addr);
|
||||||
|
this->client_ = this->server_->accept((struct sockaddr *) &source_addr, &addr_len);
|
||||||
|
if (this->client_ == nullptr)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int enable = 1;
|
||||||
|
int err = this->client_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
|
||||||
|
if (err != 0) {
|
||||||
|
ESP_LOGW(TAG, "Socket could not enable TCP nodelay, errno %d", errno);
|
||||||
|
this->client_->close();
|
||||||
|
this->client_ = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Starting update from %s", this->client_->getpeername().c_str());
|
||||||
|
this->status_set_warning();
|
||||||
|
#ifdef USE_OTA_STATE_CALLBACK
|
||||||
|
this->state_callback_.call(ota::OTA_STARTED, 0.0f, 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!this->readall_(buf, 5)) {
|
||||||
|
ESP_LOGW(TAG, "Reading magic bytes failed");
|
||||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||||
}
|
}
|
||||||
// Check remaining magic bytes: 0x26, 0xF7, 0x5C, 0x45
|
// 0x6C, 0x26, 0xF7, 0x5C, 0x45
|
||||||
if (buf[0] != 0x26 || buf[1] != 0xF7 || buf[2] != 0x5C || buf[3] != 0x45) {
|
if (buf[0] != 0x6C || buf[1] != 0x26 || buf[2] != 0xF7 || buf[3] != 0x5C || buf[4] != 0x45) {
|
||||||
ESP_LOGW(TAG, "Magic bytes mismatch! 0x6C-0x%02X-0x%02X-0x%02X-0x%02X", buf[0], buf[1], buf[2], buf[3]);
|
ESP_LOGW(TAG, "Magic bytes do not match! 0x%02X-0x%02X-0x%02X-0x%02X-0x%02X", buf[0], buf[1], buf[2], buf[3],
|
||||||
|
buf[4]);
|
||||||
error_code = ota::OTA_RESPONSE_ERROR_MAGIC;
|
error_code = ota::OTA_RESPONSE_ERROR_MAGIC;
|
||||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||||
}
|
}
|
||||||
@@ -207,7 +153,7 @@ void ESPHomeOTAComponent::handle_data_() {
|
|||||||
|
|
||||||
// Read features - 1 byte
|
// Read features - 1 byte
|
||||||
if (!this->readall_(buf, 1)) {
|
if (!this->readall_(buf, 1)) {
|
||||||
this->log_read_error_("features");
|
ESP_LOGW(TAG, "Reading features failed");
|
||||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||||
}
|
}
|
||||||
ota_features = buf[0]; // NOLINT
|
ota_features = buf[0]; // NOLINT
|
||||||
@@ -286,7 +232,7 @@ void ESPHomeOTAComponent::handle_data_() {
|
|||||||
|
|
||||||
// Read size, 4 bytes MSB first
|
// Read size, 4 bytes MSB first
|
||||||
if (!this->readall_(buf, 4)) {
|
if (!this->readall_(buf, 4)) {
|
||||||
this->log_read_error_("size");
|
ESP_LOGW(TAG, "Reading size failed");
|
||||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||||
}
|
}
|
||||||
ota_size = 0;
|
ota_size = 0;
|
||||||
@@ -296,17 +242,6 @@ void ESPHomeOTAComponent::handle_data_() {
|
|||||||
}
|
}
|
||||||
ESP_LOGV(TAG, "Size is %u bytes", ota_size);
|
ESP_LOGV(TAG, "Size is %u bytes", ota_size);
|
||||||
|
|
||||||
// Now that we've passed authentication and are actually
|
|
||||||
// starting the update, set the warning status and notify
|
|
||||||
// listeners. This ensures that port scanners do not
|
|
||||||
// accidentally trigger the update process.
|
|
||||||
this->log_start_("update");
|
|
||||||
this->status_set_warning();
|
|
||||||
#ifdef USE_OTA_STATE_CALLBACK
|
|
||||||
this->state_callback_.call(ota::OTA_STARTED, 0.0f, 0);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// This will block for a few seconds as it locks flash
|
|
||||||
error_code = backend->begin(ota_size);
|
error_code = backend->begin(ota_size);
|
||||||
if (error_code != ota::OTA_RESPONSE_OK)
|
if (error_code != ota::OTA_RESPONSE_OK)
|
||||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||||
@@ -318,7 +253,7 @@ void ESPHomeOTAComponent::handle_data_() {
|
|||||||
|
|
||||||
// Read binary MD5, 32 bytes
|
// Read binary MD5, 32 bytes
|
||||||
if (!this->readall_(buf, 32)) {
|
if (!this->readall_(buf, 32)) {
|
||||||
this->log_read_error_("MD5 checksum");
|
ESP_LOGW(TAG, "Reading binary MD5 checksum failed");
|
||||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||||
}
|
}
|
||||||
sbuf[32] = '\0';
|
sbuf[32] = '\0';
|
||||||
@@ -335,22 +270,23 @@ void ESPHomeOTAComponent::handle_data_() {
|
|||||||
ssize_t read = this->client_->read(buf, requested);
|
ssize_t read = this->client_->read(buf, requested);
|
||||||
if (read == -1) {
|
if (read == -1) {
|
||||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
this->yield_and_feed_watchdog_();
|
App.feed_wdt();
|
||||||
|
delay(1);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ESP_LOGW(TAG, "Read error, errno %d", errno);
|
ESP_LOGW(TAG, "Error receiving data for update, errno %d", errno);
|
||||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||||
} else if (read == 0) {
|
} else if (read == 0) {
|
||||||
// $ man recv
|
// $ man recv
|
||||||
// "When a stream socket peer has performed an orderly shutdown, the return value will
|
// "When a stream socket peer has performed an orderly shutdown, the return value will
|
||||||
// be 0 (the traditional "end-of-file" return)."
|
// be 0 (the traditional "end-of-file" return)."
|
||||||
ESP_LOGW(TAG, "Remote closed connection");
|
ESP_LOGW(TAG, "Remote end closed connection");
|
||||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||||
}
|
}
|
||||||
|
|
||||||
error_code = backend->write(buf, read);
|
error_code = backend->write(buf, read);
|
||||||
if (error_code != ota::OTA_RESPONSE_OK) {
|
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||||
ESP_LOGW(TAG, "Flash write error, code: %d", error_code);
|
ESP_LOGW(TAG, "Error writing binary data to flash!, error_code: %d", error_code);
|
||||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||||
}
|
}
|
||||||
total += read;
|
total += read;
|
||||||
@@ -371,7 +307,8 @@ void ESPHomeOTAComponent::handle_data_() {
|
|||||||
this->state_callback_.call(ota::OTA_IN_PROGRESS, percentage, 0);
|
this->state_callback_.call(ota::OTA_IN_PROGRESS, percentage, 0);
|
||||||
#endif
|
#endif
|
||||||
// feed watchdog and give other tasks a chance to run
|
// feed watchdog and give other tasks a chance to run
|
||||||
this->yield_and_feed_watchdog_();
|
App.feed_wdt();
|
||||||
|
yield();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,7 +318,7 @@ void ESPHomeOTAComponent::handle_data_() {
|
|||||||
|
|
||||||
error_code = backend->end();
|
error_code = backend->end();
|
||||||
if (error_code != ota::OTA_RESPONSE_OK) {
|
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||||
ESP_LOGW(TAG, "Error ending update! code: %d", error_code);
|
ESP_LOGW(TAG, "Error ending update! error_code: %d", error_code);
|
||||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,11 +328,12 @@ void ESPHomeOTAComponent::handle_data_() {
|
|||||||
|
|
||||||
// Read ACK
|
// Read ACK
|
||||||
if (!this->readall_(buf, 1) || buf[0] != ota::OTA_RESPONSE_OK) {
|
if (!this->readall_(buf, 1) || buf[0] != ota::OTA_RESPONSE_OK) {
|
||||||
this->log_read_error_("ack");
|
ESP_LOGW(TAG, "Reading back acknowledgement failed");
|
||||||
// do not go to error, this is not fatal
|
// do not go to error, this is not fatal
|
||||||
}
|
}
|
||||||
|
|
||||||
this->cleanup_connection_();
|
this->client_->close();
|
||||||
|
this->client_ = nullptr;
|
||||||
delay(10);
|
delay(10);
|
||||||
ESP_LOGI(TAG, "Update complete");
|
ESP_LOGI(TAG, "Update complete");
|
||||||
this->status_clear_warning();
|
this->status_clear_warning();
|
||||||
@@ -408,7 +346,8 @@ void ESPHomeOTAComponent::handle_data_() {
|
|||||||
error:
|
error:
|
||||||
buf[0] = static_cast<uint8_t>(error_code);
|
buf[0] = static_cast<uint8_t>(error_code);
|
||||||
this->writeall_(buf, 1);
|
this->writeall_(buf, 1);
|
||||||
this->cleanup_connection_();
|
this->client_->close();
|
||||||
|
this->client_ = nullptr;
|
||||||
|
|
||||||
if (backend != nullptr && update_started) {
|
if (backend != nullptr && update_started) {
|
||||||
backend->abort();
|
backend->abort();
|
||||||
@@ -425,24 +364,28 @@ bool ESPHomeOTAComponent::readall_(uint8_t *buf, size_t len) {
|
|||||||
uint32_t at = 0;
|
uint32_t at = 0;
|
||||||
while (len - at > 0) {
|
while (len - at > 0) {
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
if (now - start > OTA_SOCKET_TIMEOUT_DATA) {
|
if (now - start > 1000) {
|
||||||
ESP_LOGW(TAG, "Timeout reading %d bytes", len);
|
ESP_LOGW(TAG, "Timed out reading %d bytes of data", len);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t read = this->client_->read(buf + at, len - at);
|
ssize_t read = this->client_->read(buf + at, len - at);
|
||||||
if (read == -1) {
|
if (read == -1) {
|
||||||
if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
ESP_LOGW(TAG, "Error reading %d bytes, errno %d", len, errno);
|
App.feed_wdt();
|
||||||
return false;
|
delay(1);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
ESP_LOGW(TAG, "Failed to read %d bytes of data, errno %d", len, errno);
|
||||||
|
return false;
|
||||||
} else if (read == 0) {
|
} else if (read == 0) {
|
||||||
ESP_LOGW(TAG, "Remote closed connection");
|
ESP_LOGW(TAG, "Remote closed connection");
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
at += read;
|
at += read;
|
||||||
}
|
}
|
||||||
this->yield_and_feed_watchdog_();
|
App.feed_wdt();
|
||||||
|
delay(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -452,21 +395,25 @@ bool ESPHomeOTAComponent::writeall_(const uint8_t *buf, size_t len) {
|
|||||||
uint32_t at = 0;
|
uint32_t at = 0;
|
||||||
while (len - at > 0) {
|
while (len - at > 0) {
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
if (now - start > OTA_SOCKET_TIMEOUT_DATA) {
|
if (now - start > 1000) {
|
||||||
ESP_LOGW(TAG, "Timeout writing %d bytes", len);
|
ESP_LOGW(TAG, "Timed out writing %d bytes of data", len);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t written = this->client_->write(buf + at, len - at);
|
ssize_t written = this->client_->write(buf + at, len - at);
|
||||||
if (written == -1) {
|
if (written == -1) {
|
||||||
if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
ESP_LOGW(TAG, "Error writing %d bytes, errno %d", len, errno);
|
App.feed_wdt();
|
||||||
return false;
|
delay(1);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
ESP_LOGW(TAG, "Failed to write %d bytes of data, errno %d", len, errno);
|
||||||
|
return false;
|
||||||
} else {
|
} else {
|
||||||
at += written;
|
at += written;
|
||||||
}
|
}
|
||||||
this->yield_and_feed_watchdog_();
|
App.feed_wdt();
|
||||||
|
delay(1);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -474,25 +421,5 @@ bool ESPHomeOTAComponent::writeall_(const uint8_t *buf, size_t len) {
|
|||||||
float ESPHomeOTAComponent::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
float ESPHomeOTAComponent::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
||||||
uint16_t ESPHomeOTAComponent::get_port() const { return this->port_; }
|
uint16_t ESPHomeOTAComponent::get_port() const { return this->port_; }
|
||||||
void ESPHomeOTAComponent::set_port(uint16_t port) { this->port_ = port; }
|
void ESPHomeOTAComponent::set_port(uint16_t port) { this->port_ = port; }
|
||||||
|
|
||||||
void ESPHomeOTAComponent::log_socket_error_(const char *msg) { ESP_LOGW(TAG, "Socket %s: errno %d", msg, errno); }
|
|
||||||
|
|
||||||
void ESPHomeOTAComponent::log_read_error_(const char *what) { ESP_LOGW(TAG, "Read %s failed", what); }
|
|
||||||
|
|
||||||
void ESPHomeOTAComponent::log_start_(const char *phase) {
|
|
||||||
ESP_LOGD(TAG, "Starting %s from %s", phase, this->client_->getpeername().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ESPHomeOTAComponent::cleanup_connection_() {
|
|
||||||
this->client_->close();
|
|
||||||
this->client_ = nullptr;
|
|
||||||
this->client_connect_time_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ESPHomeOTAComponent::yield_and_feed_watchdog_() {
|
|
||||||
App.feed_wdt();
|
|
||||||
delay(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
#endif
|
#endif
|
||||||
|
@@ -27,22 +27,15 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
|
|||||||
uint16_t get_port() const;
|
uint16_t get_port() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void handle_handshake_();
|
void handle_();
|
||||||
void handle_data_();
|
|
||||||
bool readall_(uint8_t *buf, size_t len);
|
bool readall_(uint8_t *buf, size_t len);
|
||||||
bool writeall_(const uint8_t *buf, size_t len);
|
bool writeall_(const uint8_t *buf, size_t len);
|
||||||
void log_socket_error_(const char *msg);
|
|
||||||
void log_read_error_(const char *what);
|
|
||||||
void log_start_(const char *phase);
|
|
||||||
void cleanup_connection_();
|
|
||||||
void yield_and_feed_watchdog_();
|
|
||||||
|
|
||||||
#ifdef USE_OTA_PASSWORD
|
#ifdef USE_OTA_PASSWORD
|
||||||
std::string password_;
|
std::string password_;
|
||||||
#endif // USE_OTA_PASSWORD
|
#endif // USE_OTA_PASSWORD
|
||||||
|
|
||||||
uint16_t port_;
|
uint16_t port_;
|
||||||
uint32_t client_connect_time_{0};
|
|
||||||
|
|
||||||
std::unique_ptr<socket::Socket> server_;
|
std::unique_ptr<socket::Socket> server_;
|
||||||
std::unique_ptr<socket::Socket> client_;
|
std::unique_ptr<socket::Socket> client_;
|
||||||
|
@@ -65,6 +65,15 @@ CONF_WAIT_FOR_SENT = "wait_for_sent"
|
|||||||
MAX_ESPNOW_PACKET_SIZE = 250 # Maximum size of the payload in bytes
|
MAX_ESPNOW_PACKET_SIZE = 250 # Maximum size of the payload in bytes
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_unknown_peer(config):
|
||||||
|
if config[CONF_AUTO_ADD_PEER] and config.get(CONF_ON_UNKNOWN_PEER):
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"'{CONF_ON_UNKNOWN_PEER}' cannot be used when '{CONF_AUTO_ADD_PEER}' is enabled.",
|
||||||
|
path=[CONF_ON_UNKNOWN_PEER],
|
||||||
|
)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
@@ -94,6 +103,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
},
|
},
|
||||||
).extend(cv.COMPONENT_SCHEMA),
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
cv.only_on_esp32,
|
cv.only_on_esp32,
|
||||||
|
_validate_unknown_peer,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -114,6 +124,7 @@ async def _trigger_to_code(config):
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
print(config)
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
|
@@ -40,20 +40,20 @@ template<typename... Ts> class SendAction : public Action<Ts...>, public Parente
|
|||||||
this->num_running_++;
|
this->num_running_++;
|
||||||
send_callback_t send_callback = [this, x...](esp_err_t status) {
|
send_callback_t send_callback = [this, x...](esp_err_t status) {
|
||||||
if (status == ESP_OK) {
|
if (status == ESP_OK) {
|
||||||
if (!this->sent_.empty()) {
|
if (this->sent_.empty() && this->flags_.wait_for_sent) {
|
||||||
this->sent_.play(x...);
|
|
||||||
} else if (this->flags_.wait_for_sent) {
|
|
||||||
this->play_next_(x...);
|
this->play_next_(x...);
|
||||||
|
} else if (!this->sent_.empty()) {
|
||||||
|
this->sent_.play(x...);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!this->error_.empty()) {
|
if (this->error_.empty() && this->flags_.wait_for_sent) {
|
||||||
this->error_.play(x...);
|
|
||||||
} else if (this->flags_.wait_for_sent) {
|
|
||||||
if (this->flags_.continue_on_error) {
|
if (this->flags_.continue_on_error) {
|
||||||
this->play_next_(x...);
|
this->play_next_(x...);
|
||||||
} else {
|
} else {
|
||||||
this->stop_complex();
|
this->stop_complex();
|
||||||
}
|
}
|
||||||
|
} else if (!this->error_.empty()) {
|
||||||
|
this->error_.play(x...);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -154,7 +154,7 @@ void ESPNowComponent::setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ESPNowComponent::enable() {
|
void ESPNowComponent::enable() {
|
||||||
if (this->state_ == ESPNOW_STATE_ENABLED)
|
if (this->state_ != ESPNOW_STATE_ENABLED)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Enabling");
|
ESP_LOGD(TAG, "Enabling");
|
||||||
@@ -178,7 +178,11 @@ void ESPNowComponent::enable_() {
|
|||||||
|
|
||||||
this->apply_wifi_channel();
|
this->apply_wifi_channel();
|
||||||
}
|
}
|
||||||
this->get_wifi_channel();
|
#ifdef USE_WIFI
|
||||||
|
else {
|
||||||
|
this->wifi_channel_ = wifi::global_wifi_component->get_wifi_channel();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
esp_err_t err = esp_now_init();
|
esp_err_t err = esp_now_init();
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
@@ -208,11 +212,10 @@ void ESPNowComponent::enable_() {
|
|||||||
esp_wifi_connectionless_module_set_wake_interval(CONFIG_ESPNOW_WAKE_INTERVAL);
|
esp_wifi_connectionless_module_set_wake_interval(CONFIG_ESPNOW_WAKE_INTERVAL);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
this->state_ = ESPNOW_STATE_ENABLED;
|
|
||||||
|
|
||||||
for (auto peer : this->peers_) {
|
for (auto peer : this->peers_) {
|
||||||
this->add_peer(peer.address);
|
this->add_peer(peer.address);
|
||||||
}
|
}
|
||||||
|
this->state_ = ESPNOW_STATE_ENABLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESPNowComponent::disable() {
|
void ESPNowComponent::disable() {
|
||||||
@@ -225,6 +228,10 @@ void ESPNowComponent::disable() {
|
|||||||
esp_now_unregister_recv_cb();
|
esp_now_unregister_recv_cb();
|
||||||
esp_now_unregister_send_cb();
|
esp_now_unregister_send_cb();
|
||||||
|
|
||||||
|
for (auto peer : this->peers_) {
|
||||||
|
this->del_peer(peer.address);
|
||||||
|
}
|
||||||
|
|
||||||
esp_err_t err = esp_now_deinit();
|
esp_err_t err = esp_now_deinit();
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "esp_now_deinit failed! 0x%x", err);
|
ESP_LOGE(TAG, "esp_now_deinit failed! 0x%x", err);
|
||||||
@@ -260,6 +267,7 @@ void ESPNowComponent::loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Process received packets
|
// Process received packets
|
||||||
ESPNowPacket *packet = this->receive_packet_queue_.pop();
|
ESPNowPacket *packet = this->receive_packet_queue_.pop();
|
||||||
while (packet != nullptr) {
|
while (packet != nullptr) {
|
||||||
@@ -267,15 +275,13 @@ void ESPNowComponent::loop() {
|
|||||||
case ESPNowPacket::RECEIVED: {
|
case ESPNowPacket::RECEIVED: {
|
||||||
const ESPNowRecvInfo info = packet->get_receive_info();
|
const ESPNowRecvInfo info = packet->get_receive_info();
|
||||||
if (!esp_now_is_peer_exist(info.src_addr)) {
|
if (!esp_now_is_peer_exist(info.src_addr)) {
|
||||||
bool handled = false;
|
if (this->auto_add_peer_) {
|
||||||
for (auto *handler : this->unknown_peer_handlers_) {
|
|
||||||
if (handler->on_unknown_peer(info, packet->packet_.receive.data, packet->packet_.receive.size)) {
|
|
||||||
handled = true;
|
|
||||||
break; // If a handler returns true, stop processing further handlers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!handled && this->auto_add_peer_) {
|
|
||||||
this->add_peer(info.src_addr);
|
this->add_peer(info.src_addr);
|
||||||
|
} else {
|
||||||
|
for (auto *handler : this->unknown_peer_handlers_) {
|
||||||
|
if (handler->on_unknown_peer(info, packet->packet_.receive.data, packet->packet_.receive.size))
|
||||||
|
break; // If a handler returns true, stop processing further handlers
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Intentionally left as if instead of else in case the peer is added above
|
// Intentionally left as if instead of else in case the peer is added above
|
||||||
@@ -337,12 +343,6 @@ void ESPNowComponent::loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t ESPNowComponent::get_wifi_channel() {
|
|
||||||
wifi_second_chan_t dummy;
|
|
||||||
esp_wifi_get_channel(&this->wifi_channel_, &dummy);
|
|
||||||
return this->wifi_channel_;
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t ESPNowComponent::send(const uint8_t *peer_address, const uint8_t *payload, size_t size,
|
esp_err_t ESPNowComponent::send(const uint8_t *peer_address, const uint8_t *payload, size_t size,
|
||||||
const send_callback_t &callback) {
|
const send_callback_t &callback) {
|
||||||
if (this->state_ != ESPNOW_STATE_ENABLED) {
|
if (this->state_ != ESPNOW_STATE_ENABLED) {
|
||||||
@@ -407,7 +407,7 @@ esp_err_t ESPNowComponent::add_peer(const uint8_t *peer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (memcmp(peer, this->own_address_, ESP_NOW_ETH_ALEN) == 0) {
|
if (memcmp(peer, this->own_address_, ESP_NOW_ETH_ALEN) == 0) {
|
||||||
this->status_momentary_warning("peer-add-failed");
|
this->mark_failed();
|
||||||
return ESP_ERR_INVALID_MAC;
|
return ESP_ERR_INVALID_MAC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -110,7 +110,6 @@ class ESPNowComponent : public Component {
|
|||||||
|
|
||||||
void set_wifi_channel(uint8_t channel) { this->wifi_channel_ = channel; }
|
void set_wifi_channel(uint8_t channel) { this->wifi_channel_ = channel; }
|
||||||
void apply_wifi_channel();
|
void apply_wifi_channel();
|
||||||
uint8_t get_wifi_channel();
|
|
||||||
|
|
||||||
void set_auto_add_peer(bool value) { this->auto_add_peer_ = value; }
|
void set_auto_add_peer(bool value) { this->auto_add_peer_ = value; }
|
||||||
|
|
||||||
|
@@ -49,7 +49,7 @@ class ESPNowPacket {
|
|||||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
|
||||||
// Constructor for sent data
|
// Constructor for sent data
|
||||||
ESPNowPacket(const esp_now_send_info_t *info, esp_now_send_status_t status) {
|
ESPNowPacket(const esp_now_send_info_t *info, esp_now_send_status_t status) {
|
||||||
this->init_sent_data_(info->src_addr, status);
|
this->init_sent_data(info->src_addr, status);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
// Constructor for sent data
|
// Constructor for sent data
|
||||||
|
@@ -15,7 +15,6 @@ from freetype import (
|
|||||||
FT_LOAD_RENDER,
|
FT_LOAD_RENDER,
|
||||||
FT_LOAD_TARGET_MONO,
|
FT_LOAD_TARGET_MONO,
|
||||||
Face,
|
Face,
|
||||||
FT_Exception,
|
|
||||||
ft_pixel_mode_mono,
|
ft_pixel_mode_mono,
|
||||||
)
|
)
|
||||||
import requests
|
import requests
|
||||||
@@ -95,14 +94,7 @@ class FontCache(MutableMapping):
|
|||||||
return self.store[self._keytransform(item)]
|
return self.store[self._keytransform(item)]
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
transformed = self._keytransform(key)
|
self.store[self._keytransform(key)] = Face(str(value))
|
||||||
try:
|
|
||||||
self.store[transformed] = Face(str(value))
|
|
||||||
except FT_Exception as exc:
|
|
||||||
file = transformed.split(":", 1)
|
|
||||||
raise cv.Invalid(
|
|
||||||
f"{file[0].capitalize()} {file[1]} is not a valid font file"
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
|
|
||||||
FONT_CACHE = FontCache()
|
FONT_CACHE = FontCache()
|
||||||
|
@@ -2,11 +2,10 @@
|
|||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
|
||||||
#include <limits>
|
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
namespace esphome::gpio_expander {
|
namespace esphome {
|
||||||
|
namespace gpio_expander {
|
||||||
|
|
||||||
/// @brief A class to cache the read state of a GPIO expander.
|
/// @brief A class to cache the read state of a GPIO expander.
|
||||||
/// This class caches reads between GPIO Pins which are on the same bank.
|
/// This class caches reads between GPIO Pins which are on the same bank.
|
||||||
@@ -18,22 +17,12 @@ namespace esphome::gpio_expander {
|
|||||||
/// N - Number of pins
|
/// N - Number of pins
|
||||||
template<typename T, T N> class CachedGpioExpander {
|
template<typename T, T N> class CachedGpioExpander {
|
||||||
public:
|
public:
|
||||||
/// @brief Read the state of the given pin. This will invalidate the cache for the given pin number.
|
|
||||||
/// @param pin Pin number to read
|
|
||||||
/// @return Pin state
|
|
||||||
bool digital_read(T pin) {
|
bool digital_read(T pin) {
|
||||||
const uint8_t bank = pin / BANK_SIZE;
|
uint8_t bank = pin / (sizeof(T) * BITS_PER_BYTE);
|
||||||
const T pin_mask = (1 << (pin % BANK_SIZE));
|
if (this->read_cache_invalidated_[bank]) {
|
||||||
// Check if specific pin cache is valid
|
this->read_cache_invalidated_[bank] = false;
|
||||||
if (this->read_cache_valid_[bank] & pin_mask) {
|
|
||||||
// Invalidate pin
|
|
||||||
this->read_cache_valid_[bank] &= ~pin_mask;
|
|
||||||
} else {
|
|
||||||
// Read whole bank from hardware
|
|
||||||
if (!this->digital_read_hw(pin))
|
if (!this->digital_read_hw(pin))
|
||||||
return false;
|
return false;
|
||||||
// Mark bank cache as valid except the pin that is being returned now
|
|
||||||
this->read_cache_valid_[bank] = std::numeric_limits<T>::max() & ~pin_mask;
|
|
||||||
}
|
}
|
||||||
return this->digital_read_cache(pin);
|
return this->digital_read_cache(pin);
|
||||||
}
|
}
|
||||||
@@ -47,16 +36,18 @@ template<typename T, T N> class CachedGpioExpander {
|
|||||||
virtual bool digital_read_cache(T pin) = 0;
|
virtual bool digital_read_cache(T pin) = 0;
|
||||||
/// @brief Call component low level function to write GPIO state to device
|
/// @brief Call component low level function to write GPIO state to device
|
||||||
virtual void digital_write_hw(T pin, bool value) = 0;
|
virtual void digital_write_hw(T pin, bool value) = 0;
|
||||||
|
const uint8_t cache_byte_size_ = N / (sizeof(T) * BITS_PER_BYTE);
|
||||||
|
|
||||||
/// @brief Invalidate cache. This function should be called in component loop().
|
/// @brief Invalidate cache. This function should be called in component loop().
|
||||||
void reset_pin_cache_() { memset(this->read_cache_valid_, 0x00, CACHE_SIZE_BYTES); }
|
void reset_pin_cache_() {
|
||||||
|
for (T i = 0; i < this->cache_byte_size_; i++) {
|
||||||
|
this->read_cache_invalidated_[i] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static constexpr uint8_t BITS_PER_BYTE = 8;
|
static const uint8_t BITS_PER_BYTE = 8;
|
||||||
static constexpr uint8_t BANK_SIZE = sizeof(T) * BITS_PER_BYTE;
|
std::array<bool, N / (sizeof(T) * BITS_PER_BYTE)> read_cache_invalidated_{};
|
||||||
static constexpr size_t BANKS = N / BANK_SIZE;
|
|
||||||
static constexpr size_t CACHE_SIZE_BYTES = BANKS * sizeof(T);
|
|
||||||
|
|
||||||
T read_cache_valid_[BANKS]{0};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::gpio_expander
|
} // namespace gpio_expander
|
||||||
|
} // namespace esphome
|
||||||
|
@@ -20,11 +20,12 @@ static const size_t MAX_BUTTONS = 4; // max number of buttons scanned
|
|||||||
|
|
||||||
#define ERROR_CHECK(err) \
|
#define ERROR_CHECK(err) \
|
||||||
if ((err) != i2c::ERROR_OK) { \
|
if ((err) != i2c::ERROR_OK) { \
|
||||||
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL); \
|
this->status_set_warning("Communication failure"); \
|
||||||
return; \
|
return; \
|
||||||
}
|
}
|
||||||
|
|
||||||
void GT911Touchscreen::setup() {
|
void GT911Touchscreen::setup() {
|
||||||
|
i2c::ErrorCode err;
|
||||||
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);
|
||||||
@@ -34,14 +35,9 @@ void GT911Touchscreen::setup() {
|
|||||||
this->interrupt_pin_->digital_write(false);
|
this->interrupt_pin_->digital_write(false);
|
||||||
}
|
}
|
||||||
delay(2);
|
delay(2);
|
||||||
this->reset_pin_->digital_write(true); // wait 50ms after reset
|
this->reset_pin_->digital_write(true);
|
||||||
this->set_timeout(50, [this] { this->setup_internal_(); });
|
delay(50); // NOLINT
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
this->setup_internal_();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GT911Touchscreen::setup_internal_() {
|
|
||||||
if (this->interrupt_pin_ != nullptr) {
|
if (this->interrupt_pin_ != nullptr) {
|
||||||
// set pre-configured input mode
|
// set pre-configured input mode
|
||||||
this->interrupt_pin_->setup();
|
this->interrupt_pin_->setup();
|
||||||
@@ -49,7 +45,7 @@ void GT911Touchscreen::setup_internal_() {
|
|||||||
|
|
||||||
// check the configuration of the int line.
|
// check the configuration of the int line.
|
||||||
uint8_t data[4];
|
uint8_t data[4];
|
||||||
i2c::ErrorCode err = this->write(GET_SWITCHES, sizeof(GET_SWITCHES));
|
err = this->write(GET_SWITCHES, sizeof(GET_SWITCHES));
|
||||||
if (err != i2c::ERROR_OK && this->address_ == PRIMARY_ADDRESS) {
|
if (err != i2c::ERROR_OK && this->address_ == PRIMARY_ADDRESS) {
|
||||||
this->address_ = SECONDARY_ADDRESS;
|
this->address_ = SECONDARY_ADDRESS;
|
||||||
err = this->write(GET_SWITCHES, sizeof(GET_SWITCHES));
|
err = this->write(GET_SWITCHES, sizeof(GET_SWITCHES));
|
||||||
@@ -57,7 +53,7 @@ void GT911Touchscreen::setup_internal_() {
|
|||||||
if (err == i2c::ERROR_OK) {
|
if (err == i2c::ERROR_OK) {
|
||||||
err = this->read(data, 1);
|
err = this->read(data, 1);
|
||||||
if (err == i2c::ERROR_OK) {
|
if (err == i2c::ERROR_OK) {
|
||||||
ESP_LOGD(TAG, "Switches ADDR: 0x%02X DATA: 0x%02X", this->address_, data[0]);
|
ESP_LOGD(TAG, "Read from switches at address 0x%02X: 0x%02X", this->address_, data[0]);
|
||||||
if (this->interrupt_pin_ != nullptr) {
|
if (this->interrupt_pin_ != nullptr) {
|
||||||
this->attach_interrupt_(this->interrupt_pin_,
|
this->attach_interrupt_(this->interrupt_pin_,
|
||||||
(data[0] & 1) ? gpio::INTERRUPT_FALLING_EDGE : gpio::INTERRUPT_RISING_EDGE);
|
(data[0] & 1) ? gpio::INTERRUPT_FALLING_EDGE : gpio::INTERRUPT_RISING_EDGE);
|
||||||
@@ -79,24 +75,16 @@ void GT911Touchscreen::setup_internal_() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (err != i2c::ERROR_OK) {
|
if (err != i2c::ERROR_OK) {
|
||||||
this->mark_failed("Calibration error");
|
this->mark_failed("Failed to read calibration");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err != i2c::ERROR_OK) {
|
if (err != i2c::ERROR_OK) {
|
||||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
this->mark_failed("Failed to communicate");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
this->setup_done_ = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GT911Touchscreen::update_touches() {
|
void GT911Touchscreen::update_touches() {
|
||||||
this->skip_update_ = true; // skip send touch events by default, set to false after successful error checks
|
|
||||||
if (!this->setup_done_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
i2c::ErrorCode err;
|
i2c::ErrorCode err;
|
||||||
uint8_t touch_state = 0;
|
uint8_t touch_state = 0;
|
||||||
uint8_t data[MAX_TOUCHES + 1][8]; // 8 bytes each for each point, plus extra space for the key byte
|
uint8_t data[MAX_TOUCHES + 1][8]; // 8 bytes each for each point, plus extra space for the key byte
|
||||||
@@ -109,6 +97,7 @@ void GT911Touchscreen::update_touches() {
|
|||||||
uint8_t num_of_touches = touch_state & 0x07;
|
uint8_t num_of_touches = touch_state & 0x07;
|
||||||
|
|
||||||
if ((touch_state & 0x80) == 0 || num_of_touches > MAX_TOUCHES) {
|
if ((touch_state & 0x80) == 0 || num_of_touches > MAX_TOUCHES) {
|
||||||
|
this->skip_update_ = true; // skip send touch events, touchscreen is not ready yet.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +107,6 @@ void GT911Touchscreen::update_touches() {
|
|||||||
err = this->read(data[0], sizeof(data[0]) * num_of_touches + 1);
|
err = this->read(data[0], sizeof(data[0]) * num_of_touches + 1);
|
||||||
ERROR_CHECK(err);
|
ERROR_CHECK(err);
|
||||||
|
|
||||||
this->skip_update_ = false; // All error checks passed, send touch events
|
|
||||||
for (uint8_t i = 0; i != num_of_touches; i++) {
|
for (uint8_t i = 0; i != num_of_touches; i++) {
|
||||||
uint16_t id = data[i][0];
|
uint16_t id = data[i][0];
|
||||||
uint16_t x = encode_uint16(data[i][2], data[i][1]);
|
uint16_t x = encode_uint16(data[i][2], data[i][1]);
|
||||||
|
@@ -15,20 +15,8 @@ class GT911ButtonListener {
|
|||||||
|
|
||||||
class GT911Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice {
|
class GT911Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice {
|
||||||
public:
|
public:
|
||||||
/// @brief Initialize the GT911 touchscreen.
|
|
||||||
///
|
|
||||||
/// If @ref reset_pin_ is set, the touchscreen will be hardware reset,
|
|
||||||
/// and the rest of the setup will be scheduled to run 50ms later using @ref set_timeout()
|
|
||||||
/// to allow the device to stabilize after reset.
|
|
||||||
///
|
|
||||||
/// If @ref interrupt_pin_ is set, it will be temporarily configured during reset
|
|
||||||
/// to control I2C address selection.
|
|
||||||
///
|
|
||||||
/// After the timeout, or immediately if no reset is performed, @ref setup_internal_()
|
|
||||||
/// is called to complete the initialization.
|
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
bool can_proceed() override { return this->setup_done_; }
|
|
||||||
|
|
||||||
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
||||||
void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }
|
void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }
|
||||||
@@ -37,20 +25,8 @@ class GT911Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
|
|||||||
protected:
|
protected:
|
||||||
void update_touches() override;
|
void update_touches() override;
|
||||||
|
|
||||||
/// @brief Perform the internal setup routine for the GT911 touchscreen.
|
InternalGPIOPin *interrupt_pin_{};
|
||||||
///
|
GPIOPin *reset_pin_{};
|
||||||
/// This function checks the I2C address, configures the interrupt pin (if available),
|
|
||||||
/// reads the touchscreen mode from the controller, and attempts to read calibration
|
|
||||||
/// data (maximum X and Y values) if not already set.
|
|
||||||
///
|
|
||||||
/// On success, sets @ref setup_done_ to true.
|
|
||||||
/// On failure, calls @ref mark_failed() with an appropriate error message.
|
|
||||||
void setup_internal_();
|
|
||||||
/// @brief True if the touchscreen setup has completed successfully.
|
|
||||||
bool setup_done_{false};
|
|
||||||
|
|
||||||
InternalGPIOPin *interrupt_pin_{nullptr};
|
|
||||||
GPIOPin *reset_pin_{nullptr};
|
|
||||||
std::vector<GT911ButtonListener *> button_listeners_;
|
std::vector<GT911ButtonListener *> button_listeners_;
|
||||||
uint8_t button_state_{0xFF}; // last button state. Initial FF guarantees first update.
|
uint8_t button_state_{0xFF}; // last button state. Initial FF guarantees first update.
|
||||||
};
|
};
|
||||||
|
@@ -212,7 +212,7 @@ def validate_use_legacy(value):
|
|||||||
f"All i2s_audio components must set {CONF_USE_LEGACY} to the same value."
|
f"All i2s_audio components must set {CONF_USE_LEGACY} to the same value."
|
||||||
)
|
)
|
||||||
if (not value[CONF_USE_LEGACY]) and (CORE.using_arduino):
|
if (not value[CONF_USE_LEGACY]) and (CORE.using_arduino):
|
||||||
raise cv.Invalid("Arduino supports only the legacy i2s driver")
|
raise cv.Invalid("Arduino supports only the legacy i2s driver.")
|
||||||
_use_legacy_driver = value[CONF_USE_LEGACY]
|
_use_legacy_driver = value[CONF_USE_LEGACY]
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@@ -92,7 +92,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
|
|
||||||
def _final_validate(_):
|
def _final_validate(_):
|
||||||
if not use_legacy():
|
if not use_legacy():
|
||||||
raise cv.Invalid("I2S media player is only compatible with legacy i2s driver")
|
raise cv.Invalid("I2S media player is only compatible with legacy i2s driver.")
|
||||||
|
|
||||||
|
|
||||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||||
|
@@ -122,7 +122,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
|
|
||||||
def _final_validate(config):
|
def _final_validate(config):
|
||||||
if not use_legacy() and config[CONF_ADC_TYPE] == "internal":
|
if not use_legacy() and config[CONF_ADC_TYPE] == "internal":
|
||||||
raise cv.Invalid("Internal ADC is only compatible with legacy i2s driver")
|
raise cv.Invalid("Internal ADC is only compatible with legacy i2s driver.")
|
||||||
|
|
||||||
|
|
||||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||||
|
@@ -24,6 +24,9 @@ static const uint32_t READ_DURATION_MS = 16;
|
|||||||
static const size_t TASK_STACK_SIZE = 4096;
|
static const size_t TASK_STACK_SIZE = 4096;
|
||||||
static const ssize_t TASK_PRIORITY = 23;
|
static const ssize_t TASK_PRIORITY = 23;
|
||||||
|
|
||||||
|
// Use an exponential moving average to correct a DC offset with weight factor 1/1000
|
||||||
|
static const int32_t DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR = 1000;
|
||||||
|
|
||||||
static const char *const TAG = "i2s_audio.microphone";
|
static const char *const TAG = "i2s_audio.microphone";
|
||||||
|
|
||||||
enum MicrophoneEventGroupBits : uint32_t {
|
enum MicrophoneEventGroupBits : uint32_t {
|
||||||
@@ -378,57 +381,26 @@ void I2SAudioMicrophone::mic_task(void *params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void I2SAudioMicrophone::fix_dc_offset_(std::vector<uint8_t> &data) {
|
void I2SAudioMicrophone::fix_dc_offset_(std::vector<uint8_t> &data) {
|
||||||
/**
|
|
||||||
* From https://www.musicdsp.org/en/latest/Filters/135-dc-filter.html:
|
|
||||||
*
|
|
||||||
* y(n) = x(n) - x(n-1) + R * y(n-1)
|
|
||||||
* R = 1 - (pi * 2 * frequency / samplerate)
|
|
||||||
*
|
|
||||||
* From https://en.wikipedia.org/wiki/Hearing_range:
|
|
||||||
* The human range is commonly given as 20Hz up.
|
|
||||||
*
|
|
||||||
* From https://en.wikipedia.org/wiki/High-resolution_audio:
|
|
||||||
* A reasonable upper bound for sample rate seems to be 96kHz.
|
|
||||||
*
|
|
||||||
* Calculate R value for 20Hz on a 96kHz sample rate:
|
|
||||||
* R = 1 - (pi * 2 * 20 / 96000)
|
|
||||||
* R = 0.9986910031
|
|
||||||
*
|
|
||||||
* Transform floating point to bit-shifting approximation:
|
|
||||||
* output = input - prev_input + R * prev_output
|
|
||||||
* output = input - prev_input + (prev_output - (prev_output >> S))
|
|
||||||
*
|
|
||||||
* Approximate bit-shift value S from R:
|
|
||||||
* R = 1 - (1 >> S)
|
|
||||||
* R = 1 - (1 / 2^S)
|
|
||||||
* R = 1 - 2^-S
|
|
||||||
* 0.9986910031 = 1 - 2^-S
|
|
||||||
* S = 9.57732 ~= 10
|
|
||||||
*
|
|
||||||
* Actual R from S:
|
|
||||||
* R = 1 - 2^-10 = 0.9990234375
|
|
||||||
*
|
|
||||||
* Confirm this has effect outside human hearing on 96000kHz sample:
|
|
||||||
* 0.9990234375 = 1 - (pi * 2 * f / 96000)
|
|
||||||
* f = 14.9208Hz
|
|
||||||
*
|
|
||||||
* Confirm this has effect outside human hearing on PDM 16kHz sample:
|
|
||||||
* 0.9990234375 = 1 - (pi * 2 * f / 16000)
|
|
||||||
* f = 2.4868Hz
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
const uint8_t dc_filter_shift = 10;
|
|
||||||
const size_t bytes_per_sample = this->audio_stream_info_.samples_to_bytes(1);
|
const size_t bytes_per_sample = this->audio_stream_info_.samples_to_bytes(1);
|
||||||
const uint32_t total_samples = this->audio_stream_info_.bytes_to_samples(data.size());
|
const uint32_t total_samples = this->audio_stream_info_.bytes_to_samples(data.size());
|
||||||
|
|
||||||
|
if (total_samples == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t offset_accumulator = 0;
|
||||||
for (uint32_t sample_index = 0; sample_index < total_samples; ++sample_index) {
|
for (uint32_t sample_index = 0; sample_index < total_samples; ++sample_index) {
|
||||||
const uint32_t byte_index = sample_index * bytes_per_sample;
|
const uint32_t byte_index = sample_index * bytes_per_sample;
|
||||||
int32_t input = audio::unpack_audio_sample_to_q31(&data[byte_index], bytes_per_sample);
|
int32_t sample = audio::unpack_audio_sample_to_q31(&data[byte_index], bytes_per_sample);
|
||||||
int32_t output = input - this->dc_offset_prev_input_ +
|
offset_accumulator += sample;
|
||||||
(this->dc_offset_prev_output_ - (this->dc_offset_prev_output_ >> dc_filter_shift));
|
sample -= this->dc_offset_;
|
||||||
this->dc_offset_prev_input_ = input;
|
audio::pack_q31_as_audio_sample(sample, &data[byte_index], bytes_per_sample);
|
||||||
this->dc_offset_prev_output_ = output;
|
|
||||||
audio::pack_q31_as_audio_sample(output, &data[byte_index], bytes_per_sample);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const int32_t new_offset = offset_accumulator / total_samples;
|
||||||
|
this->dc_offset_ = new_offset / DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR +
|
||||||
|
(DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR - 1) * this->dc_offset_ /
|
||||||
|
DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t I2SAudioMicrophone::read_(uint8_t *buf, size_t len, TickType_t ticks_to_wait) {
|
size_t I2SAudioMicrophone::read_(uint8_t *buf, size_t len, TickType_t ticks_to_wait) {
|
||||||
|
@@ -82,8 +82,7 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
|||||||
|
|
||||||
bool correct_dc_offset_;
|
bool correct_dc_offset_;
|
||||||
bool locked_driver_{false};
|
bool locked_driver_{false};
|
||||||
int32_t dc_offset_prev_input_{0};
|
int32_t dc_offset_{0};
|
||||||
int32_t dc_offset_prev_output_{0};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace i2s_audio
|
} // namespace i2s_audio
|
||||||
|
@@ -163,7 +163,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
def _final_validate(config):
|
def _final_validate(config):
|
||||||
if not use_legacy():
|
if not use_legacy():
|
||||||
if config[CONF_DAC_TYPE] == "internal":
|
if config[CONF_DAC_TYPE] == "internal":
|
||||||
raise cv.Invalid("Internal DAC is only compatible with legacy i2s driver")
|
raise cv.Invalid("Internal DAC is only compatible with legacy i2s driver.")
|
||||||
if config[CONF_I2S_COMM_FMT] == "stand_max":
|
if config[CONF_I2S_COMM_FMT] == "stand_max":
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
"I2S standard max format only implemented with legacy i2s driver."
|
"I2S standard max format only implemented with legacy i2s driver."
|
||||||
|
@@ -2,7 +2,7 @@ import esphome.codegen as cg
|
|||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.core import coroutine_with_priority
|
from esphome.core import coroutine_with_priority
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@OttoWinter"]
|
||||||
json_ns = cg.esphome_ns.namespace("json")
|
json_ns = cg.esphome_ns.namespace("json")
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
@@ -14,16 +14,18 @@ ld2410_ns = cg.esphome_ns.namespace("ld2410")
|
|||||||
LD2410Component = ld2410_ns.class_("LD2410Component", cg.Component, uart.UARTDevice)
|
LD2410Component = ld2410_ns.class_("LD2410Component", cg.Component, uart.UARTDevice)
|
||||||
|
|
||||||
CONF_LD2410_ID = "ld2410_id"
|
CONF_LD2410_ID = "ld2410_id"
|
||||||
|
|
||||||
CONF_MAX_MOVE_DISTANCE = "max_move_distance"
|
CONF_MAX_MOVE_DISTANCE = "max_move_distance"
|
||||||
CONF_MAX_STILL_DISTANCE = "max_still_distance"
|
CONF_MAX_STILL_DISTANCE = "max_still_distance"
|
||||||
CONF_MOVE_THRESHOLDS = [f"g{x}_move_threshold" for x in range(9)]
|
|
||||||
CONF_STILL_THRESHOLDS = [f"g{x}_still_threshold" for x in range(9)]
|
CONF_STILL_THRESHOLDS = [f"g{x}_still_threshold" for x in range(9)]
|
||||||
|
CONF_MOVE_THRESHOLDS = [f"g{x}_move_threshold" for x in range(9)]
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema(
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(LD2410Component),
|
cv.GenerateID(): cv.declare_id(LD2410Component),
|
||||||
cv.Optional(CONF_THROTTLE): cv.invalid(
|
cv.Optional(CONF_THROTTLE, default="1000ms"): cv.All(
|
||||||
f"{CONF_THROTTLE} has been removed; use per-sensor filters, instead"
|
cv.positive_time_period_milliseconds,
|
||||||
|
cv.Range(min=cv.TimePeriod(milliseconds=1)),
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_MAX_MOVE_DISTANCE): cv.invalid(
|
cv.Optional(CONF_MAX_MOVE_DISTANCE): cv.invalid(
|
||||||
f"The '{CONF_MAX_MOVE_DISTANCE}' option has been moved to the '{CONF_MAX_MOVE_DISTANCE}'"
|
f"The '{CONF_MAX_MOVE_DISTANCE}' option has been moved to the '{CONF_MAX_MOVE_DISTANCE}'"
|
||||||
@@ -73,6 +75,7 @@ async def to_code(config):
|
|||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
await uart.register_uart_device(var, config)
|
await uart.register_uart_device(var, config)
|
||||||
|
cg.add(var.set_throttle(config[CONF_THROTTLE]))
|
||||||
|
|
||||||
|
|
||||||
CALIBRATION_ACTION_SCHEMA = maybe_simple_id(
|
CALIBRATION_ACTION_SCHEMA = maybe_simple_id(
|
||||||
|
@@ -22,23 +22,19 @@ CONFIG_SCHEMA = {
|
|||||||
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
|
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
|
||||||
cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema(
|
cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema(
|
||||||
device_class=DEVICE_CLASS_OCCUPANCY,
|
device_class=DEVICE_CLASS_OCCUPANCY,
|
||||||
filters=[{"settle": cv.TimePeriod(milliseconds=1000)}],
|
|
||||||
icon=ICON_ACCOUNT,
|
icon=ICON_ACCOUNT,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_HAS_MOVING_TARGET): binary_sensor.binary_sensor_schema(
|
cv.Optional(CONF_HAS_MOVING_TARGET): binary_sensor.binary_sensor_schema(
|
||||||
device_class=DEVICE_CLASS_MOTION,
|
device_class=DEVICE_CLASS_MOTION,
|
||||||
filters=[{"settle": cv.TimePeriod(milliseconds=1000)}],
|
|
||||||
icon=ICON_MOTION_SENSOR,
|
icon=ICON_MOTION_SENSOR,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_HAS_STILL_TARGET): binary_sensor.binary_sensor_schema(
|
cv.Optional(CONF_HAS_STILL_TARGET): binary_sensor.binary_sensor_schema(
|
||||||
device_class=DEVICE_CLASS_OCCUPANCY,
|
device_class=DEVICE_CLASS_OCCUPANCY,
|
||||||
filters=[{"settle": cv.TimePeriod(milliseconds=1000)}],
|
|
||||||
icon=ICON_MOTION_SENSOR,
|
icon=ICON_MOTION_SENSOR,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_OUT_PIN_PRESENCE_STATUS): binary_sensor.binary_sensor_schema(
|
cv.Optional(CONF_OUT_PIN_PRESENCE_STATUS): binary_sensor.binary_sensor_schema(
|
||||||
device_class=DEVICE_CLASS_PRESENCE,
|
device_class=DEVICE_CLASS_PRESENCE,
|
||||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
filters=[{"settle": cv.TimePeriod(milliseconds=1000)}],
|
|
||||||
icon=ICON_ACCOUNT,
|
icon=ICON_ACCOUNT,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@@ -188,8 +188,9 @@ void LD2410Component::dump_config() {
|
|||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
"LD2410:\n"
|
"LD2410:\n"
|
||||||
" Firmware version: %s\n"
|
" Firmware version: %s\n"
|
||||||
" MAC address: %s",
|
" MAC address: %s\n"
|
||||||
version.c_str(), mac_str.c_str());
|
" Throttle: %u ms",
|
||||||
|
version.c_str(), mac_str.c_str(), this->throttle_);
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
ESP_LOGCONFIG(TAG, "Binary Sensors:");
|
ESP_LOGCONFIG(TAG, "Binary Sensors:");
|
||||||
LOG_BINARY_SENSOR(" ", "Target", this->target_binary_sensor_);
|
LOG_BINARY_SENSOR(" ", "Target", this->target_binary_sensor_);
|
||||||
@@ -305,6 +306,11 @@ void LD2410Component::send_command_(uint8_t command, const uint8_t *command_valu
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LD2410Component::handle_periodic_data_() {
|
void LD2410Component::handle_periodic_data_() {
|
||||||
|
// Reduce data update rate to reduce home assistant database growth
|
||||||
|
// Check this first to prevent unnecessary processing done in later checks/parsing
|
||||||
|
if (App.get_loop_component_start_time() - this->last_periodic_millis_ < this->throttle_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 4 frame header bytes + 2 length bytes + 1 data end byte + 1 crc byte + 4 frame footer bytes
|
// 4 frame header bytes + 2 length bytes + 1 data end byte + 1 crc byte + 4 frame footer bytes
|
||||||
// data header=0xAA, data footer=0x55, crc=0x00
|
// data header=0xAA, data footer=0x55, crc=0x00
|
||||||
if (this->buffer_pos_ < 12 || !ld2410::validate_header_footer(DATA_FRAME_HEADER, this->buffer_data_) ||
|
if (this->buffer_pos_ < 12 || !ld2410::validate_header_footer(DATA_FRAME_HEADER, this->buffer_data_) ||
|
||||||
@@ -312,6 +318,9 @@ void LD2410Component::handle_periodic_data_() {
|
|||||||
this->buffer_data_[this->buffer_pos_ - 5] != CHECK) {
|
this->buffer_data_[this->buffer_pos_ - 5] != CHECK) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Save the timestamp after validating the frame so, if invalid, we'll take the next frame immediately
|
||||||
|
this->last_periodic_millis_ = App.get_loop_component_start_time();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Data Type: 7th
|
Data Type: 7th
|
||||||
0x01: Engineering mode
|
0x01: Engineering mode
|
||||||
|
@@ -93,6 +93,7 @@ class LD2410Component : public Component, public uart::UARTDevice {
|
|||||||
void set_gate_move_sensor(uint8_t gate, sensor::Sensor *s);
|
void set_gate_move_sensor(uint8_t gate, sensor::Sensor *s);
|
||||||
void set_gate_still_sensor(uint8_t gate, sensor::Sensor *s);
|
void set_gate_still_sensor(uint8_t gate, sensor::Sensor *s);
|
||||||
#endif
|
#endif
|
||||||
|
void set_throttle(uint16_t value) { this->throttle_ = value; };
|
||||||
void set_bluetooth_password(const std::string &password);
|
void set_bluetooth_password(const std::string &password);
|
||||||
void set_engineering_mode(bool enable);
|
void set_engineering_mode(bool enable);
|
||||||
void read_all_info();
|
void read_all_info();
|
||||||
@@ -115,6 +116,8 @@ class LD2410Component : public Component, public uart::UARTDevice {
|
|||||||
void query_light_control_();
|
void query_light_control_();
|
||||||
void restart_();
|
void restart_();
|
||||||
|
|
||||||
|
uint32_t last_periodic_millis_ = 0;
|
||||||
|
uint16_t throttle_ = 0;
|
||||||
uint8_t light_function_ = 0;
|
uint8_t light_function_ = 0;
|
||||||
uint8_t light_threshold_ = 0;
|
uint8_t light_threshold_ = 0;
|
||||||
uint8_t out_pin_level_ = 0;
|
uint8_t out_pin_level_ = 0;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user