mirror of
https://github.com/esphome/esphome.git
synced 2025-08-03 08:57:47 +00:00
Merge branch 'dev' into runtime_stats
This commit is contained in:
commit
f1b888b309
1
.clang-tidy.hash
Normal file
1
.clang-tidy.hash
Normal file
@ -0,0 +1 @@
|
|||||||
|
a3cdfc378d28b53b416a1d5bf0ab9077ee18867f0d39436ea8013cf5a4ead87a
|
2
.github/actions/restore-python/action.yml
vendored
2
.github/actions/restore-python/action.yml
vendored
@ -41,7 +41,7 @@ runs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
python -m venv venv
|
python -m venv venv
|
||||||
./venv/Scripts/activate
|
source ./venv/Scripts/activate
|
||||||
python --version
|
python --version
|
||||||
pip install -r requirements.txt -r requirements_test.txt
|
pip install -r requirements.txt -r requirements_test.txt
|
||||||
pip install -e .
|
pip install -e .
|
||||||
|
76
.github/workflows/ci-clang-tidy-hash.yml
vendored
Normal file
76
.github/workflows/ci-clang-tidy-hash.yml
vendored
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
name: Clang-tidy Hash CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- ".clang-tidy"
|
||||||
|
- "platformio.ini"
|
||||||
|
- "requirements_dev.txt"
|
||||||
|
- ".clang-tidy.hash"
|
||||||
|
- "script/clang_tidy_hash.py"
|
||||||
|
- ".github/workflows/ci-clang-tidy-hash.yml"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
verify-hash:
|
||||||
|
name: Verify clang-tidy hash
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4.2.2
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5.6.0
|
||||||
|
with:
|
||||||
|
python-version: "3.11"
|
||||||
|
|
||||||
|
- name: Verify hash
|
||||||
|
run: |
|
||||||
|
python script/clang_tidy_hash.py --verify
|
||||||
|
|
||||||
|
- if: failure()
|
||||||
|
name: Show hash details
|
||||||
|
run: |
|
||||||
|
python script/clang_tidy_hash.py
|
||||||
|
echo "## Job Failed" | tee -a $GITHUB_STEP_SUMMARY
|
||||||
|
echo "You have modified clang-tidy configuration but have not updated the hash." | tee -a $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Please run 'script/clang_tidy_hash.py --update' and commit the changes." | tee -a $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
- if: failure()
|
||||||
|
name: Request changes
|
||||||
|
uses: actions/github-script@v7.0.1
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
await github.rest.pulls.createReview({
|
||||||
|
pull_number: context.issue.number,
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
event: 'REQUEST_CHANGES',
|
||||||
|
body: 'You have modified clang-tidy configuration but have not updated the hash.\nPlease run `script/clang_tidy_hash.py --update` and commit the changes.'
|
||||||
|
})
|
||||||
|
|
||||||
|
- if: success()
|
||||||
|
name: Dismiss review
|
||||||
|
uses: actions/github-script@v7.0.1
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
let reviews = await github.rest.pulls.listReviews({
|
||||||
|
pull_number: context.issue.number,
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo
|
||||||
|
});
|
||||||
|
for (let review of reviews.data) {
|
||||||
|
if (review.user.login === 'github-actions[bot]' && review.state === 'CHANGES_REQUESTED') {
|
||||||
|
await github.rest.pulls.dismissReview({
|
||||||
|
pull_number: context.issue.number,
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
review_id: review.id,
|
||||||
|
message: 'Clang-tidy hash now matches configuration.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
151
.github/workflows/ci.yml
vendored
151
.github/workflows/ci.yml
vendored
@ -66,6 +66,8 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs:
|
needs:
|
||||||
- common
|
- common
|
||||||
|
- determine-jobs
|
||||||
|
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@v4.2.2
|
uses: actions/checkout@v4.2.2
|
||||||
@ -87,6 +89,8 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs:
|
needs:
|
||||||
- common
|
- common
|
||||||
|
- determine-jobs
|
||||||
|
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@v4.2.2
|
uses: actions/checkout@v4.2.2
|
||||||
@ -108,6 +112,8 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs:
|
needs:
|
||||||
- common
|
- common
|
||||||
|
- determine-jobs
|
||||||
|
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@v4.2.2
|
uses: actions/checkout@v4.2.2
|
||||||
@ -129,6 +135,8 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs:
|
needs:
|
||||||
- common
|
- common
|
||||||
|
- determine-jobs
|
||||||
|
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@v4.2.2
|
uses: actions/checkout@v4.2.2
|
||||||
@ -204,6 +212,7 @@ jobs:
|
|||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v4.2.2
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
|
id: restore-python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
@ -213,7 +222,7 @@ jobs:
|
|||||||
- name: Run pytest
|
- name: Run pytest
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
run: |
|
run: |
|
||||||
./venv/Scripts/activate
|
. ./venv/Scripts/activate.ps1
|
||||||
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
|
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
|
||||||
- name: Run pytest
|
- name: Run pytest
|
||||||
if: matrix.os == 'ubuntu-latest' || matrix.os == 'macOS-latest'
|
if: matrix.os == 'ubuntu-latest' || matrix.os == 'macOS-latest'
|
||||||
@ -224,12 +233,61 @@ jobs:
|
|||||||
uses: codecov/codecov-action@v5.4.3
|
uses: codecov/codecov-action@v5.4.3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
- name: Save Python virtual environment cache
|
||||||
|
if: github.ref == 'refs/heads/dev'
|
||||||
|
uses: actions/cache/save@v4.2.3
|
||||||
|
with:
|
||||||
|
path: venv
|
||||||
|
key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||||
|
|
||||||
|
determine-jobs:
|
||||||
|
name: Determine which jobs to run
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
needs:
|
||||||
|
- common
|
||||||
|
outputs:
|
||||||
|
integration-tests: ${{ steps.determine.outputs.integration-tests }}
|
||||||
|
clang-tidy: ${{ steps.determine.outputs.clang-tidy }}
|
||||||
|
clang-format: ${{ steps.determine.outputs.clang-format }}
|
||||||
|
python-linters: ${{ steps.determine.outputs.python-linters }}
|
||||||
|
changed-components: ${{ steps.determine.outputs.changed-components }}
|
||||||
|
component-test-count: ${{ steps.determine.outputs.component-test-count }}
|
||||||
|
steps:
|
||||||
|
- name: Check out code from GitHub
|
||||||
|
uses: actions/checkout@v4.2.2
|
||||||
|
with:
|
||||||
|
# Fetch enough history to find the merge base
|
||||||
|
fetch-depth: 2
|
||||||
|
- name: Restore Python
|
||||||
|
uses: ./.github/actions/restore-python
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||||
|
- name: Determine which tests to run
|
||||||
|
id: determine
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
. venv/bin/activate
|
||||||
|
output=$(python script/determine-jobs.py)
|
||||||
|
echo "Test determination output:"
|
||||||
|
echo "$output" | jq
|
||||||
|
|
||||||
|
# Extract individual fields
|
||||||
|
echo "integration-tests=$(echo "$output" | jq -r '.integration_tests')" >> $GITHUB_OUTPUT
|
||||||
|
echo "clang-tidy=$(echo "$output" | jq -r '.clang_tidy')" >> $GITHUB_OUTPUT
|
||||||
|
echo "clang-format=$(echo "$output" | jq -r '.clang_format')" >> $GITHUB_OUTPUT
|
||||||
|
echo "python-linters=$(echo "$output" | jq -r '.python_linters')" >> $GITHUB_OUTPUT
|
||||||
|
echo "changed-components=$(echo "$output" | jq -c '.changed_components')" >> $GITHUB_OUTPUT
|
||||||
|
echo "component-test-count=$(echo "$output" | jq -r '.component_test_count')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
integration-tests:
|
integration-tests:
|
||||||
name: Run integration tests
|
name: Run integration tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- common
|
- common
|
||||||
|
- determine-jobs
|
||||||
|
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@v4.2.2
|
uses: actions/checkout@v4.2.2
|
||||||
@ -264,6 +322,8 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs:
|
needs:
|
||||||
- common
|
- common
|
||||||
|
- determine-jobs
|
||||||
|
if: needs.determine-jobs.outputs.clang-format == 'true'
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v4.2.2
|
||||||
@ -297,6 +357,10 @@ jobs:
|
|||||||
- pylint
|
- pylint
|
||||||
- pytest
|
- pytest
|
||||||
- pyupgrade
|
- pyupgrade
|
||||||
|
- determine-jobs
|
||||||
|
if: needs.determine-jobs.outputs.clang-tidy == 'true'
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
max-parallel: 2
|
max-parallel: 2
|
||||||
@ -335,6 +399,10 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v4.2.2
|
||||||
|
with:
|
||||||
|
# Need history for HEAD~1 to work for checking changed files
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
@ -346,14 +414,14 @@ jobs:
|
|||||||
uses: actions/cache@v4.2.3
|
uses: actions/cache@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-${{ matrix.pio_cache_key }}
|
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.3
|
uses: actions/cache/restore@v4.2.3
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-${{ matrix.pio_cache_key }}
|
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||||
|
|
||||||
- name: Register problem matchers
|
- name: Register problem matchers
|
||||||
run: |
|
run: |
|
||||||
@ -367,10 +435,28 @@ jobs:
|
|||||||
mkdir -p .temp
|
mkdir -p .temp
|
||||||
pio run --list-targets -e esp32-idf-tidy
|
pio run --list-targets -e esp32-idf-tidy
|
||||||
|
|
||||||
|
- name: Check if full clang-tidy scan needed
|
||||||
|
id: check_full_scan
|
||||||
|
run: |
|
||||||
|
. venv/bin/activate
|
||||||
|
if python script/clang_tidy_hash.py --check; then
|
||||||
|
echo "full_scan=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "reason=hash_changed" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "full_scan=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "reason=normal" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Run clang-tidy
|
- name: Run clang-tidy
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
|
if [ "${{ steps.check_full_scan.outputs.full_scan }}" = "true" ]; then
|
||||||
|
echo "Running FULL clang-tidy scan (hash changed)"
|
||||||
script/clang-tidy --all-headers --fix ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
|
script/clang-tidy --all-headers --fix ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
|
||||||
|
else
|
||||||
|
echo "Running clang-tidy on changed files only"
|
||||||
|
script/clang-tidy --all-headers --fix --changed ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
|
||||||
|
fi
|
||||||
env:
|
env:
|
||||||
# Also cache libdeps, store them in a ~/.platformio subfolder
|
# Also cache libdeps, store them in a ~/.platformio subfolder
|
||||||
PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
|
PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
|
||||||
@ -380,59 +466,18 @@ jobs:
|
|||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
if: always()
|
if: always()
|
||||||
|
|
||||||
list-components:
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
needs:
|
|
||||||
- common
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
outputs:
|
|
||||||
components: ${{ steps.list-components.outputs.components }}
|
|
||||||
count: ${{ steps.list-components.outputs.count }}
|
|
||||||
steps:
|
|
||||||
- name: Check out code from GitHub
|
|
||||||
uses: actions/checkout@v4.2.2
|
|
||||||
with:
|
|
||||||
# Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works.
|
|
||||||
fetch-depth: 500
|
|
||||||
- name: Get target branch
|
|
||||||
id: target-branch
|
|
||||||
run: |
|
|
||||||
echo "branch=${{ github.event.pull_request.base.ref }}" >> $GITHUB_OUTPUT
|
|
||||||
- name: Fetch ${{ steps.target-branch.outputs.branch }} branch
|
|
||||||
run: |
|
|
||||||
git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +refs/heads/${{ steps.target-branch.outputs.branch }}:refs/remotes/origin/${{ steps.target-branch.outputs.branch }}
|
|
||||||
git merge-base refs/remotes/origin/${{ steps.target-branch.outputs.branch }} HEAD
|
|
||||||
- name: Restore Python
|
|
||||||
uses: ./.github/actions/restore-python
|
|
||||||
with:
|
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
|
||||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
|
||||||
- name: Find changed components
|
|
||||||
id: list-components
|
|
||||||
run: |
|
|
||||||
. venv/bin/activate
|
|
||||||
components=$(script/list-components.py --changed --branch ${{ steps.target-branch.outputs.branch }})
|
|
||||||
output_components=$(echo "$components" | jq -R -s -c 'split("\n")[:-1] | map(select(length > 0))')
|
|
||||||
count=$(echo "$output_components" | jq length)
|
|
||||||
|
|
||||||
echo "components=$output_components" >> $GITHUB_OUTPUT
|
|
||||||
echo "count=$count" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
echo "$count Components:"
|
|
||||||
echo "$output_components" | jq
|
|
||||||
|
|
||||||
test-build-components:
|
test-build-components:
|
||||||
name: Component test ${{ matrix.file }}
|
name: Component test ${{ matrix.file }}
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs:
|
needs:
|
||||||
- common
|
- common
|
||||||
- list-components
|
- determine-jobs
|
||||||
if: github.event_name == 'pull_request' && fromJSON(needs.list-components.outputs.count) > 0 && fromJSON(needs.list-components.outputs.count) < 100
|
if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) > 0 && fromJSON(needs.determine-jobs.outputs.component-test-count) < 100
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
max-parallel: 2
|
max-parallel: 2
|
||||||
matrix:
|
matrix:
|
||||||
file: ${{ fromJson(needs.list-components.outputs.components) }}
|
file: ${{ fromJson(needs.determine-jobs.outputs.changed-components) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
@ -460,8 +505,8 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs:
|
needs:
|
||||||
- common
|
- common
|
||||||
- list-components
|
- determine-jobs
|
||||||
if: github.event_name == 'pull_request' && fromJSON(needs.list-components.outputs.count) >= 100
|
if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) >= 100
|
||||||
outputs:
|
outputs:
|
||||||
matrix: ${{ steps.split.outputs.components }}
|
matrix: ${{ steps.split.outputs.components }}
|
||||||
steps:
|
steps:
|
||||||
@ -470,7 +515,7 @@ jobs:
|
|||||||
- name: Split components into 20 groups
|
- name: Split components into 20 groups
|
||||||
id: split
|
id: split
|
||||||
run: |
|
run: |
|
||||||
components=$(echo '${{ needs.list-components.outputs.components }}' | jq -c '.[]' | shuf | jq -s -c '[_nwise(20) | join(" ")]')
|
components=$(echo '${{ needs.determine-jobs.outputs.changed-components }}' | jq -c '.[]' | shuf | jq -s -c '[_nwise(20) | join(" ")]')
|
||||||
echo "components=$components" >> $GITHUB_OUTPUT
|
echo "components=$components" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
test-build-components-split:
|
test-build-components-split:
|
||||||
@ -478,9 +523,9 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs:
|
needs:
|
||||||
- common
|
- common
|
||||||
- list-components
|
- determine-jobs
|
||||||
- test-build-components-splitter
|
- test-build-components-splitter
|
||||||
if: github.event_name == 'pull_request' && fromJSON(needs.list-components.outputs.count) >= 100
|
if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) >= 100
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
max-parallel: 4
|
max-parallel: 4
|
||||||
@ -531,7 +576,7 @@ jobs:
|
|||||||
- integration-tests
|
- integration-tests
|
||||||
- pyupgrade
|
- pyupgrade
|
||||||
- clang-tidy
|
- clang-tidy
|
||||||
- list-components
|
- determine-jobs
|
||||||
- test-build-components
|
- test-build-components
|
||||||
- test-build-components-splitter
|
- test-build-components-splitter
|
||||||
- test-build-components-split
|
- test-build-components-split
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: v0.12.2
|
rev: v0.12.3
|
||||||
hooks:
|
hooks:
|
||||||
# Run the linter.
|
# Run the linter.
|
||||||
- id: ruff
|
- id: ruff
|
||||||
@ -48,3 +48,10 @@ repos:
|
|||||||
entry: python3 script/run-in-env.py pylint
|
entry: python3 script/run-in-env.py pylint
|
||||||
language: system
|
language: system
|
||||||
types: [python]
|
types: [python]
|
||||||
|
- id: clang-tidy-hash
|
||||||
|
name: Update clang-tidy hash
|
||||||
|
entry: python script/clang_tidy_hash.py --update-if-changed
|
||||||
|
language: python
|
||||||
|
files: ^(\.clang-tidy|platformio\.ini|requirements_dev\.txt)$
|
||||||
|
pass_filenames: false
|
||||||
|
additional_dependencies: []
|
||||||
|
@ -28,7 +28,7 @@ esphome/components/aic3204/* @kbx81
|
|||||||
esphome/components/airthings_ble/* @jeromelaban
|
esphome/components/airthings_ble/* @jeromelaban
|
||||||
esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau
|
esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau
|
||||||
esphome/components/airthings_wave_mini/* @ncareau
|
esphome/components/airthings_wave_mini/* @ncareau
|
||||||
esphome/components/airthings_wave_plus/* @jeromelaban
|
esphome/components/airthings_wave_plus/* @jeromelaban @precurse
|
||||||
esphome/components/alarm_control_panel/* @grahambrown11 @hwstar
|
esphome/components/alarm_control_panel/* @grahambrown11 @hwstar
|
||||||
esphome/components/alpha3/* @jan-hofmeier
|
esphome/components/alpha3/* @jan-hofmeier
|
||||||
esphome/components/am2315c/* @swoboda1337
|
esphome/components/am2315c/* @swoboda1337
|
||||||
@ -170,6 +170,7 @@ esphome/components/ft5x06/* @clydebarrow
|
|||||||
esphome/components/ft63x6/* @gpambrozio
|
esphome/components/ft63x6/* @gpambrozio
|
||||||
esphome/components/gcja5/* @gcormier
|
esphome/components/gcja5/* @gcormier
|
||||||
esphome/components/gdk101/* @Szewcson
|
esphome/components/gdk101/* @Szewcson
|
||||||
|
esphome/components/gl_r01_i2c/* @pkejval
|
||||||
esphome/components/globals/* @esphome/core
|
esphome/components/globals/* @esphome/core
|
||||||
esphome/components/gp2y1010au0f/* @zry98
|
esphome/components/gp2y1010au0f/* @zry98
|
||||||
esphome/components/gp8403/* @jesserockz
|
esphome/components/gp8403/* @jesserockz
|
||||||
@ -254,6 +255,7 @@ esphome/components/ln882x/* @lamauny
|
|||||||
esphome/components/lock/* @esphome/core
|
esphome/components/lock/* @esphome/core
|
||||||
esphome/components/logger/* @esphome/core
|
esphome/components/logger/* @esphome/core
|
||||||
esphome/components/logger/select/* @clydebarrow
|
esphome/components/logger/select/* @clydebarrow
|
||||||
|
esphome/components/lps22/* @nagisa
|
||||||
esphome/components/ltr390/* @latonita @sjtrny
|
esphome/components/ltr390/* @latonita @sjtrny
|
||||||
esphome/components/ltr501/* @latonita
|
esphome/components/ltr501/* @latonita
|
||||||
esphome/components/ltr_als_ps/* @latonita
|
esphome/components/ltr_als_ps/* @latonita
|
||||||
|
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.7.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
|
||||||
|
@ -1 +1 @@
|
|||||||
CODEOWNERS = ["@jeromelaban"]
|
CODEOWNERS = ["@jeromelaban", "@precurse"]
|
||||||
|
@ -73,11 +73,29 @@ void AirthingsWavePlus::dump_config() {
|
|||||||
LOG_SENSOR(" ", "Illuminance", this->illuminance_sensor_);
|
LOG_SENSOR(" ", "Illuminance", this->illuminance_sensor_);
|
||||||
}
|
}
|
||||||
|
|
||||||
AirthingsWavePlus::AirthingsWavePlus() {
|
void AirthingsWavePlus::setup() {
|
||||||
this->service_uuid_ = espbt::ESPBTUUID::from_raw(SERVICE_UUID);
|
const char *service_uuid;
|
||||||
this->sensors_data_characteristic_uuid_ = espbt::ESPBTUUID::from_raw(CHARACTERISTIC_UUID);
|
const char *characteristic_uuid;
|
||||||
|
const char *access_control_point_characteristic_uuid;
|
||||||
|
|
||||||
|
// Change UUIDs for Wave Radon Gen2
|
||||||
|
switch (this->wave_device_type_) {
|
||||||
|
case WaveDeviceType::WAVE_GEN2:
|
||||||
|
service_uuid = SERVICE_UUID_WAVE_RADON_GEN2;
|
||||||
|
characteristic_uuid = CHARACTERISTIC_UUID_WAVE_RADON_GEN2;
|
||||||
|
access_control_point_characteristic_uuid = ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID_WAVE_RADON_GEN2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Wave Plus
|
||||||
|
service_uuid = SERVICE_UUID;
|
||||||
|
characteristic_uuid = CHARACTERISTIC_UUID;
|
||||||
|
access_control_point_characteristic_uuid = ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->service_uuid_ = espbt::ESPBTUUID::from_raw(service_uuid);
|
||||||
|
this->sensors_data_characteristic_uuid_ = espbt::ESPBTUUID::from_raw(characteristic_uuid);
|
||||||
this->access_control_point_characteristic_uuid_ =
|
this->access_control_point_characteristic_uuid_ =
|
||||||
espbt::ESPBTUUID::from_raw(ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID);
|
espbt::ESPBTUUID::from_raw(access_control_point_characteristic_uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace airthings_wave_plus
|
} // namespace airthings_wave_plus
|
||||||
|
@ -9,13 +9,20 @@ namespace airthings_wave_plus {
|
|||||||
|
|
||||||
namespace espbt = esphome::esp32_ble_tracker;
|
namespace espbt = esphome::esp32_ble_tracker;
|
||||||
|
|
||||||
|
enum WaveDeviceType : uint8_t { WAVE_PLUS = 0, WAVE_GEN2 = 1 };
|
||||||
|
|
||||||
static const char *const SERVICE_UUID = "b42e1c08-ade7-11e4-89d3-123b93f75cba";
|
static const char *const SERVICE_UUID = "b42e1c08-ade7-11e4-89d3-123b93f75cba";
|
||||||
static const char *const CHARACTERISTIC_UUID = "b42e2a68-ade7-11e4-89d3-123b93f75cba";
|
static const char *const CHARACTERISTIC_UUID = "b42e2a68-ade7-11e4-89d3-123b93f75cba";
|
||||||
static const char *const ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID = "b42e2d06-ade7-11e4-89d3-123b93f75cba";
|
static const char *const ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID = "b42e2d06-ade7-11e4-89d3-123b93f75cba";
|
||||||
|
|
||||||
|
static const char *const SERVICE_UUID_WAVE_RADON_GEN2 = "b42e4a8e-ade7-11e4-89d3-123b93f75cba";
|
||||||
|
static const char *const CHARACTERISTIC_UUID_WAVE_RADON_GEN2 = "b42e4dcc-ade7-11e4-89d3-123b93f75cba";
|
||||||
|
static const char *const ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID_WAVE_RADON_GEN2 =
|
||||||
|
"b42e50d8-ade7-11e4-89d3-123b93f75cba";
|
||||||
|
|
||||||
class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase {
|
class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase {
|
||||||
public:
|
public:
|
||||||
AirthingsWavePlus();
|
void setup() override;
|
||||||
|
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
|
||||||
@ -23,12 +30,14 @@ class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase {
|
|||||||
void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; }
|
void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; }
|
||||||
void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; }
|
void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; }
|
||||||
void set_illuminance(sensor::Sensor *illuminance) { illuminance_sensor_ = illuminance; }
|
void set_illuminance(sensor::Sensor *illuminance) { illuminance_sensor_ = illuminance; }
|
||||||
|
void set_device_type(WaveDeviceType wave_device_type) { wave_device_type_ = wave_device_type; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool is_valid_radon_value_(uint16_t radon);
|
bool is_valid_radon_value_(uint16_t radon);
|
||||||
bool is_valid_co2_value_(uint16_t co2);
|
bool is_valid_co2_value_(uint16_t co2);
|
||||||
|
|
||||||
void read_sensors(uint8_t *raw_value, uint16_t value_len) override;
|
void read_sensors(uint8_t *raw_value, uint16_t value_len) override;
|
||||||
|
WaveDeviceType wave_device_type_{WaveDeviceType::WAVE_PLUS};
|
||||||
|
|
||||||
sensor::Sensor *radon_sensor_{nullptr};
|
sensor::Sensor *radon_sensor_{nullptr};
|
||||||
sensor::Sensor *radon_long_term_sensor_{nullptr};
|
sensor::Sensor *radon_long_term_sensor_{nullptr};
|
||||||
|
@ -7,6 +7,7 @@ from esphome.const import (
|
|||||||
CONF_ILLUMINANCE,
|
CONF_ILLUMINANCE,
|
||||||
CONF_RADON,
|
CONF_RADON,
|
||||||
CONF_RADON_LONG_TERM,
|
CONF_RADON_LONG_TERM,
|
||||||
|
CONF_TVOC,
|
||||||
DEVICE_CLASS_CARBON_DIOXIDE,
|
DEVICE_CLASS_CARBON_DIOXIDE,
|
||||||
DEVICE_CLASS_ILLUMINANCE,
|
DEVICE_CLASS_ILLUMINANCE,
|
||||||
ICON_RADIOACTIVE,
|
ICON_RADIOACTIVE,
|
||||||
@ -15,6 +16,7 @@ from esphome.const import (
|
|||||||
UNIT_LUX,
|
UNIT_LUX,
|
||||||
UNIT_PARTS_PER_MILLION,
|
UNIT_PARTS_PER_MILLION,
|
||||||
)
|
)
|
||||||
|
from esphome.types import ConfigType
|
||||||
|
|
||||||
DEPENDENCIES = airthings_wave_base.DEPENDENCIES
|
DEPENDENCIES = airthings_wave_base.DEPENDENCIES
|
||||||
|
|
||||||
@ -25,8 +27,27 @@ AirthingsWavePlus = airthings_wave_plus_ns.class_(
|
|||||||
"AirthingsWavePlus", airthings_wave_base.AirthingsWaveBase
|
"AirthingsWavePlus", airthings_wave_base.AirthingsWaveBase
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CONF_DEVICE_TYPE = "device_type"
|
||||||
|
WaveDeviceType = airthings_wave_plus_ns.enum("WaveDeviceType")
|
||||||
|
DEVICE_TYPES = {
|
||||||
|
"WAVE_PLUS": WaveDeviceType.WAVE_PLUS,
|
||||||
|
"WAVE_GEN2": WaveDeviceType.WAVE_GEN2,
|
||||||
|
}
|
||||||
|
|
||||||
CONFIG_SCHEMA = airthings_wave_base.BASE_SCHEMA.extend(
|
|
||||||
|
def validate_wave_gen2_config(config: ConfigType) -> ConfigType:
|
||||||
|
"""Validate that Wave Gen2 devices don't have CO2 or TVOC sensors."""
|
||||||
|
if config[CONF_DEVICE_TYPE] == "WAVE_GEN2":
|
||||||
|
if CONF_CO2 in config:
|
||||||
|
raise cv.Invalid("Wave Gen2 devices do not support CO2 sensor")
|
||||||
|
# Check for TVOC in the base schema config
|
||||||
|
if CONF_TVOC in config:
|
||||||
|
raise cv.Invalid("Wave Gen2 devices do not support TVOC sensor")
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
airthings_wave_base.BASE_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(AirthingsWavePlus),
|
cv.GenerateID(): cv.declare_id(AirthingsWavePlus),
|
||||||
cv.Optional(CONF_RADON): sensor.sensor_schema(
|
cv.Optional(CONF_RADON): sensor.sensor_schema(
|
||||||
@ -53,7 +74,12 @@ CONFIG_SCHEMA = airthings_wave_base.BASE_SCHEMA.extend(
|
|||||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_DEVICE_TYPE, default="WAVE_PLUS"): cv.enum(
|
||||||
|
DEVICE_TYPES, upper=True
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
),
|
||||||
|
validate_wave_gen2_config,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -73,3 +99,4 @@ async def to_code(config):
|
|||||||
if config_illuminance := config.get(CONF_ILLUMINANCE):
|
if config_illuminance := config.get(CONF_ILLUMINANCE):
|
||||||
sens = await sensor.new_sensor(config_illuminance)
|
sens = await sensor.new_sensor(config_illuminance)
|
||||||
cg.add(var.set_illuminance(sens))
|
cg.add(var.set_illuminance(sens))
|
||||||
|
cg.add(var.set_device_type(config[CONF_DEVICE_TYPE]))
|
||||||
|
@ -23,7 +23,7 @@ void APDS9960::setup() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id != 0xAB && id != 0x9C && id != 0xA8) { // APDS9960 all should have one of these IDs
|
if (id != 0xAB && id != 0x9C && id != 0xA8 && id != 0x9E) { // APDS9960 all should have one of these IDs
|
||||||
this->error_code_ = WRONG_ID;
|
this->error_code_ = WRONG_ID;
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
|
@ -24,8 +24,9 @@ from esphome.const import (
|
|||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_VARIABLES,
|
CONF_VARIABLES,
|
||||||
)
|
)
|
||||||
from esphome.core import coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
|
|
||||||
|
DOMAIN = "api"
|
||||||
DEPENDENCIES = ["network"]
|
DEPENDENCIES = ["network"]
|
||||||
AUTO_LOAD = ["socket"]
|
AUTO_LOAD = ["socket"]
|
||||||
CODEOWNERS = ["@OttoWinter"]
|
CODEOWNERS = ["@OttoWinter"]
|
||||||
@ -51,6 +52,7 @@ SERVICE_ARG_NATIVE_TYPES = {
|
|||||||
}
|
}
|
||||||
CONF_ENCRYPTION = "encryption"
|
CONF_ENCRYPTION = "encryption"
|
||||||
CONF_BATCH_DELAY = "batch_delay"
|
CONF_BATCH_DELAY = "batch_delay"
|
||||||
|
CONF_CUSTOM_SERVICES = "custom_services"
|
||||||
|
|
||||||
|
|
||||||
def validate_encryption_key(value):
|
def validate_encryption_key(value):
|
||||||
@ -115,6 +117,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.positive_time_period_milliseconds,
|
cv.positive_time_period_milliseconds,
|
||||||
cv.Range(max=cv.TimePeriod(milliseconds=65535)),
|
cv.Range(max=cv.TimePeriod(milliseconds=65535)),
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_CUSTOM_SERVICES, default=False): cv.boolean,
|
||||||
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
|
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
|
||||||
single=True
|
single=True
|
||||||
),
|
),
|
||||||
@ -139,8 +142,11 @@ async def to_code(config):
|
|||||||
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
||||||
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
|
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
|
||||||
|
|
||||||
|
# Set USE_API_SERVICES if any services are enabled
|
||||||
|
if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:
|
||||||
|
cg.add_define("USE_API_SERVICES")
|
||||||
|
|
||||||
if actions := config.get(CONF_ACTIONS, []):
|
if actions := config.get(CONF_ACTIONS, []):
|
||||||
cg.add_define("USE_API_YAML_SERVICES")
|
|
||||||
for conf in actions:
|
for conf in actions:
|
||||||
template_args = []
|
template_args = []
|
||||||
func_args = []
|
func_args = []
|
||||||
@ -317,7 +323,10 @@ async def api_connected_to_code(config, condition_id, template_arg, args):
|
|||||||
|
|
||||||
|
|
||||||
def FILTER_SOURCE_FILES() -> list[str]:
|
def FILTER_SOURCE_FILES() -> list[str]:
|
||||||
"""Filter out api_pb2_dump.cpp when proto message dumping is not enabled."""
|
"""Filter out api_pb2_dump.cpp when proto message dumping is not enabled
|
||||||
|
and user_services.cpp when no services are defined."""
|
||||||
|
files_to_filter = []
|
||||||
|
|
||||||
# api_pb2_dump.cpp is only needed when HAS_PROTO_MESSAGE_DUMP is defined
|
# api_pb2_dump.cpp is only needed when HAS_PROTO_MESSAGE_DUMP is defined
|
||||||
# This is a particularly large file that still needs to be opened and read
|
# This is a particularly large file that still needs to be opened and read
|
||||||
# all the way to the end even when ifdef'd out
|
# all the way to the end even when ifdef'd out
|
||||||
@ -325,6 +334,11 @@ def FILTER_SOURCE_FILES() -> list[str]:
|
|||||||
# HAS_PROTO_MESSAGE_DUMP is defined when ESPHOME_LOG_HAS_VERY_VERBOSE is set,
|
# HAS_PROTO_MESSAGE_DUMP is defined when ESPHOME_LOG_HAS_VERY_VERBOSE is set,
|
||||||
# which happens when the logger level is VERY_VERBOSE
|
# which happens when the logger level is VERY_VERBOSE
|
||||||
if get_logger_level() != "VERY_VERBOSE":
|
if get_logger_level() != "VERY_VERBOSE":
|
||||||
return ["api_pb2_dump.cpp"]
|
files_to_filter.append("api_pb2_dump.cpp")
|
||||||
|
|
||||||
return []
|
# user_services.cpp is only needed when services are defined
|
||||||
|
config = CORE.config.get(DOMAIN, {})
|
||||||
|
if config and not config.get(CONF_ACTIONS) and not config[CONF_CUSTOM_SERVICES]:
|
||||||
|
files_to_filter.append("user_services.cpp")
|
||||||
|
|
||||||
|
return files_to_filter
|
||||||
|
@ -374,6 +374,7 @@ message CoverCommandRequest {
|
|||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
option (ifdef) = "USE_COVER";
|
option (ifdef) = "USE_COVER";
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (base_class) = "CommandProtoMessage";
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
|
|
||||||
@ -387,6 +388,7 @@ message CoverCommandRequest {
|
|||||||
bool has_tilt = 6;
|
bool has_tilt = 6;
|
||||||
float tilt = 7;
|
float tilt = 7;
|
||||||
bool stop = 8;
|
bool stop = 8;
|
||||||
|
uint32 device_id = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== FAN ====================
|
// ==================== FAN ====================
|
||||||
@ -441,6 +443,7 @@ message FanCommandRequest {
|
|||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
option (ifdef) = "USE_FAN";
|
option (ifdef) = "USE_FAN";
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (base_class) = "CommandProtoMessage";
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
bool has_state = 2;
|
bool has_state = 2;
|
||||||
@ -455,6 +458,7 @@ message FanCommandRequest {
|
|||||||
int32 speed_level = 11;
|
int32 speed_level = 11;
|
||||||
bool has_preset_mode = 12;
|
bool has_preset_mode = 12;
|
||||||
string preset_mode = 13;
|
string preset_mode = 13;
|
||||||
|
uint32 device_id = 14;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== LIGHT ====================
|
// ==================== LIGHT ====================
|
||||||
@ -523,6 +527,7 @@ message LightCommandRequest {
|
|||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
option (ifdef) = "USE_LIGHT";
|
option (ifdef) = "USE_LIGHT";
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (base_class) = "CommandProtoMessage";
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
bool has_state = 2;
|
bool has_state = 2;
|
||||||
@ -551,6 +556,7 @@ message LightCommandRequest {
|
|||||||
uint32 flash_length = 17;
|
uint32 flash_length = 17;
|
||||||
bool has_effect = 18;
|
bool has_effect = 18;
|
||||||
string effect = 19;
|
string effect = 19;
|
||||||
|
uint32 device_id = 28;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== SENSOR ====================
|
// ==================== SENSOR ====================
|
||||||
@ -640,9 +646,11 @@ message SwitchCommandRequest {
|
|||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
option (ifdef) = "USE_SWITCH";
|
option (ifdef) = "USE_SWITCH";
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (base_class) = "CommandProtoMessage";
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
bool state = 2;
|
bool state = 2;
|
||||||
|
uint32 device_id = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== TEXT SENSOR ====================
|
// ==================== TEXT SENSOR ====================
|
||||||
@ -799,18 +807,21 @@ enum ServiceArgType {
|
|||||||
SERVICE_ARG_TYPE_STRING_ARRAY = 7;
|
SERVICE_ARG_TYPE_STRING_ARRAY = 7;
|
||||||
}
|
}
|
||||||
message ListEntitiesServicesArgument {
|
message ListEntitiesServicesArgument {
|
||||||
|
option (ifdef) = "USE_API_SERVICES";
|
||||||
string name = 1;
|
string name = 1;
|
||||||
ServiceArgType type = 2;
|
ServiceArgType type = 2;
|
||||||
}
|
}
|
||||||
message ListEntitiesServicesResponse {
|
message ListEntitiesServicesResponse {
|
||||||
option (id) = 41;
|
option (id) = 41;
|
||||||
option (source) = SOURCE_SERVER;
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_API_SERVICES";
|
||||||
|
|
||||||
string name = 1;
|
string name = 1;
|
||||||
fixed32 key = 2;
|
fixed32 key = 2;
|
||||||
repeated ListEntitiesServicesArgument args = 3;
|
repeated ListEntitiesServicesArgument args = 3;
|
||||||
}
|
}
|
||||||
message ExecuteServiceArgument {
|
message ExecuteServiceArgument {
|
||||||
|
option (ifdef) = "USE_API_SERVICES";
|
||||||
bool bool_ = 1;
|
bool bool_ = 1;
|
||||||
int32 legacy_int = 2;
|
int32 legacy_int = 2;
|
||||||
float float_ = 3;
|
float float_ = 3;
|
||||||
@ -826,6 +837,7 @@ message ExecuteServiceRequest {
|
|||||||
option (id) = 42;
|
option (id) = 42;
|
||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (ifdef) = "USE_API_SERVICES";
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
repeated ExecuteServiceArgument args = 2;
|
repeated ExecuteServiceArgument args = 2;
|
||||||
@ -850,12 +862,14 @@ message ListEntitiesCameraResponse {
|
|||||||
|
|
||||||
message CameraImageResponse {
|
message CameraImageResponse {
|
||||||
option (id) = 44;
|
option (id) = 44;
|
||||||
|
option (base_class) = "StateResponseProtoMessage";
|
||||||
option (source) = SOURCE_SERVER;
|
option (source) = SOURCE_SERVER;
|
||||||
option (ifdef) = "USE_CAMERA";
|
option (ifdef) = "USE_CAMERA";
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
bytes data = 2;
|
bytes data = 2;
|
||||||
bool done = 3;
|
bool done = 3;
|
||||||
|
uint32 device_id = 4;
|
||||||
}
|
}
|
||||||
message CameraImageRequest {
|
message CameraImageRequest {
|
||||||
option (id) = 45;
|
option (id) = 45;
|
||||||
@ -980,6 +994,7 @@ message ClimateCommandRequest {
|
|||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
option (ifdef) = "USE_CLIMATE";
|
option (ifdef) = "USE_CLIMATE";
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (base_class) = "CommandProtoMessage";
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
bool has_mode = 2;
|
bool has_mode = 2;
|
||||||
@ -1005,6 +1020,7 @@ message ClimateCommandRequest {
|
|||||||
string custom_preset = 21;
|
string custom_preset = 21;
|
||||||
bool has_target_humidity = 22;
|
bool has_target_humidity = 22;
|
||||||
float target_humidity = 23;
|
float target_humidity = 23;
|
||||||
|
uint32 device_id = 24;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== NUMBER ====================
|
// ==================== NUMBER ====================
|
||||||
@ -1054,9 +1070,11 @@ message NumberCommandRequest {
|
|||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
option (ifdef) = "USE_NUMBER";
|
option (ifdef) = "USE_NUMBER";
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (base_class) = "CommandProtoMessage";
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
float state = 2;
|
float state = 2;
|
||||||
|
uint32 device_id = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== SELECT ====================
|
// ==================== SELECT ====================
|
||||||
@ -1096,9 +1114,11 @@ message SelectCommandRequest {
|
|||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
option (ifdef) = "USE_SELECT";
|
option (ifdef) = "USE_SELECT";
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (base_class) = "CommandProtoMessage";
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
string state = 2;
|
string state = 2;
|
||||||
|
uint32 device_id = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== SIREN ====================
|
// ==================== SIREN ====================
|
||||||
@ -1137,6 +1157,7 @@ message SirenCommandRequest {
|
|||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
option (ifdef) = "USE_SIREN";
|
option (ifdef) = "USE_SIREN";
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (base_class) = "CommandProtoMessage";
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
bool has_state = 2;
|
bool has_state = 2;
|
||||||
@ -1147,6 +1168,7 @@ message SirenCommandRequest {
|
|||||||
uint32 duration = 7;
|
uint32 duration = 7;
|
||||||
bool has_volume = 8;
|
bool has_volume = 8;
|
||||||
float volume = 9;
|
float volume = 9;
|
||||||
|
uint32 device_id = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== LOCK ====================
|
// ==================== LOCK ====================
|
||||||
@ -1201,12 +1223,14 @@ message LockCommandRequest {
|
|||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
option (ifdef) = "USE_LOCK";
|
option (ifdef) = "USE_LOCK";
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (base_class) = "CommandProtoMessage";
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
LockCommand command = 2;
|
LockCommand command = 2;
|
||||||
|
|
||||||
// Not yet implemented:
|
// Not yet implemented:
|
||||||
bool has_code = 3;
|
bool has_code = 3;
|
||||||
string code = 4;
|
string code = 4;
|
||||||
|
uint32 device_id = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== BUTTON ====================
|
// ==================== BUTTON ====================
|
||||||
@ -1232,8 +1256,10 @@ message ButtonCommandRequest {
|
|||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
option (ifdef) = "USE_BUTTON";
|
option (ifdef) = "USE_BUTTON";
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (base_class) = "CommandProtoMessage";
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
|
uint32 device_id = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== MEDIA PLAYER ====================
|
// ==================== MEDIA PLAYER ====================
|
||||||
@ -1301,6 +1327,7 @@ message MediaPlayerCommandRequest {
|
|||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
option (ifdef) = "USE_MEDIA_PLAYER";
|
option (ifdef) = "USE_MEDIA_PLAYER";
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (base_class) = "CommandProtoMessage";
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
|
|
||||||
@ -1315,6 +1342,7 @@ message MediaPlayerCommandRequest {
|
|||||||
|
|
||||||
bool has_announcement = 8;
|
bool has_announcement = 8;
|
||||||
bool announcement = 9;
|
bool announcement = 9;
|
||||||
|
uint32 device_id = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== BLUETOOTH ====================
|
// ==================== BLUETOOTH ====================
|
||||||
@ -1843,9 +1871,11 @@ message AlarmControlPanelCommandRequest {
|
|||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
option (ifdef) = "USE_ALARM_CONTROL_PANEL";
|
option (ifdef) = "USE_ALARM_CONTROL_PANEL";
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (base_class) = "CommandProtoMessage";
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
AlarmControlPanelStateCommand command = 2;
|
AlarmControlPanelStateCommand command = 2;
|
||||||
string code = 3;
|
string code = 3;
|
||||||
|
uint32 device_id = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================== TEXT =====================
|
// ===================== TEXT =====================
|
||||||
@ -1892,9 +1922,11 @@ message TextCommandRequest {
|
|||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
option (ifdef) = "USE_TEXT";
|
option (ifdef) = "USE_TEXT";
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (base_class) = "CommandProtoMessage";
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
string state = 2;
|
string state = 2;
|
||||||
|
uint32 device_id = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1936,11 +1968,13 @@ message DateCommandRequest {
|
|||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
option (ifdef) = "USE_DATETIME_DATE";
|
option (ifdef) = "USE_DATETIME_DATE";
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (base_class) = "CommandProtoMessage";
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
uint32 year = 2;
|
uint32 year = 2;
|
||||||
uint32 month = 3;
|
uint32 month = 3;
|
||||||
uint32 day = 4;
|
uint32 day = 4;
|
||||||
|
uint32 device_id = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== DATETIME TIME ====================
|
// ==================== DATETIME TIME ====================
|
||||||
@ -1981,11 +2015,13 @@ message TimeCommandRequest {
|
|||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
option (ifdef) = "USE_DATETIME_TIME";
|
option (ifdef) = "USE_DATETIME_TIME";
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (base_class) = "CommandProtoMessage";
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
uint32 hour = 2;
|
uint32 hour = 2;
|
||||||
uint32 minute = 3;
|
uint32 minute = 3;
|
||||||
uint32 second = 4;
|
uint32 second = 4;
|
||||||
|
uint32 device_id = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== EVENT ====================
|
// ==================== EVENT ====================
|
||||||
@ -2065,11 +2101,13 @@ message ValveCommandRequest {
|
|||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
option (ifdef) = "USE_VALVE";
|
option (ifdef) = "USE_VALVE";
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (base_class) = "CommandProtoMessage";
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
bool has_position = 2;
|
bool has_position = 2;
|
||||||
float position = 3;
|
float position = 3;
|
||||||
bool stop = 4;
|
bool stop = 4;
|
||||||
|
uint32 device_id = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== DATETIME DATETIME ====================
|
// ==================== DATETIME DATETIME ====================
|
||||||
@ -2108,9 +2146,11 @@ message DateTimeCommandRequest {
|
|||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
option (ifdef) = "USE_DATETIME_DATETIME";
|
option (ifdef) = "USE_DATETIME_DATETIME";
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (base_class) = "CommandProtoMessage";
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
fixed32 epoch_seconds = 2;
|
fixed32 epoch_seconds = 2;
|
||||||
|
uint32 device_id = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== UPDATE ====================
|
// ==================== UPDATE ====================
|
||||||
@ -2160,7 +2200,9 @@ message UpdateCommandRequest {
|
|||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
option (ifdef) = "USE_UPDATE";
|
option (ifdef) = "USE_UPDATE";
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (base_class) = "CommandProtoMessage";
|
||||||
|
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
UpdateCommand command = 2;
|
UpdateCommand command = 2;
|
||||||
|
uint32 device_id = 3;
|
||||||
}
|
}
|
||||||
|
@ -193,14 +193,15 @@ void APIConnection::loop() {
|
|||||||
// If we can't send the ping request directly (tx_buffer full),
|
// If we can't send the ping request directly (tx_buffer full),
|
||||||
// schedule it at the front of the batch so it will be sent with priority
|
// schedule it at the front of the batch so it will be sent with priority
|
||||||
ESP_LOGW(TAG, "Buffer full, ping queued");
|
ESP_LOGW(TAG, "Buffer full, ping queued");
|
||||||
this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE);
|
this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE,
|
||||||
|
PingRequest::ESTIMATED_SIZE);
|
||||||
this->flags_.sent_ping = true; // Mark as sent to avoid scheduling multiple pings
|
this->flags_.sent_ping = true; // Mark as sent to avoid scheduling multiple pings
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_CAMERA
|
#ifdef USE_CAMERA
|
||||||
if (this->image_reader_ && this->image_reader_->available() && this->helper_->can_write_without_blocking()) {
|
if (this->image_reader_ && this->image_reader_->available() && this->helper_->can_write_without_blocking()) {
|
||||||
uint32_t to_send = std::min((size_t) MAX_PACKET_SIZE, this->image_reader_->available());
|
uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available());
|
||||||
bool done = this->image_reader_->available() == to_send;
|
bool done = this->image_reader_->available() == to_send;
|
||||||
uint32_t msg_size = 0;
|
uint32_t msg_size = 0;
|
||||||
ProtoSize::add_fixed_field<4>(msg_size, 1, true);
|
ProtoSize::add_fixed_field<4>(msg_size, 1, true);
|
||||||
@ -265,7 +266,7 @@ void APIConnection::on_disconnect_response(const DisconnectResponse &value) {
|
|||||||
|
|
||||||
// Encodes a message to the buffer and returns the total number of bytes used,
|
// Encodes a message to the buffer and returns the total number of bytes used,
|
||||||
// including header and footer overhead. Returns 0 if the message doesn't fit.
|
// including header and footer overhead. Returns 0 if the message doesn't fit.
|
||||||
uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint16_t message_type, APIConnection *conn,
|
uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn,
|
||||||
uint32_t remaining_size, bool is_single) {
|
uint32_t remaining_size, bool is_single) {
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
// If in log-only mode, just log and return
|
// If in log-only mode, just log and return
|
||||||
@ -316,7 +317,7 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint16_t mes
|
|||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor) {
|
bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor) {
|
||||||
return this->send_message_smart_(binary_sensor, &APIConnection::try_send_binary_sensor_state,
|
return this->send_message_smart_(binary_sensor, &APIConnection::try_send_binary_sensor_state,
|
||||||
BinarySensorStateResponse::MESSAGE_TYPE);
|
BinarySensorStateResponse::MESSAGE_TYPE, BinarySensorStateResponse::ESTIMATED_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
@ -343,7 +344,8 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne
|
|||||||
|
|
||||||
#ifdef USE_COVER
|
#ifdef USE_COVER
|
||||||
bool APIConnection::send_cover_state(cover::Cover *cover) {
|
bool APIConnection::send_cover_state(cover::Cover *cover) {
|
||||||
return this->send_message_smart_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE);
|
return this->send_message_smart_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE,
|
||||||
|
CoverStateResponse::ESTIMATED_SIZE);
|
||||||
}
|
}
|
||||||
uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
bool is_single) {
|
bool is_single) {
|
||||||
@ -400,7 +402,8 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) {
|
|||||||
|
|
||||||
#ifdef USE_FAN
|
#ifdef USE_FAN
|
||||||
bool APIConnection::send_fan_state(fan::Fan *fan) {
|
bool APIConnection::send_fan_state(fan::Fan *fan) {
|
||||||
return this->send_message_smart_(fan, &APIConnection::try_send_fan_state, FanStateResponse::MESSAGE_TYPE);
|
return this->send_message_smart_(fan, &APIConnection::try_send_fan_state, FanStateResponse::MESSAGE_TYPE,
|
||||||
|
FanStateResponse::ESTIMATED_SIZE);
|
||||||
}
|
}
|
||||||
uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
bool is_single) {
|
bool is_single) {
|
||||||
@ -455,7 +458,8 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
|
|||||||
|
|
||||||
#ifdef USE_LIGHT
|
#ifdef USE_LIGHT
|
||||||
bool APIConnection::send_light_state(light::LightState *light) {
|
bool APIConnection::send_light_state(light::LightState *light) {
|
||||||
return this->send_message_smart_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE);
|
return this->send_message_smart_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE,
|
||||||
|
LightStateResponse::ESTIMATED_SIZE);
|
||||||
}
|
}
|
||||||
uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
bool is_single) {
|
bool is_single) {
|
||||||
@ -543,7 +547,8 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
|
|||||||
|
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
bool APIConnection::send_sensor_state(sensor::Sensor *sensor) {
|
bool APIConnection::send_sensor_state(sensor::Sensor *sensor) {
|
||||||
return this->send_message_smart_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE);
|
return this->send_message_smart_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE,
|
||||||
|
SensorStateResponse::ESTIMATED_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
@ -575,7 +580,8 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection *
|
|||||||
|
|
||||||
#ifdef USE_SWITCH
|
#ifdef USE_SWITCH
|
||||||
bool APIConnection::send_switch_state(switch_::Switch *a_switch) {
|
bool APIConnection::send_switch_state(switch_::Switch *a_switch) {
|
||||||
return this->send_message_smart_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE);
|
return this->send_message_smart_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE,
|
||||||
|
SwitchStateResponse::ESTIMATED_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
@ -611,7 +617,7 @@ void APIConnection::switch_command(const SwitchCommandRequest &msg) {
|
|||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) {
|
bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) {
|
||||||
return this->send_message_smart_(text_sensor, &APIConnection::try_send_text_sensor_state,
|
return this->send_message_smart_(text_sensor, &APIConnection::try_send_text_sensor_state,
|
||||||
TextSensorStateResponse::MESSAGE_TYPE);
|
TextSensorStateResponse::MESSAGE_TYPE, TextSensorStateResponse::ESTIMATED_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
@ -638,7 +644,8 @@ uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnect
|
|||||||
|
|
||||||
#ifdef USE_CLIMATE
|
#ifdef USE_CLIMATE
|
||||||
bool APIConnection::send_climate_state(climate::Climate *climate) {
|
bool APIConnection::send_climate_state(climate::Climate *climate) {
|
||||||
return this->send_message_smart_(climate, &APIConnection::try_send_climate_state, ClimateStateResponse::MESSAGE_TYPE);
|
return this->send_message_smart_(climate, &APIConnection::try_send_climate_state, ClimateStateResponse::MESSAGE_TYPE,
|
||||||
|
ClimateStateResponse::ESTIMATED_SIZE);
|
||||||
}
|
}
|
||||||
uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
bool is_single) {
|
bool is_single) {
|
||||||
@ -734,7 +741,8 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
|||||||
|
|
||||||
#ifdef USE_NUMBER
|
#ifdef USE_NUMBER
|
||||||
bool APIConnection::send_number_state(number::Number *number) {
|
bool APIConnection::send_number_state(number::Number *number) {
|
||||||
return this->send_message_smart_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE);
|
return this->send_message_smart_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE,
|
||||||
|
NumberStateResponse::ESTIMATED_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
@ -770,7 +778,8 @@ void APIConnection::number_command(const NumberCommandRequest &msg) {
|
|||||||
|
|
||||||
#ifdef USE_DATETIME_DATE
|
#ifdef USE_DATETIME_DATE
|
||||||
bool APIConnection::send_date_state(datetime::DateEntity *date) {
|
bool APIConnection::send_date_state(datetime::DateEntity *date) {
|
||||||
return this->send_message_smart_(date, &APIConnection::try_send_date_state, DateStateResponse::MESSAGE_TYPE);
|
return this->send_message_smart_(date, &APIConnection::try_send_date_state, DateStateResponse::MESSAGE_TYPE,
|
||||||
|
DateStateResponse::ESTIMATED_SIZE);
|
||||||
}
|
}
|
||||||
uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
bool is_single) {
|
bool is_single) {
|
||||||
@ -800,7 +809,8 @@ void APIConnection::date_command(const DateCommandRequest &msg) {
|
|||||||
|
|
||||||
#ifdef USE_DATETIME_TIME
|
#ifdef USE_DATETIME_TIME
|
||||||
bool APIConnection::send_time_state(datetime::TimeEntity *time) {
|
bool APIConnection::send_time_state(datetime::TimeEntity *time) {
|
||||||
return this->send_message_smart_(time, &APIConnection::try_send_time_state, TimeStateResponse::MESSAGE_TYPE);
|
return this->send_message_smart_(time, &APIConnection::try_send_time_state, TimeStateResponse::MESSAGE_TYPE,
|
||||||
|
TimeStateResponse::ESTIMATED_SIZE);
|
||||||
}
|
}
|
||||||
uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
bool is_single) {
|
bool is_single) {
|
||||||
@ -831,7 +841,7 @@ void APIConnection::time_command(const TimeCommandRequest &msg) {
|
|||||||
#ifdef USE_DATETIME_DATETIME
|
#ifdef USE_DATETIME_DATETIME
|
||||||
bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) {
|
bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) {
|
||||||
return this->send_message_smart_(datetime, &APIConnection::try_send_datetime_state,
|
return this->send_message_smart_(datetime, &APIConnection::try_send_datetime_state,
|
||||||
DateTimeStateResponse::MESSAGE_TYPE);
|
DateTimeStateResponse::MESSAGE_TYPE, DateTimeStateResponse::ESTIMATED_SIZE);
|
||||||
}
|
}
|
||||||
uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
bool is_single) {
|
bool is_single) {
|
||||||
@ -862,7 +872,8 @@ void APIConnection::datetime_command(const DateTimeCommandRequest &msg) {
|
|||||||
|
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
bool APIConnection::send_text_state(text::Text *text) {
|
bool APIConnection::send_text_state(text::Text *text) {
|
||||||
return this->send_message_smart_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE);
|
return this->send_message_smart_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE,
|
||||||
|
TextStateResponse::ESTIMATED_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
@ -896,7 +907,8 @@ void APIConnection::text_command(const TextCommandRequest &msg) {
|
|||||||
|
|
||||||
#ifdef USE_SELECT
|
#ifdef USE_SELECT
|
||||||
bool APIConnection::send_select_state(select::Select *select) {
|
bool APIConnection::send_select_state(select::Select *select) {
|
||||||
return this->send_message_smart_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE);
|
return this->send_message_smart_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE,
|
||||||
|
SelectStateResponse::ESTIMATED_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
@ -944,7 +956,8 @@ void esphome::api::APIConnection::button_command(const ButtonCommandRequest &msg
|
|||||||
|
|
||||||
#ifdef USE_LOCK
|
#ifdef USE_LOCK
|
||||||
bool APIConnection::send_lock_state(lock::Lock *a_lock) {
|
bool APIConnection::send_lock_state(lock::Lock *a_lock) {
|
||||||
return this->send_message_smart_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE);
|
return this->send_message_smart_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE,
|
||||||
|
LockStateResponse::ESTIMATED_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
@ -986,7 +999,8 @@ void APIConnection::lock_command(const LockCommandRequest &msg) {
|
|||||||
|
|
||||||
#ifdef USE_VALVE
|
#ifdef USE_VALVE
|
||||||
bool APIConnection::send_valve_state(valve::Valve *valve) {
|
bool APIConnection::send_valve_state(valve::Valve *valve) {
|
||||||
return this->send_message_smart_(valve, &APIConnection::try_send_valve_state, ValveStateResponse::MESSAGE_TYPE);
|
return this->send_message_smart_(valve, &APIConnection::try_send_valve_state, ValveStateResponse::MESSAGE_TYPE,
|
||||||
|
ValveStateResponse::ESTIMATED_SIZE);
|
||||||
}
|
}
|
||||||
uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
bool is_single) {
|
bool is_single) {
|
||||||
@ -1023,7 +1037,7 @@ void APIConnection::valve_command(const ValveCommandRequest &msg) {
|
|||||||
#ifdef USE_MEDIA_PLAYER
|
#ifdef USE_MEDIA_PLAYER
|
||||||
bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) {
|
bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) {
|
||||||
return this->send_message_smart_(media_player, &APIConnection::try_send_media_player_state,
|
return this->send_message_smart_(media_player, &APIConnection::try_send_media_player_state,
|
||||||
MediaPlayerStateResponse::MESSAGE_TYPE);
|
MediaPlayerStateResponse::MESSAGE_TYPE, MediaPlayerStateResponse::ESTIMATED_SIZE);
|
||||||
}
|
}
|
||||||
uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
bool is_single) {
|
bool is_single) {
|
||||||
@ -1262,7 +1276,8 @@ void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetCon
|
|||||||
#ifdef USE_ALARM_CONTROL_PANEL
|
#ifdef USE_ALARM_CONTROL_PANEL
|
||||||
bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
|
bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
|
||||||
return this->send_message_smart_(a_alarm_control_panel, &APIConnection::try_send_alarm_control_panel_state,
|
return this->send_message_smart_(a_alarm_control_panel, &APIConnection::try_send_alarm_control_panel_state,
|
||||||
AlarmControlPanelStateResponse::MESSAGE_TYPE);
|
AlarmControlPanelStateResponse::MESSAGE_TYPE,
|
||||||
|
AlarmControlPanelStateResponse::ESTIMATED_SIZE);
|
||||||
}
|
}
|
||||||
uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn,
|
uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn,
|
||||||
uint32_t remaining_size, bool is_single) {
|
uint32_t remaining_size, bool is_single) {
|
||||||
@ -1316,7 +1331,8 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
|
|||||||
|
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
void APIConnection::send_event(event::Event *event, const std::string &event_type) {
|
void APIConnection::send_event(event::Event *event, const std::string &event_type) {
|
||||||
this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE);
|
this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE,
|
||||||
|
EventResponse::ESTIMATED_SIZE);
|
||||||
}
|
}
|
||||||
uint16_t APIConnection::try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn,
|
uint16_t APIConnection::try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn,
|
||||||
uint32_t remaining_size, bool is_single) {
|
uint32_t remaining_size, bool is_single) {
|
||||||
@ -1341,7 +1357,8 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c
|
|||||||
|
|
||||||
#ifdef USE_UPDATE
|
#ifdef USE_UPDATE
|
||||||
bool APIConnection::send_update_state(update::UpdateEntity *update) {
|
bool APIConnection::send_update_state(update::UpdateEntity *update) {
|
||||||
return this->send_message_smart_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE);
|
return this->send_message_smart_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE,
|
||||||
|
UpdateStateResponse::ESTIMATED_SIZE);
|
||||||
}
|
}
|
||||||
uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
bool is_single) {
|
bool is_single) {
|
||||||
@ -1534,6 +1551,7 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#ifdef USE_API_SERVICES
|
||||||
void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
||||||
bool found = false;
|
bool found = false;
|
||||||
for (auto *service : this->parent_->get_user_services()) {
|
for (auto *service : this->parent_->get_user_services()) {
|
||||||
@ -1545,6 +1563,7 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
|||||||
ESP_LOGV(TAG, "Could not find service");
|
ESP_LOGV(TAG, "Could not find service");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
NoiseEncryptionSetKeyResponse APIConnection::noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) {
|
NoiseEncryptionSetKeyResponse APIConnection::noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) {
|
||||||
psk_t psk{};
|
psk_t psk{};
|
||||||
@ -1588,7 +1607,7 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) {
|
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
|
||||||
if (!this->try_to_clear_buffer(message_type != SubscribeLogsResponse::MESSAGE_TYPE)) { // SubscribeLogsResponse
|
if (!this->try_to_clear_buffer(message_type != SubscribeLogsResponse::MESSAGE_TYPE)) { // SubscribeLogsResponse
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1622,7 +1641,8 @@ void APIConnection::on_fatal_error() {
|
|||||||
this->flags_.remove = true;
|
this->flags_.remove = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type) {
|
void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type,
|
||||||
|
uint8_t estimated_size) {
|
||||||
// Check if we already have a message of this type for this entity
|
// Check if we already have a message of this type for this entity
|
||||||
// This provides deduplication per entity/message_type combination
|
// This provides deduplication per entity/message_type combination
|
||||||
// O(n) but optimized for RAM and not performance.
|
// O(n) but optimized for RAM and not performance.
|
||||||
@ -1637,12 +1657,13 @@ void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator c
|
|||||||
}
|
}
|
||||||
|
|
||||||
// No existing item found, add new one
|
// No existing item found, add new one
|
||||||
items.emplace_back(entity, std::move(creator), message_type);
|
items.emplace_back(entity, std::move(creator), message_type, estimated_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type) {
|
void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type,
|
||||||
|
uint8_t estimated_size) {
|
||||||
// Insert at front for high priority messages (no deduplication check)
|
// Insert at front for high priority messages (no deduplication check)
|
||||||
items.insert(items.begin(), BatchItem(entity, std::move(creator), message_type));
|
items.insert(items.begin(), BatchItem(entity, std::move(creator), message_type, estimated_size));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool APIConnection::schedule_batch_() {
|
bool APIConnection::schedule_batch_() {
|
||||||
@ -1714,7 +1735,7 @@ void APIConnection::process_batch_() {
|
|||||||
uint32_t total_estimated_size = 0;
|
uint32_t total_estimated_size = 0;
|
||||||
for (size_t i = 0; i < this->deferred_batch_.size(); i++) {
|
for (size_t i = 0; i < this->deferred_batch_.size(); i++) {
|
||||||
const auto &item = this->deferred_batch_[i];
|
const auto &item = this->deferred_batch_[i];
|
||||||
total_estimated_size += get_estimated_message_size(item.message_type);
|
total_estimated_size += item.estimated_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate total overhead for all messages
|
// Calculate total overhead for all messages
|
||||||
@ -1752,9 +1773,9 @@ void APIConnection::process_batch_() {
|
|||||||
|
|
||||||
// Update tracking variables
|
// Update tracking variables
|
||||||
items_processed++;
|
items_processed++;
|
||||||
// After first message, set remaining size to MAX_PACKET_SIZE to avoid fragmentation
|
// After first message, set remaining size to MAX_BATCH_PACKET_SIZE to avoid fragmentation
|
||||||
if (items_processed == 1) {
|
if (items_processed == 1) {
|
||||||
remaining_size = MAX_PACKET_SIZE;
|
remaining_size = MAX_BATCH_PACKET_SIZE;
|
||||||
}
|
}
|
||||||
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
|
||||||
@ -1808,7 +1829,7 @@ void APIConnection::process_batch_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
bool is_single, uint16_t message_type) const {
|
bool is_single, uint8_t message_type) const {
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
// Special case: EventResponse uses string pointer
|
// Special case: EventResponse uses string pointer
|
||||||
if (message_type == EventResponse::MESSAGE_TYPE) {
|
if (message_type == EventResponse::MESSAGE_TYPE) {
|
||||||
@ -1839,149 +1860,6 @@ uint16_t APIConnection::try_send_ping_request(EntityBase *entity, APIConnection
|
|||||||
return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t APIConnection::get_estimated_message_size(uint16_t message_type) {
|
|
||||||
// Use generated ESTIMATED_SIZE constants from each message type
|
|
||||||
switch (message_type) {
|
|
||||||
#ifdef USE_BINARY_SENSOR
|
|
||||||
case BinarySensorStateResponse::MESSAGE_TYPE:
|
|
||||||
return BinarySensorStateResponse::ESTIMATED_SIZE;
|
|
||||||
case ListEntitiesBinarySensorResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesBinarySensorResponse::ESTIMATED_SIZE;
|
|
||||||
#endif
|
|
||||||
#ifdef USE_SENSOR
|
|
||||||
case SensorStateResponse::MESSAGE_TYPE:
|
|
||||||
return SensorStateResponse::ESTIMATED_SIZE;
|
|
||||||
case ListEntitiesSensorResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesSensorResponse::ESTIMATED_SIZE;
|
|
||||||
#endif
|
|
||||||
#ifdef USE_SWITCH
|
|
||||||
case SwitchStateResponse::MESSAGE_TYPE:
|
|
||||||
return SwitchStateResponse::ESTIMATED_SIZE;
|
|
||||||
case ListEntitiesSwitchResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesSwitchResponse::ESTIMATED_SIZE;
|
|
||||||
#endif
|
|
||||||
#ifdef USE_TEXT_SENSOR
|
|
||||||
case TextSensorStateResponse::MESSAGE_TYPE:
|
|
||||||
return TextSensorStateResponse::ESTIMATED_SIZE;
|
|
||||||
case ListEntitiesTextSensorResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesTextSensorResponse::ESTIMATED_SIZE;
|
|
||||||
#endif
|
|
||||||
#ifdef USE_NUMBER
|
|
||||||
case NumberStateResponse::MESSAGE_TYPE:
|
|
||||||
return NumberStateResponse::ESTIMATED_SIZE;
|
|
||||||
case ListEntitiesNumberResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesNumberResponse::ESTIMATED_SIZE;
|
|
||||||
#endif
|
|
||||||
#ifdef USE_TEXT
|
|
||||||
case TextStateResponse::MESSAGE_TYPE:
|
|
||||||
return TextStateResponse::ESTIMATED_SIZE;
|
|
||||||
case ListEntitiesTextResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesTextResponse::ESTIMATED_SIZE;
|
|
||||||
#endif
|
|
||||||
#ifdef USE_SELECT
|
|
||||||
case SelectStateResponse::MESSAGE_TYPE:
|
|
||||||
return SelectStateResponse::ESTIMATED_SIZE;
|
|
||||||
case ListEntitiesSelectResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesSelectResponse::ESTIMATED_SIZE;
|
|
||||||
#endif
|
|
||||||
#ifdef USE_LOCK
|
|
||||||
case LockStateResponse::MESSAGE_TYPE:
|
|
||||||
return LockStateResponse::ESTIMATED_SIZE;
|
|
||||||
case ListEntitiesLockResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesLockResponse::ESTIMATED_SIZE;
|
|
||||||
#endif
|
|
||||||
#ifdef USE_EVENT
|
|
||||||
case EventResponse::MESSAGE_TYPE:
|
|
||||||
return EventResponse::ESTIMATED_SIZE;
|
|
||||||
case ListEntitiesEventResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesEventResponse::ESTIMATED_SIZE;
|
|
||||||
#endif
|
|
||||||
#ifdef USE_COVER
|
|
||||||
case CoverStateResponse::MESSAGE_TYPE:
|
|
||||||
return CoverStateResponse::ESTIMATED_SIZE;
|
|
||||||
case ListEntitiesCoverResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesCoverResponse::ESTIMATED_SIZE;
|
|
||||||
#endif
|
|
||||||
#ifdef USE_FAN
|
|
||||||
case FanStateResponse::MESSAGE_TYPE:
|
|
||||||
return FanStateResponse::ESTIMATED_SIZE;
|
|
||||||
case ListEntitiesFanResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesFanResponse::ESTIMATED_SIZE;
|
|
||||||
#endif
|
|
||||||
#ifdef USE_LIGHT
|
|
||||||
case LightStateResponse::MESSAGE_TYPE:
|
|
||||||
return LightStateResponse::ESTIMATED_SIZE;
|
|
||||||
case ListEntitiesLightResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesLightResponse::ESTIMATED_SIZE;
|
|
||||||
#endif
|
|
||||||
#ifdef USE_CLIMATE
|
|
||||||
case ClimateStateResponse::MESSAGE_TYPE:
|
|
||||||
return ClimateStateResponse::ESTIMATED_SIZE;
|
|
||||||
case ListEntitiesClimateResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesClimateResponse::ESTIMATED_SIZE;
|
|
||||||
#endif
|
|
||||||
#ifdef USE_ESP32_CAMERA
|
|
||||||
case ListEntitiesCameraResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesCameraResponse::ESTIMATED_SIZE;
|
|
||||||
#endif
|
|
||||||
#ifdef USE_BUTTON
|
|
||||||
case ListEntitiesButtonResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesButtonResponse::ESTIMATED_SIZE;
|
|
||||||
#endif
|
|
||||||
#ifdef USE_MEDIA_PLAYER
|
|
||||||
case MediaPlayerStateResponse::MESSAGE_TYPE:
|
|
||||||
return MediaPlayerStateResponse::ESTIMATED_SIZE;
|
|
||||||
case ListEntitiesMediaPlayerResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesMediaPlayerResponse::ESTIMATED_SIZE;
|
|
||||||
#endif
|
|
||||||
#ifdef USE_ALARM_CONTROL_PANEL
|
|
||||||
case AlarmControlPanelStateResponse::MESSAGE_TYPE:
|
|
||||||
return AlarmControlPanelStateResponse::ESTIMATED_SIZE;
|
|
||||||
case ListEntitiesAlarmControlPanelResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesAlarmControlPanelResponse::ESTIMATED_SIZE;
|
|
||||||
#endif
|
|
||||||
#ifdef USE_DATETIME_DATE
|
|
||||||
case DateStateResponse::MESSAGE_TYPE:
|
|
||||||
return DateStateResponse::ESTIMATED_SIZE;
|
|
||||||
case ListEntitiesDateResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesDateResponse::ESTIMATED_SIZE;
|
|
||||||
#endif
|
|
||||||
#ifdef USE_DATETIME_TIME
|
|
||||||
case TimeStateResponse::MESSAGE_TYPE:
|
|
||||||
return TimeStateResponse::ESTIMATED_SIZE;
|
|
||||||
case ListEntitiesTimeResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesTimeResponse::ESTIMATED_SIZE;
|
|
||||||
#endif
|
|
||||||
#ifdef USE_DATETIME_DATETIME
|
|
||||||
case DateTimeStateResponse::MESSAGE_TYPE:
|
|
||||||
return DateTimeStateResponse::ESTIMATED_SIZE;
|
|
||||||
case ListEntitiesDateTimeResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesDateTimeResponse::ESTIMATED_SIZE;
|
|
||||||
#endif
|
|
||||||
#ifdef USE_VALVE
|
|
||||||
case ValveStateResponse::MESSAGE_TYPE:
|
|
||||||
return ValveStateResponse::ESTIMATED_SIZE;
|
|
||||||
case ListEntitiesValveResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesValveResponse::ESTIMATED_SIZE;
|
|
||||||
#endif
|
|
||||||
#ifdef USE_UPDATE
|
|
||||||
case UpdateStateResponse::MESSAGE_TYPE:
|
|
||||||
return UpdateStateResponse::ESTIMATED_SIZE;
|
|
||||||
case ListEntitiesUpdateResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesUpdateResponse::ESTIMATED_SIZE;
|
|
||||||
#endif
|
|
||||||
case ListEntitiesServicesResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesServicesResponse::ESTIMATED_SIZE;
|
|
||||||
case ListEntitiesDoneResponse::MESSAGE_TYPE:
|
|
||||||
return ListEntitiesDoneResponse::ESTIMATED_SIZE;
|
|
||||||
case DisconnectRequest::MESSAGE_TYPE:
|
|
||||||
return DisconnectRequest::ESTIMATED_SIZE;
|
|
||||||
default:
|
|
||||||
// Fallback for unknown message types
|
|
||||||
return 24;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
#endif
|
#endif
|
||||||
|
@ -33,7 +33,7 @@ class APIConnection : public APIServerConnection {
|
|||||||
|
|
||||||
bool send_list_info_done() {
|
bool send_list_info_done() {
|
||||||
return this->schedule_message_(nullptr, &APIConnection::try_send_list_info_done,
|
return this->schedule_message_(nullptr, &APIConnection::try_send_list_info_done,
|
||||||
ListEntitiesDoneResponse::MESSAGE_TYPE);
|
ListEntitiesDoneResponse::MESSAGE_TYPE, ListEntitiesDoneResponse::ESTIMATED_SIZE);
|
||||||
}
|
}
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor);
|
bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor);
|
||||||
@ -195,7 +195,9 @@ class APIConnection : public APIServerConnection {
|
|||||||
// TODO
|
// TODO
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
#ifdef USE_API_SERVICES
|
||||||
void execute_service(const ExecuteServiceRequest &msg) override;
|
void execute_service(const ExecuteServiceRequest &msg) override;
|
||||||
|
#endif
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) override;
|
NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) override;
|
||||||
#endif
|
#endif
|
||||||
@ -256,7 +258,7 @@ class APIConnection : public APIServerConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool try_to_clear_buffer(bool log_out_of_space);
|
bool try_to_clear_buffer(bool log_out_of_space);
|
||||||
bool send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) override;
|
bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override;
|
||||||
|
|
||||||
std::string get_client_combined_info() const {
|
std::string get_client_combined_info() const {
|
||||||
if (this->client_info_ == this->client_peername_) {
|
if (this->client_info_ == this->client_peername_) {
|
||||||
@ -298,7 +300,7 @@ class APIConnection : public APIServerConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Non-template helper to encode any ProtoMessage
|
// Non-template helper to encode any ProtoMessage
|
||||||
static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint16_t message_type, APIConnection *conn,
|
static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn,
|
||||||
uint32_t remaining_size, bool is_single);
|
uint32_t remaining_size, bool is_single);
|
||||||
|
|
||||||
#ifdef USE_VOICE_ASSISTANT
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
@ -443,9 +445,6 @@ class APIConnection : public APIServerConnection {
|
|||||||
static uint16_t try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
static uint16_t try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
bool is_single);
|
bool is_single);
|
||||||
|
|
||||||
// Helper function to get estimated message size for buffer pre-allocation
|
|
||||||
static uint16_t get_estimated_message_size(uint16_t message_type);
|
|
||||||
|
|
||||||
// Batch message method for ping requests
|
// Batch message method for ping requests
|
||||||
static uint16_t try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
static uint16_t try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
bool is_single);
|
bool is_single);
|
||||||
@ -505,10 +504,10 @@ class APIConnection : public APIServerConnection {
|
|||||||
|
|
||||||
// Call operator - uses message_type to determine union type
|
// Call operator - uses message_type to determine union type
|
||||||
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single,
|
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single,
|
||||||
uint16_t message_type) const;
|
uint8_t message_type) const;
|
||||||
|
|
||||||
// Manual cleanup method - must be called before destruction for string types
|
// Manual cleanup method - must be called before destruction for string types
|
||||||
void cleanup(uint16_t message_type) {
|
void cleanup(uint8_t message_type) {
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
if (message_type == EventResponse::MESSAGE_TYPE && data_.string_ptr != nullptr) {
|
if (message_type == EventResponse::MESSAGE_TYPE && data_.string_ptr != nullptr) {
|
||||||
delete data_.string_ptr;
|
delete data_.string_ptr;
|
||||||
@ -529,11 +528,12 @@ class APIConnection : public APIServerConnection {
|
|||||||
struct BatchItem {
|
struct BatchItem {
|
||||||
EntityBase *entity; // Entity pointer
|
EntityBase *entity; // Entity pointer
|
||||||
MessageCreator creator; // Function that creates the message when needed
|
MessageCreator creator; // Function that creates the message when needed
|
||||||
uint16_t message_type; // Message type for overhead calculation
|
uint8_t message_type; // Message type for overhead calculation (max 255)
|
||||||
|
uint8_t estimated_size; // Estimated message size (max 255 bytes)
|
||||||
|
|
||||||
// Constructor for creating BatchItem
|
// Constructor for creating BatchItem
|
||||||
BatchItem(EntityBase *entity, MessageCreator creator, uint16_t message_type)
|
BatchItem(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size)
|
||||||
: entity(entity), creator(std::move(creator)), message_type(message_type) {}
|
: entity(entity), creator(std::move(creator)), message_type(message_type), estimated_size(estimated_size) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<BatchItem> items;
|
std::vector<BatchItem> items;
|
||||||
@ -559,9 +559,9 @@ class APIConnection : public APIServerConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add item to the batch
|
// Add item to the batch
|
||||||
void add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type);
|
void add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
|
||||||
// Add item to the front of the batch (for high priority messages like ping)
|
// Add item to the front of the batch (for high priority messages like ping)
|
||||||
void add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type);
|
void add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
|
||||||
|
|
||||||
// Clear all items with proper cleanup
|
// Clear all items with proper cleanup
|
||||||
void clear() {
|
void clear() {
|
||||||
@ -630,7 +630,7 @@ class APIConnection : public APIServerConnection {
|
|||||||
// to send in one go. This is the maximum size of a single packet
|
// to send in one go. This is the maximum size of a single packet
|
||||||
// that can be sent over the network.
|
// that can be sent over the network.
|
||||||
// This is to avoid fragmentation of the packet.
|
// This is to avoid fragmentation of the packet.
|
||||||
static constexpr size_t MAX_PACKET_SIZE = 1390; // MTU
|
static constexpr size_t MAX_BATCH_PACKET_SIZE = 1390; // MTU
|
||||||
|
|
||||||
bool schedule_batch_();
|
bool schedule_batch_();
|
||||||
void process_batch_();
|
void process_batch_();
|
||||||
@ -641,9 +641,9 @@ class APIConnection : public APIServerConnection {
|
|||||||
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
// Helper to log a proto message from a MessageCreator object
|
// Helper to log a proto message from a MessageCreator object
|
||||||
void log_proto_message_(EntityBase *entity, const MessageCreator &creator, uint16_t message_type) {
|
void log_proto_message_(EntityBase *entity, const MessageCreator &creator, uint8_t message_type) {
|
||||||
this->flags_.log_only_mode = true;
|
this->flags_.log_only_mode = true;
|
||||||
creator(entity, this, MAX_PACKET_SIZE, true, message_type);
|
creator(entity, this, MAX_BATCH_PACKET_SIZE, true, message_type);
|
||||||
this->flags_.log_only_mode = false;
|
this->flags_.log_only_mode = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -654,7 +654,8 @@ class APIConnection : public APIServerConnection {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Helper method to send a message either immediately or via batching
|
// Helper method to send a message either immediately or via batching
|
||||||
bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint16_t message_type) {
|
bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint8_t message_type,
|
||||||
|
uint8_t estimated_size) {
|
||||||
// Try to send immediately if:
|
// Try to send immediately if:
|
||||||
// 1. We should try to send immediately (should_try_send_immediately = true)
|
// 1. We should try to send immediately (should_try_send_immediately = true)
|
||||||
// 2. Batch delay is 0 (user has opted in to immediate sending)
|
// 2. Batch delay is 0 (user has opted in to immediate sending)
|
||||||
@ -662,7 +663,7 @@ class APIConnection : public APIServerConnection {
|
|||||||
if (this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0 &&
|
if (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_PACKET_SIZE, true) &&
|
if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true) &&
|
||||||
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) {
|
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) {
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
// Log the message in verbose mode
|
// Log the message in verbose mode
|
||||||
@ -675,23 +676,25 @@ class APIConnection : public APIServerConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fall back to scheduled batching
|
// Fall back to scheduled batching
|
||||||
return this->schedule_message_(entity, creator, message_type);
|
return this->schedule_message_(entity, creator, message_type, estimated_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to schedule a deferred message with known message type
|
// Helper function to schedule a deferred message with known message type
|
||||||
bool schedule_message_(EntityBase *entity, MessageCreator creator, uint16_t message_type) {
|
bool schedule_message_(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) {
|
||||||
this->deferred_batch_.add_item(entity, std::move(creator), message_type);
|
this->deferred_batch_.add_item(entity, std::move(creator), message_type, estimated_size);
|
||||||
return this->schedule_batch_();
|
return this->schedule_batch_();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overload for function pointers (for info messages and current state reads)
|
// Overload for function pointers (for info messages and current state reads)
|
||||||
bool schedule_message_(EntityBase *entity, MessageCreatorPtr function_ptr, uint16_t message_type) {
|
bool schedule_message_(EntityBase *entity, MessageCreatorPtr function_ptr, uint8_t message_type,
|
||||||
return schedule_message_(entity, MessageCreator(function_ptr), message_type);
|
uint8_t estimated_size) {
|
||||||
|
return schedule_message_(entity, MessageCreator(function_ptr), message_type, estimated_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to schedule a high priority message at the front of the batch
|
// Helper function to schedule a high priority message at the front of the batch
|
||||||
bool schedule_message_front_(EntityBase *entity, MessageCreatorPtr function_ptr, uint16_t message_type) {
|
bool schedule_message_front_(EntityBase *entity, MessageCreatorPtr function_ptr, uint8_t message_type,
|
||||||
this->deferred_batch_.add_item_front(entity, MessageCreator(function_ptr), message_type);
|
uint8_t estimated_size) {
|
||||||
|
this->deferred_batch_.add_item_front(entity, MessageCreator(function_ptr), message_type, estimated_size);
|
||||||
return this->schedule_batch_();
|
return this->schedule_batch_();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -613,7 +613,7 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
|||||||
buffer->type = type;
|
buffer->type = type;
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
|
APIError APINoiseFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
|
||||||
// Resize to include MAC space (required for Noise encryption)
|
// Resize to include MAC space (required for Noise encryption)
|
||||||
buffer.get_buffer()->resize(buffer.get_buffer()->size() + frame_footer_size_);
|
buffer.get_buffer()->resize(buffer.get_buffer()->size() + frame_footer_size_);
|
||||||
PacketInfo packet{type, 0,
|
PacketInfo packet{type, 0,
|
||||||
@ -1002,7 +1002,7 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
|||||||
buffer->type = rx_header_parsed_type_;
|
buffer->type = rx_header_parsed_type_;
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
|
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
|
||||||
PacketInfo packet{type, 0, static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_)};
|
PacketInfo packet{type, 0, static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_)};
|
||||||
return write_protobuf_packets(buffer, std::span<const PacketInfo>(&packet, 1));
|
return write_protobuf_packets(buffer, std::span<const PacketInfo>(&packet, 1));
|
||||||
}
|
}
|
||||||
|
@ -30,13 +30,11 @@ struct ReadPacketBuffer {
|
|||||||
|
|
||||||
// Packed packet info structure to minimize memory usage
|
// Packed packet info structure to minimize memory usage
|
||||||
struct PacketInfo {
|
struct PacketInfo {
|
||||||
uint16_t message_type; // 2 bytes
|
uint16_t offset; // Offset in buffer where message starts
|
||||||
uint16_t offset; // 2 bytes (sufficient for packet size ~1460 bytes)
|
uint16_t payload_size; // Size of the message payload
|
||||||
uint16_t payload_size; // 2 bytes (up to 65535 bytes)
|
uint8_t message_type; // Message type (0-255)
|
||||||
uint16_t padding; // 2 byte (for alignment)
|
|
||||||
|
|
||||||
PacketInfo(uint16_t type, uint16_t off, uint16_t size)
|
PacketInfo(uint8_t type, uint16_t off, uint16_t size) : offset(off), payload_size(size), message_type(type) {}
|
||||||
: message_type(type), offset(off), payload_size(size), padding(0) {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class APIError : uint16_t {
|
enum class APIError : uint16_t {
|
||||||
@ -98,7 +96,7 @@ class APIFrameHelper {
|
|||||||
}
|
}
|
||||||
// Give this helper a name for logging
|
// Give this helper a name for logging
|
||||||
void set_log_info(std::string info) { info_ = std::move(info); }
|
void set_log_info(std::string info) { info_ = std::move(info); }
|
||||||
virtual APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) = 0;
|
virtual APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) = 0;
|
||||||
// Write multiple protobuf packets in a single operation
|
// Write multiple protobuf packets in a single operation
|
||||||
// packets contains (message_type, offset, length) for each message in the buffer
|
// packets contains (message_type, offset, length) for each message in the buffer
|
||||||
// The buffer contains all messages with appropriate padding before each
|
// The buffer contains all messages with appropriate padding before each
|
||||||
@ -197,7 +195,7 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
|||||||
APIError init() override;
|
APIError init() override;
|
||||||
APIError loop() override;
|
APIError loop() override;
|
||||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||||
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
|
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
||||||
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
||||||
// Get the frame header padding required by this protocol
|
// Get the frame header padding required by this protocol
|
||||||
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
||||||
@ -251,7 +249,7 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
|
|||||||
APIError init() override;
|
APIError init() override;
|
||||||
APIError loop() override;
|
APIError loop() override;
|
||||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||||
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
|
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
||||||
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
||||||
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
||||||
// Get the frame footer size required by this protocol
|
// Get the frame footer size required by this protocol
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -162,6 +162,7 @@ template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel val
|
|||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#ifdef USE_API_SERVICES
|
||||||
template<> const char *proto_enum_to_string<enums::ServiceArgType>(enums::ServiceArgType value) {
|
template<> const char *proto_enum_to_string<enums::ServiceArgType>(enums::ServiceArgType value) {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case enums::SERVICE_ARG_TYPE_BOOL:
|
case enums::SERVICE_ARG_TYPE_BOOL:
|
||||||
@ -184,6 +185,7 @@ template<> const char *proto_enum_to_string<enums::ServiceArgType>(enums::Servic
|
|||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
#ifdef USE_CLIMATE
|
#ifdef USE_CLIMATE
|
||||||
template<> const char *proto_enum_to_string<enums::ClimateMode>(enums::ClimateMode value) {
|
template<> const char *proto_enum_to_string<enums::ClimateMode>(enums::ClimateMode value) {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
@ -986,6 +988,11 @@ void CoverCommandRequest::dump_to(std::string &out) const {
|
|||||||
out.append(" stop: ");
|
out.append(" stop: ");
|
||||||
out.append(YESNO(this->stop));
|
out.append(YESNO(this->stop));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" device_id: ");
|
||||||
|
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -1146,6 +1153,11 @@ void FanCommandRequest::dump_to(std::string &out) const {
|
|||||||
out.append(" preset_mode: ");
|
out.append(" preset_mode: ");
|
||||||
out.append("'").append(this->preset_mode).append("'");
|
out.append("'").append(this->preset_mode).append("'");
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" device_id: ");
|
||||||
|
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -1419,6 +1431,11 @@ void LightCommandRequest::dump_to(std::string &out) const {
|
|||||||
out.append(" effect: ");
|
out.append(" effect: ");
|
||||||
out.append("'").append(this->effect).append("'");
|
out.append("'").append(this->effect).append("'");
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" device_id: ");
|
||||||
|
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -1586,6 +1603,11 @@ void SwitchCommandRequest::dump_to(std::string &out) const {
|
|||||||
out.append(" state: ");
|
out.append(" state: ");
|
||||||
out.append(YESNO(this->state));
|
out.append(YESNO(this->state));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" device_id: ");
|
||||||
|
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -1791,6 +1813,7 @@ void GetTimeResponse::dump_to(std::string &out) const {
|
|||||||
out.append("\n");
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
|
#ifdef USE_API_SERVICES
|
||||||
void ListEntitiesServicesArgument::dump_to(std::string &out) const {
|
void ListEntitiesServicesArgument::dump_to(std::string &out) const {
|
||||||
__attribute__((unused)) char buffer[64];
|
__attribute__((unused)) char buffer[64];
|
||||||
out.append("ListEntitiesServicesArgument {\n");
|
out.append("ListEntitiesServicesArgument {\n");
|
||||||
@ -1890,6 +1913,7 @@ void ExecuteServiceRequest::dump_to(std::string &out) const {
|
|||||||
}
|
}
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
#ifdef USE_CAMERA
|
#ifdef USE_CAMERA
|
||||||
void ListEntitiesCameraResponse::dump_to(std::string &out) const {
|
void ListEntitiesCameraResponse::dump_to(std::string &out) const {
|
||||||
__attribute__((unused)) char buffer[64];
|
__attribute__((unused)) char buffer[64];
|
||||||
@ -1944,6 +1968,11 @@ void CameraImageResponse::dump_to(std::string &out) const {
|
|||||||
out.append(" done: ");
|
out.append(" done: ");
|
||||||
out.append(YESNO(this->done));
|
out.append(YESNO(this->done));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" device_id: ");
|
||||||
|
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
void CameraImageRequest::dump_to(std::string &out) const {
|
void CameraImageRequest::dump_to(std::string &out) const {
|
||||||
@ -2263,6 +2292,11 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
|
|||||||
snprintf(buffer, sizeof(buffer), "%g", this->target_humidity);
|
snprintf(buffer, sizeof(buffer), "%g", this->target_humidity);
|
||||||
out.append(buffer);
|
out.append(buffer);
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" device_id: ");
|
||||||
|
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -2367,6 +2401,11 @@ void NumberCommandRequest::dump_to(std::string &out) const {
|
|||||||
snprintf(buffer, sizeof(buffer), "%g", this->state);
|
snprintf(buffer, sizeof(buffer), "%g", this->state);
|
||||||
out.append(buffer);
|
out.append(buffer);
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" device_id: ");
|
||||||
|
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -2448,6 +2487,11 @@ void SelectCommandRequest::dump_to(std::string &out) const {
|
|||||||
out.append(" state: ");
|
out.append(" state: ");
|
||||||
out.append("'").append(this->state).append("'");
|
out.append("'").append(this->state).append("'");
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" device_id: ");
|
||||||
|
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -2563,6 +2607,11 @@ void SirenCommandRequest::dump_to(std::string &out) const {
|
|||||||
snprintf(buffer, sizeof(buffer), "%g", this->volume);
|
snprintf(buffer, sizeof(buffer), "%g", this->volume);
|
||||||
out.append(buffer);
|
out.append(buffer);
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" device_id: ");
|
||||||
|
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -2658,6 +2707,11 @@ void LockCommandRequest::dump_to(std::string &out) const {
|
|||||||
out.append(" code: ");
|
out.append(" code: ");
|
||||||
out.append("'").append(this->code).append("'");
|
out.append("'").append(this->code).append("'");
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" device_id: ");
|
||||||
|
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -2711,6 +2765,11 @@ void ButtonCommandRequest::dump_to(std::string &out) const {
|
|||||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key);
|
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->key);
|
||||||
out.append(buffer);
|
out.append(buffer);
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" device_id: ");
|
||||||
|
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -2857,6 +2916,11 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const {
|
|||||||
out.append(" announcement: ");
|
out.append(" announcement: ");
|
||||||
out.append(YESNO(this->announcement));
|
out.append(YESNO(this->announcement));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" device_id: ");
|
||||||
|
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -3682,6 +3746,11 @@ void AlarmControlPanelCommandRequest::dump_to(std::string &out) const {
|
|||||||
out.append(" code: ");
|
out.append(" code: ");
|
||||||
out.append("'").append(this->code).append("'");
|
out.append("'").append(this->code).append("'");
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" device_id: ");
|
||||||
|
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -3775,6 +3844,11 @@ void TextCommandRequest::dump_to(std::string &out) const {
|
|||||||
out.append(" state: ");
|
out.append(" state: ");
|
||||||
out.append("'").append(this->state).append("'");
|
out.append("'").append(this->state).append("'");
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" device_id: ");
|
||||||
|
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -3872,6 +3946,11 @@ void DateCommandRequest::dump_to(std::string &out) const {
|
|||||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->day);
|
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->day);
|
||||||
out.append(buffer);
|
out.append(buffer);
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" device_id: ");
|
||||||
|
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -3969,6 +4048,11 @@ void TimeCommandRequest::dump_to(std::string &out) const {
|
|||||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->second);
|
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->second);
|
||||||
out.append(buffer);
|
out.append(buffer);
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" device_id: ");
|
||||||
|
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -4138,6 +4222,11 @@ void ValveCommandRequest::dump_to(std::string &out) const {
|
|||||||
out.append(" stop: ");
|
out.append(" stop: ");
|
||||||
out.append(YESNO(this->stop));
|
out.append(YESNO(this->stop));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" device_id: ");
|
||||||
|
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -4215,6 +4304,11 @@ void DateTimeCommandRequest::dump_to(std::string &out) const {
|
|||||||
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->epoch_seconds);
|
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->epoch_seconds);
|
||||||
out.append(buffer);
|
out.append(buffer);
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" device_id: ");
|
||||||
|
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -4323,6 +4417,11 @@ void UpdateCommandRequest::dump_to(std::string &out) const {
|
|||||||
out.append(" command: ");
|
out.append(" command: ");
|
||||||
out.append(proto_enum_to_string<enums::UpdateCommand>(this->command));
|
out.append(proto_enum_to_string<enums::UpdateCommand>(this->command));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" device_id: ");
|
||||||
|
snprintf(buffer, sizeof(buffer), "%" PRIu32, this->device_id);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -195,6 +195,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
this->on_home_assistant_state_response(msg);
|
this->on_home_assistant_state_response(msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
#ifdef USE_API_SERVICES
|
||||||
case 42: {
|
case 42: {
|
||||||
ExecuteServiceRequest msg;
|
ExecuteServiceRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
@ -204,6 +205,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
this->on_execute_service_request(msg);
|
this->on_execute_service_request(msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
#ifdef USE_CAMERA
|
#ifdef USE_CAMERA
|
||||||
case 45: {
|
case 45: {
|
||||||
CameraImageRequest msg;
|
CameraImageRequest msg;
|
||||||
@ -660,11 +662,13 @@ void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#ifdef USE_API_SERVICES
|
||||||
void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) {
|
void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
if (this->check_authenticated_()) {
|
||||||
this->execute_service(msg);
|
this->execute_service(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) {
|
void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
if (this->check_authenticated_()) {
|
||||||
|
@ -69,7 +69,9 @@ class APIServerConnectionBase : public ProtoService {
|
|||||||
virtual void on_get_time_request(const GetTimeRequest &value){};
|
virtual void on_get_time_request(const GetTimeRequest &value){};
|
||||||
virtual void on_get_time_response(const GetTimeResponse &value){};
|
virtual void on_get_time_response(const GetTimeResponse &value){};
|
||||||
|
|
||||||
|
#ifdef USE_API_SERVICES
|
||||||
virtual void on_execute_service_request(const ExecuteServiceRequest &value){};
|
virtual void on_execute_service_request(const ExecuteServiceRequest &value){};
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_CAMERA
|
#ifdef USE_CAMERA
|
||||||
virtual void on_camera_image_request(const CameraImageRequest &value){};
|
virtual void on_camera_image_request(const CameraImageRequest &value){};
|
||||||
@ -216,7 +218,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
|||||||
virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0;
|
virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0;
|
||||||
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
|
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
|
||||||
virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0;
|
virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0;
|
||||||
|
#ifdef USE_API_SERVICES
|
||||||
virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
|
virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
|
||||||
|
#endif
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
virtual NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) = 0;
|
virtual NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) = 0;
|
||||||
#endif
|
#endif
|
||||||
@ -333,7 +337,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
|||||||
void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override;
|
void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override;
|
||||||
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
|
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
|
||||||
void on_get_time_request(const GetTimeRequest &msg) override;
|
void on_get_time_request(const GetTimeRequest &msg) override;
|
||||||
|
#ifdef USE_API_SERVICES
|
||||||
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
|
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
|
||||||
|
#endif
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override;
|
void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override;
|
||||||
#endif
|
#endif
|
||||||
|
@ -141,9 +141,9 @@ class ProtoSize {
|
|||||||
/**
|
/**
|
||||||
* @brief Calculates and adds the size of an int32 field to the total message size
|
* @brief Calculates and adds the size of an int32 field to the total message size
|
||||||
*/
|
*/
|
||||||
static inline void add_int32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value, bool force = false) {
|
static inline void add_int32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
|
||||||
// Skip calculation if value is zero and not forced
|
// Skip calculation if value is zero
|
||||||
if (value == 0 && !force) {
|
if (value == 0) {
|
||||||
return; // No need to update total_size
|
return; // No need to update total_size
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,13 +157,26 @@ class ProtoSize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of an int32 field to the total message size (repeated field version)
|
||||||
|
*/
|
||||||
|
static inline void add_int32_field_repeated(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
|
||||||
|
// Always calculate size for repeated fields
|
||||||
|
if (value < 0) {
|
||||||
|
// Negative values are encoded as 10-byte varints in protobuf
|
||||||
|
total_size += field_id_size + 10;
|
||||||
|
} else {
|
||||||
|
// For non-negative values, use the standard varint size
|
||||||
|
total_size += field_id_size + varint(static_cast<uint32_t>(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Calculates and adds the size of a uint32 field to the total message size
|
* @brief Calculates and adds the size of a uint32 field to the total message size
|
||||||
*/
|
*/
|
||||||
static inline void add_uint32_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value,
|
static inline void add_uint32_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
|
||||||
bool force = false) {
|
// Skip calculation if value is zero
|
||||||
// Skip calculation if value is zero and not forced
|
if (value == 0) {
|
||||||
if (value == 0 && !force) {
|
|
||||||
return; // No need to update total_size
|
return; // No need to update total_size
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,12 +184,20 @@ class ProtoSize {
|
|||||||
total_size += field_id_size + varint(value);
|
total_size += field_id_size + varint(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a uint32 field to the total message size (repeated field version)
|
||||||
|
*/
|
||||||
|
static inline void add_uint32_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
|
||||||
|
// Always calculate size for repeated fields
|
||||||
|
total_size += field_id_size + varint(value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Calculates and adds the size of a boolean field to the total message size
|
* @brief Calculates and adds the size of a boolean field to the total message size
|
||||||
*/
|
*/
|
||||||
static inline void add_bool_field(uint32_t &total_size, uint32_t field_id_size, bool value, bool force = false) {
|
static inline void add_bool_field(uint32_t &total_size, uint32_t field_id_size, bool value) {
|
||||||
// Skip calculation if value is false and not forced
|
// Skip calculation if value is false
|
||||||
if (!value && !force) {
|
if (!value) {
|
||||||
return; // No need to update total_size
|
return; // No need to update total_size
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,6 +205,15 @@ class ProtoSize {
|
|||||||
total_size += field_id_size + 1;
|
total_size += field_id_size + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a boolean field to the total message size (repeated field version)
|
||||||
|
*/
|
||||||
|
static inline void add_bool_field_repeated(uint32_t &total_size, uint32_t field_id_size, bool value) {
|
||||||
|
// Always calculate size for repeated fields
|
||||||
|
// Boolean fields always use 1 byte
|
||||||
|
total_size += field_id_size + 1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Calculates and adds the size of a fixed field to the total message size
|
* @brief Calculates and adds the size of a fixed field to the total message size
|
||||||
*
|
*
|
||||||
@ -193,10 +223,9 @@ class ProtoSize {
|
|||||||
* @param is_nonzero Whether the value is non-zero
|
* @param is_nonzero Whether the value is non-zero
|
||||||
*/
|
*/
|
||||||
template<uint32_t NumBytes>
|
template<uint32_t NumBytes>
|
||||||
static inline void add_fixed_field(uint32_t &total_size, uint32_t field_id_size, bool is_nonzero,
|
static inline void add_fixed_field(uint32_t &total_size, uint32_t field_id_size, bool is_nonzero) {
|
||||||
bool force = false) {
|
// Skip calculation if value is zero
|
||||||
// Skip calculation if value is zero and not forced
|
if (!is_nonzero) {
|
||||||
if (!is_nonzero && !force) {
|
|
||||||
return; // No need to update total_size
|
return; // No need to update total_size
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,9 +238,9 @@ class ProtoSize {
|
|||||||
*
|
*
|
||||||
* Enum fields are encoded as uint32 varints.
|
* Enum fields are encoded as uint32 varints.
|
||||||
*/
|
*/
|
||||||
static inline void add_enum_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value, bool force = false) {
|
static inline void add_enum_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
|
||||||
// Skip calculation if value is zero and not forced
|
// Skip calculation if value is zero
|
||||||
if (value == 0 && !force) {
|
if (value == 0) {
|
||||||
return; // No need to update total_size
|
return; // No need to update total_size
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,14 +248,25 @@ class ProtoSize {
|
|||||||
total_size += field_id_size + varint(value);
|
total_size += field_id_size + varint(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of an enum field to the total message size (repeated field version)
|
||||||
|
*
|
||||||
|
* Enum fields are encoded as uint32 varints.
|
||||||
|
*/
|
||||||
|
static inline void add_enum_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
|
||||||
|
// Always calculate size for repeated fields
|
||||||
|
// Enums are encoded as uint32
|
||||||
|
total_size += field_id_size + varint(value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Calculates and adds the size of a sint32 field to the total message size
|
* @brief Calculates and adds the size of a sint32 field to the total message size
|
||||||
*
|
*
|
||||||
* Sint32 fields use ZigZag encoding, which is more efficient for negative values.
|
* Sint32 fields use ZigZag encoding, which is more efficient for negative values.
|
||||||
*/
|
*/
|
||||||
static inline void add_sint32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value, bool force = false) {
|
static inline void add_sint32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
|
||||||
// Skip calculation if value is zero and not forced
|
// Skip calculation if value is zero
|
||||||
if (value == 0 && !force) {
|
if (value == 0) {
|
||||||
return; // No need to update total_size
|
return; // No need to update total_size
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,12 +275,24 @@ class ProtoSize {
|
|||||||
total_size += field_id_size + varint(zigzag);
|
total_size += field_id_size + varint(zigzag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a sint32 field to the total message size (repeated field version)
|
||||||
|
*
|
||||||
|
* Sint32 fields use ZigZag encoding, which is more efficient for negative values.
|
||||||
|
*/
|
||||||
|
static inline void add_sint32_field_repeated(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
|
||||||
|
// Always calculate size for repeated fields
|
||||||
|
// ZigZag encoding for sint32: (n << 1) ^ (n >> 31)
|
||||||
|
uint32_t zigzag = (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
|
||||||
|
total_size += field_id_size + varint(zigzag);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Calculates and adds the size of an int64 field to the total message size
|
* @brief Calculates and adds the size of an int64 field to the total message size
|
||||||
*/
|
*/
|
||||||
static inline void add_int64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value, bool force = false) {
|
static inline void add_int64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value) {
|
||||||
// Skip calculation if value is zero and not forced
|
// Skip calculation if value is zero
|
||||||
if (value == 0 && !force) {
|
if (value == 0) {
|
||||||
return; // No need to update total_size
|
return; // No need to update total_size
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,13 +300,20 @@ class ProtoSize {
|
|||||||
total_size += field_id_size + varint(value);
|
total_size += field_id_size + varint(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of an int64 field to the total message size (repeated field version)
|
||||||
|
*/
|
||||||
|
static inline void add_int64_field_repeated(uint32_t &total_size, uint32_t field_id_size, int64_t value) {
|
||||||
|
// Always calculate size for repeated fields
|
||||||
|
total_size += field_id_size + varint(value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Calculates and adds the size of a uint64 field to the total message size
|
* @brief Calculates and adds the size of a uint64 field to the total message size
|
||||||
*/
|
*/
|
||||||
static inline void add_uint64_field(uint32_t &total_size, uint32_t field_id_size, uint64_t value,
|
static inline void add_uint64_field(uint32_t &total_size, uint32_t field_id_size, uint64_t value) {
|
||||||
bool force = false) {
|
// Skip calculation if value is zero
|
||||||
// Skip calculation if value is zero and not forced
|
if (value == 0) {
|
||||||
if (value == 0 && !force) {
|
|
||||||
return; // No need to update total_size
|
return; // No need to update total_size
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,14 +321,22 @@ class ProtoSize {
|
|||||||
total_size += field_id_size + varint(value);
|
total_size += field_id_size + varint(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a uint64 field to the total message size (repeated field version)
|
||||||
|
*/
|
||||||
|
static inline void add_uint64_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint64_t value) {
|
||||||
|
// Always calculate size for repeated fields
|
||||||
|
total_size += field_id_size + varint(value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Calculates and adds the size of a sint64 field to the total message size
|
* @brief Calculates and adds the size of a sint64 field to the total message size
|
||||||
*
|
*
|
||||||
* Sint64 fields use ZigZag encoding, which is more efficient for negative values.
|
* Sint64 fields use ZigZag encoding, which is more efficient for negative values.
|
||||||
*/
|
*/
|
||||||
static inline void add_sint64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value, bool force = false) {
|
static inline void add_sint64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value) {
|
||||||
// Skip calculation if value is zero and not forced
|
// Skip calculation if value is zero
|
||||||
if (value == 0 && !force) {
|
if (value == 0) {
|
||||||
return; // No need to update total_size
|
return; // No need to update total_size
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,13 +345,24 @@ class ProtoSize {
|
|||||||
total_size += field_id_size + varint(zigzag);
|
total_size += field_id_size + varint(zigzag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a sint64 field to the total message size (repeated field version)
|
||||||
|
*
|
||||||
|
* Sint64 fields use ZigZag encoding, which is more efficient for negative values.
|
||||||
|
*/
|
||||||
|
static inline void add_sint64_field_repeated(uint32_t &total_size, uint32_t field_id_size, int64_t value) {
|
||||||
|
// Always calculate size for repeated fields
|
||||||
|
// ZigZag encoding for sint64: (n << 1) ^ (n >> 63)
|
||||||
|
uint64_t zigzag = (static_cast<uint64_t>(value) << 1) ^ (static_cast<uint64_t>(value >> 63));
|
||||||
|
total_size += field_id_size + varint(zigzag);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Calculates and adds the size of a string/bytes field to the total message size
|
* @brief Calculates and adds the size of a string/bytes field to the total message size
|
||||||
*/
|
*/
|
||||||
static inline void add_string_field(uint32_t &total_size, uint32_t field_id_size, const std::string &str,
|
static inline void add_string_field(uint32_t &total_size, uint32_t field_id_size, const std::string &str) {
|
||||||
bool force = false) {
|
// Skip calculation if string is empty
|
||||||
// Skip calculation if string is empty and not forced
|
if (str.empty()) {
|
||||||
if (str.empty() && !force) {
|
|
||||||
return; // No need to update total_size
|
return; // No need to update total_size
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,18 +371,26 @@ class ProtoSize {
|
|||||||
total_size += field_id_size + varint(str_size) + str_size;
|
total_size += field_id_size + varint(str_size) + str_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a string/bytes field to the total message size (repeated field version)
|
||||||
|
*/
|
||||||
|
static inline void add_string_field_repeated(uint32_t &total_size, uint32_t field_id_size, const std::string &str) {
|
||||||
|
// Always calculate size for repeated fields
|
||||||
|
const uint32_t str_size = static_cast<uint32_t>(str.size());
|
||||||
|
total_size += field_id_size + varint(str_size) + str_size;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Calculates and adds the size of a nested message field to the total message size
|
* @brief Calculates and adds the size of a nested message field to the total message size
|
||||||
*
|
*
|
||||||
* This helper function directly updates the total_size reference if the nested size
|
* This helper function directly updates the total_size reference if the nested size
|
||||||
* is greater than zero or force is true.
|
* is greater than zero.
|
||||||
*
|
*
|
||||||
* @param nested_size The pre-calculated size of the nested message
|
* @param nested_size The pre-calculated size of the nested message
|
||||||
*/
|
*/
|
||||||
static inline void add_message_field(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size,
|
static inline void add_message_field(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size) {
|
||||||
bool force = false) {
|
// Skip calculation if nested message is empty
|
||||||
// Skip calculation if nested message is empty and not forced
|
if (nested_size == 0) {
|
||||||
if (nested_size == 0 && !force) {
|
|
||||||
return; // No need to update total_size
|
return; // No need to update total_size
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,6 +399,17 @@ class ProtoSize {
|
|||||||
total_size += field_id_size + varint(nested_size) + nested_size;
|
total_size += field_id_size + varint(nested_size) + nested_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a nested message field to the total message size (repeated field version)
|
||||||
|
*
|
||||||
|
* @param nested_size The pre-calculated size of the nested message
|
||||||
|
*/
|
||||||
|
static inline void add_message_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size) {
|
||||||
|
// Always calculate size for repeated fields
|
||||||
|
// Field ID + length varint + nested message content
|
||||||
|
total_size += field_id_size + varint(nested_size) + nested_size;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Calculates and adds the size of a nested message field to the total message size
|
* @brief Calculates and adds the size of a nested message field to the total message size
|
||||||
*
|
*
|
||||||
@ -322,13 +419,26 @@ class ProtoSize {
|
|||||||
*
|
*
|
||||||
* @param message The nested message object
|
* @param message The nested message object
|
||||||
*/
|
*/
|
||||||
static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const ProtoMessage &message,
|
static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const ProtoMessage &message) {
|
||||||
bool force = false) {
|
|
||||||
uint32_t nested_size = 0;
|
uint32_t nested_size = 0;
|
||||||
message.calculate_size(nested_size);
|
message.calculate_size(nested_size);
|
||||||
|
|
||||||
// Use the base implementation with the calculated nested_size
|
// Use the base implementation with the calculated nested_size
|
||||||
add_message_field(total_size, field_id_size, nested_size, force);
|
add_message_field(total_size, field_id_size, nested_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a nested message field to the total message size (repeated field version)
|
||||||
|
*
|
||||||
|
* @param message The nested message object
|
||||||
|
*/
|
||||||
|
static inline void add_message_object_repeated(uint32_t &total_size, uint32_t field_id_size,
|
||||||
|
const ProtoMessage &message) {
|
||||||
|
uint32_t nested_size = 0;
|
||||||
|
message.calculate_size(nested_size);
|
||||||
|
|
||||||
|
// Use the base implementation with the calculated nested_size
|
||||||
|
add_message_field_repeated(total_size, field_id_size, nested_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -348,9 +458,9 @@ class ProtoSize {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For repeated fields, always use force=true
|
// Use the repeated field version for all messages
|
||||||
for (const auto &message : messages) {
|
for (const auto &message : messages) {
|
||||||
add_message_object(total_size, field_id_size, message, true);
|
add_message_object_repeated(total_size, field_id_size, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -24,14 +24,6 @@ static const char *const TAG = "api";
|
|||||||
// APIServer
|
// APIServer
|
||||||
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
|
||||||
#ifndef USE_API_YAML_SERVICES
|
|
||||||
// Global empty vector to avoid guard variables (saves 8 bytes)
|
|
||||||
// This is initialized at program startup before any threads
|
|
||||||
static const std::vector<UserServiceDescriptor *> empty_user_services{};
|
|
||||||
|
|
||||||
const std::vector<UserServiceDescriptor *> &get_empty_user_services_instance() { return empty_user_services; }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
APIServer::APIServer() {
|
APIServer::APIServer() {
|
||||||
global_api_server = this;
|
global_api_server = this;
|
||||||
// Pre-allocate shared write buffer
|
// Pre-allocate shared write buffer
|
||||||
@ -475,7 +467,8 @@ void APIServer::on_shutdown() {
|
|||||||
if (!c->send_message(DisconnectRequest())) {
|
if (!c->send_message(DisconnectRequest())) {
|
||||||
// If we can't send the disconnect request directly (tx_buffer full),
|
// If we can't send the disconnect request directly (tx_buffer full),
|
||||||
// schedule it at the front of the batch so it will be sent with priority
|
// schedule it at the front of the batch so it will be sent with priority
|
||||||
c->schedule_message_front_(nullptr, &APIConnection::try_send_disconnect_request, DisconnectRequest::MESSAGE_TYPE);
|
c->schedule_message_front_(nullptr, &APIConnection::try_send_disconnect_request, DisconnectRequest::MESSAGE_TYPE,
|
||||||
|
DisconnectRequest::ESTIMATED_SIZE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,9 @@
|
|||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "list_entities.h"
|
#include "list_entities.h"
|
||||||
#include "subscribe_state.h"
|
#include "subscribe_state.h"
|
||||||
|
#ifdef USE_API_SERVICES
|
||||||
#include "user_services.h"
|
#include "user_services.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@ -25,11 +27,6 @@ struct SavedNoisePsk {
|
|||||||
} PACKED; // NOLINT
|
} PACKED; // NOLINT
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef USE_API_YAML_SERVICES
|
|
||||||
// Forward declaration of helper function
|
|
||||||
const std::vector<UserServiceDescriptor *> &get_empty_user_services_instance();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class APIServer : public Component, public Controller {
|
class APIServer : public Component, public Controller {
|
||||||
public:
|
public:
|
||||||
APIServer();
|
APIServer();
|
||||||
@ -112,18 +109,9 @@ class APIServer : public Component, public Controller {
|
|||||||
void on_media_player_update(media_player::MediaPlayer *obj) override;
|
void on_media_player_update(media_player::MediaPlayer *obj) override;
|
||||||
#endif
|
#endif
|
||||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
|
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
|
||||||
void register_user_service(UserServiceDescriptor *descriptor) {
|
#ifdef USE_API_SERVICES
|
||||||
#ifdef USE_API_YAML_SERVICES
|
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
||||||
// Vector is pre-allocated when services are defined in YAML
|
|
||||||
this->user_services_.push_back(descriptor);
|
|
||||||
#else
|
|
||||||
// Lazy allocate vector on first use for CustomAPIDevice
|
|
||||||
if (!this->user_services_) {
|
|
||||||
this->user_services_ = std::make_unique<std::vector<UserServiceDescriptor *>>();
|
|
||||||
}
|
|
||||||
this->user_services_->push_back(descriptor);
|
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
#ifdef USE_HOMEASSISTANT_TIME
|
#ifdef USE_HOMEASSISTANT_TIME
|
||||||
void request_time();
|
void request_time();
|
||||||
#endif
|
#endif
|
||||||
@ -152,17 +140,9 @@ class APIServer : public Component, public Controller {
|
|||||||
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||||
std::function<void(std::string)> f);
|
std::function<void(std::string)> f);
|
||||||
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
|
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
|
||||||
const std::vector<UserServiceDescriptor *> &get_user_services() const {
|
#ifdef USE_API_SERVICES
|
||||||
#ifdef USE_API_YAML_SERVICES
|
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }
|
||||||
return this->user_services_;
|
|
||||||
#else
|
|
||||||
if (this->user_services_) {
|
|
||||||
return *this->user_services_;
|
|
||||||
}
|
|
||||||
// Return reference to global empty instance (no guard needed)
|
|
||||||
return get_empty_user_services_instance();
|
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
|
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
|
||||||
Trigger<std::string, std::string> *get_client_connected_trigger() const { return this->client_connected_trigger_; }
|
Trigger<std::string, std::string> *get_client_connected_trigger() const { return this->client_connected_trigger_; }
|
||||||
@ -194,14 +174,8 @@ class APIServer : public Component, public Controller {
|
|||||||
#endif
|
#endif
|
||||||
std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections
|
std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections
|
||||||
std::vector<HomeAssistantStateSubscription> state_subs_;
|
std::vector<HomeAssistantStateSubscription> state_subs_;
|
||||||
#ifdef USE_API_YAML_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
// When services are defined in YAML, we know at compile time that services will be registered
|
|
||||||
std::vector<UserServiceDescriptor *> user_services_;
|
std::vector<UserServiceDescriptor *> user_services_;
|
||||||
#else
|
|
||||||
// Services can still be registered at runtime by CustomAPIDevice components even when not
|
|
||||||
// defined in YAML. Using unique_ptr allows lazy allocation, saving 12 bytes in the common
|
|
||||||
// case where no services (YAML or custom) are used.
|
|
||||||
std::unique_ptr<std::vector<UserServiceDescriptor *>> user_services_;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Group smaller types together
|
// Group smaller types together
|
||||||
|
@ -3,10 +3,13 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include "api_server.h"
|
#include "api_server.h"
|
||||||
#ifdef USE_API
|
#ifdef USE_API
|
||||||
|
#ifdef USE_API_SERVICES
|
||||||
#include "user_services.h"
|
#include "user_services.h"
|
||||||
|
#endif
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
|
#ifdef USE_API_SERVICES
|
||||||
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceBase<Ts...> {
|
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceBase<Ts...> {
|
||||||
public:
|
public:
|
||||||
CustomAPIDeviceService(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names, T *obj,
|
CustomAPIDeviceService(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names, T *obj,
|
||||||
@ -19,6 +22,7 @@ template<typename T, typename... Ts> class CustomAPIDeviceService : public UserS
|
|||||||
T *obj_;
|
T *obj_;
|
||||||
void (T::*callback_)(Ts...);
|
void (T::*callback_)(Ts...);
|
||||||
};
|
};
|
||||||
|
#endif // USE_API_SERVICES
|
||||||
|
|
||||||
class CustomAPIDevice {
|
class CustomAPIDevice {
|
||||||
public:
|
public:
|
||||||
@ -46,12 +50,14 @@ class CustomAPIDevice {
|
|||||||
* @param name The name of the service to register.
|
* @param name The name of the service to register.
|
||||||
* @param arg_names The name of the arguments for the service, must match the arguments of the function.
|
* @param arg_names The name of the arguments for the service, must match the arguments of the function.
|
||||||
*/
|
*/
|
||||||
|
#ifdef USE_API_SERVICES
|
||||||
template<typename T, typename... Ts>
|
template<typename T, typename... Ts>
|
||||||
void register_service(void (T::*callback)(Ts...), const std::string &name,
|
void register_service(void (T::*callback)(Ts...), const std::string &name,
|
||||||
const std::array<std::string, sizeof...(Ts)> &arg_names) {
|
const std::array<std::string, sizeof...(Ts)> &arg_names) {
|
||||||
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // 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);
|
||||||
}
|
}
|
||||||
|
#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.
|
||||||
*
|
*
|
||||||
@ -71,10 +77,12 @@ class CustomAPIDevice {
|
|||||||
* @param callback The member function to call when the service is triggered.
|
* @param callback The member function to call when the service is triggered.
|
||||||
* @param name The name of the arguments for the service, must match the arguments of the function.
|
* @param name The name of the arguments for the service, must match the arguments of the function.
|
||||||
*/
|
*/
|
||||||
|
#ifdef USE_API_SERVICES
|
||||||
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
|
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/** Subscribe to the state (or attribute state) of an entity from Home Assistant.
|
/** Subscribe to the state (or attribute state) of an entity from Home Assistant.
|
||||||
*
|
*
|
||||||
|
@ -83,10 +83,12 @@ bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(
|
|||||||
|
|
||||||
ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {}
|
ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {}
|
||||||
|
|
||||||
|
#ifdef USE_API_SERVICES
|
||||||
bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
|
bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
|
||||||
auto resp = service->encode_list_service_response();
|
auto resp = service->encode_list_service_response();
|
||||||
return this->client_->send_message(resp);
|
return this->client_->send_message(resp);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
@ -14,7 +14,7 @@ class APIConnection;
|
|||||||
#define LIST_ENTITIES_HANDLER(entity_type, EntityClass, ResponseType) \
|
#define LIST_ENTITIES_HANDLER(entity_type, EntityClass, ResponseType) \
|
||||||
bool ListEntitiesIterator::on_##entity_type(EntityClass *entity) { /* NOLINT(bugprone-macro-parentheses) */ \
|
bool ListEntitiesIterator::on_##entity_type(EntityClass *entity) { /* NOLINT(bugprone-macro-parentheses) */ \
|
||||||
return this->client_->schedule_message_(entity, &APIConnection::try_send_##entity_type##_info, \
|
return this->client_->schedule_message_(entity, &APIConnection::try_send_##entity_type##_info, \
|
||||||
ResponseType::MESSAGE_TYPE); \
|
ResponseType::MESSAGE_TYPE, ResponseType::ESTIMATED_SIZE); \
|
||||||
}
|
}
|
||||||
|
|
||||||
class ListEntitiesIterator : public ComponentIterator {
|
class ListEntitiesIterator : public ComponentIterator {
|
||||||
@ -44,7 +44,9 @@ class ListEntitiesIterator : public ComponentIterator {
|
|||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
bool on_text_sensor(text_sensor::TextSensor *entity) override;
|
bool on_text_sensor(text_sensor::TextSensor *entity) override;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_API_SERVICES
|
||||||
bool on_service(UserServiceDescriptor *service) override;
|
bool on_service(UserServiceDescriptor *service) override;
|
||||||
|
#endif
|
||||||
#ifdef USE_CAMERA
|
#ifdef USE_CAMERA
|
||||||
bool on_camera(camera::Camera *entity) override;
|
bool on_camera(camera::Camera *entity) override;
|
||||||
#endif
|
#endif
|
||||||
|
@ -59,7 +59,6 @@ class ProtoVarInt {
|
|||||||
uint32_t as_uint32() const { return this->value_; }
|
uint32_t as_uint32() const { return this->value_; }
|
||||||
uint64_t as_uint64() const { return this->value_; }
|
uint64_t as_uint64() const { return this->value_; }
|
||||||
bool as_bool() const { return this->value_; }
|
bool as_bool() const { return this->value_; }
|
||||||
template<typename T> T as_enum() const { return static_cast<T>(this->as_uint32()); }
|
|
||||||
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());
|
||||||
@ -133,15 +132,24 @@ class ProtoVarInt {
|
|||||||
uint64_t value_;
|
uint64_t value_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Forward declaration for decode_to_message and encode_to_writer
|
||||||
|
class ProtoMessage;
|
||||||
|
|
||||||
class ProtoLengthDelimited {
|
class ProtoLengthDelimited {
|
||||||
public:
|
public:
|
||||||
explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {}
|
explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {}
|
||||||
std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); }
|
std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); }
|
||||||
template<class C> C as_message() const {
|
|
||||||
auto msg = C();
|
/**
|
||||||
msg.decode(this->value_, this->length_);
|
* Decode the length-delimited data into an existing ProtoMessage instance.
|
||||||
return msg;
|
*
|
||||||
}
|
* This method allows decoding without templates, enabling use in contexts
|
||||||
|
* where the message type is not known at compile time. The ProtoMessage's
|
||||||
|
* decode() method will be called with the raw data and length.
|
||||||
|
*
|
||||||
|
* @param msg The ProtoMessage instance to decode into
|
||||||
|
*/
|
||||||
|
void decode_to_message(ProtoMessage &msg) const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
const uint8_t *const value_;
|
const uint8_t *const value_;
|
||||||
@ -263,9 +271,6 @@ class ProtoWriteBuffer {
|
|||||||
this->write((value >> 48) & 0xFF);
|
this->write((value >> 48) & 0xFF);
|
||||||
this->write((value >> 56) & 0xFF);
|
this->write((value >> 56) & 0xFF);
|
||||||
}
|
}
|
||||||
template<typename T> void encode_enum(uint32_t field_id, T value, bool force = false) {
|
|
||||||
this->encode_uint32(field_id, static_cast<uint32_t>(value), force);
|
|
||||||
}
|
|
||||||
void encode_float(uint32_t field_id, float value, bool force = false) {
|
void encode_float(uint32_t field_id, float value, bool force = false) {
|
||||||
if (value == 0.0f && !force)
|
if (value == 0.0f && !force)
|
||||||
return;
|
return;
|
||||||
@ -306,18 +311,7 @@ class ProtoWriteBuffer {
|
|||||||
}
|
}
|
||||||
this->encode_uint64(field_id, uvalue, force);
|
this->encode_uint64(field_id, uvalue, force);
|
||||||
}
|
}
|
||||||
template<class C> void encode_message(uint32_t field_id, const C &value, bool force = false) {
|
void encode_message(uint32_t field_id, const ProtoMessage &value, bool force = false);
|
||||||
this->encode_field_raw(field_id, 2); // type 2: Length-delimited message
|
|
||||||
size_t begin = this->buffer_->size();
|
|
||||||
|
|
||||||
value.encode(*this);
|
|
||||||
|
|
||||||
const uint32_t nested_length = this->buffer_->size() - begin;
|
|
||||||
// add size varint
|
|
||||||
std::vector<uint8_t> var;
|
|
||||||
ProtoVarInt(nested_length).encode(var);
|
|
||||||
this->buffer_->insert(this->buffer_->begin() + begin, var.begin(), var.end());
|
|
||||||
}
|
|
||||||
std::vector<uint8_t> *get_buffer() const { return buffer_; }
|
std::vector<uint8_t> *get_buffer() const { return buffer_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@ -345,6 +339,25 @@ class ProtoMessage {
|
|||||||
virtual bool decode_64bit(uint32_t field_id, Proto64Bit value) { return false; }
|
virtual bool decode_64bit(uint32_t field_id, Proto64Bit value) { return false; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Implementation of encode_message - must be after ProtoMessage is defined
|
||||||
|
inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessage &value, bool force) {
|
||||||
|
this->encode_field_raw(field_id, 2); // type 2: Length-delimited message
|
||||||
|
size_t begin = this->buffer_->size();
|
||||||
|
|
||||||
|
value.encode(*this);
|
||||||
|
|
||||||
|
const uint32_t nested_length = this->buffer_->size() - begin;
|
||||||
|
// add size varint
|
||||||
|
std::vector<uint8_t> var;
|
||||||
|
ProtoVarInt(nested_length).encode(var);
|
||||||
|
this->buffer_->insert(this->buffer_->begin() + begin, var.begin(), var.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementation of decode_to_message - must be after ProtoMessage is defined
|
||||||
|
inline void ProtoLengthDelimited::decode_to_message(ProtoMessage &msg) const {
|
||||||
|
msg.decode(this->value_, this->length_);
|
||||||
|
}
|
||||||
|
|
||||||
template<typename T> const char *proto_enum_to_string(T value);
|
template<typename T> const char *proto_enum_to_string(T value);
|
||||||
|
|
||||||
class ProtoService {
|
class ProtoService {
|
||||||
@ -363,11 +376,11 @@ class ProtoService {
|
|||||||
* @return A ProtoWriteBuffer object with the reserved size.
|
* @return A ProtoWriteBuffer object with the reserved size.
|
||||||
*/
|
*/
|
||||||
virtual ProtoWriteBuffer create_buffer(uint32_t reserve_size) = 0;
|
virtual ProtoWriteBuffer create_buffer(uint32_t reserve_size) = 0;
|
||||||
virtual bool send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) = 0;
|
virtual bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) = 0;
|
||||||
virtual void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0;
|
virtual void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0;
|
||||||
|
|
||||||
// Optimized method that pre-allocates buffer based on message size
|
// Optimized method that pre-allocates buffer based on message size
|
||||||
bool send_message_(const ProtoMessage &msg, uint16_t message_type) {
|
bool send_message_(const ProtoMessage &msg, uint8_t message_type) {
|
||||||
uint32_t msg_size = 0;
|
uint32_t msg_size = 0;
|
||||||
msg.calculate_size(msg_size);
|
msg.calculate_size(msg_size);
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "api_pb2.h"
|
#include "api_pb2.h"
|
||||||
|
|
||||||
|
#ifdef USE_API_SERVICES
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
@ -73,3 +74,4 @@ template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...
|
|||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif // USE_API_SERVICES
|
||||||
|
@ -53,6 +53,7 @@ void DebugComponent::on_shutdown() {
|
|||||||
auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name()));
|
auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name()));
|
||||||
if (component != nullptr) {
|
if (component != nullptr) {
|
||||||
strncpy(buffer, component->get_component_source(), REBOOT_MAX_LEN - 1);
|
strncpy(buffer, component->get_component_source(), REBOOT_MAX_LEN - 1);
|
||||||
|
buffer[REBOOT_MAX_LEN - 1] = '\0';
|
||||||
}
|
}
|
||||||
ESP_LOGD(TAG, "Storing reboot source: %s", buffer);
|
ESP_LOGD(TAG, "Storing reboot source: %s", buffer);
|
||||||
pref.save(&buffer);
|
pref.save(&buffer);
|
||||||
@ -68,6 +69,7 @@ std::string DebugComponent::get_reset_reason_() {
|
|||||||
auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name()));
|
auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name()));
|
||||||
char buffer[REBOOT_MAX_LEN]{};
|
char buffer[REBOOT_MAX_LEN]{};
|
||||||
if (pref.load(&buffer)) {
|
if (pref.load(&buffer)) {
|
||||||
|
buffer[REBOOT_MAX_LEN - 1] = '\0';
|
||||||
reset_reason = "Reboot request from " + std::string(buffer);
|
reset_reason = "Reboot request from " + std::string(buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from esphome import automation, pins
|
from esphome import automation, pins
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import time
|
from esphome.components import esp32, time
|
||||||
from esphome.components.esp32 import get_esp32_variant
|
from esphome.components.esp32 import get_esp32_variant
|
||||||
from esphome.components.esp32.const import (
|
from esphome.components.esp32.const import (
|
||||||
VARIANT_ESP32,
|
VARIANT_ESP32,
|
||||||
@ -116,12 +116,20 @@ def validate_pin_number(value):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def validate_config(config):
|
def _validate_ex1_wakeup_mode(value):
|
||||||
if get_esp32_variant() == VARIANT_ESP32C3 and CONF_ESP32_EXT1_WAKEUP in config:
|
if value == "ALL_LOW":
|
||||||
raise cv.Invalid("ESP32-C3 does not support wakeup from touch.")
|
esp32.only_on_variant(supported=[VARIANT_ESP32], msg_prefix="ALL_LOW")(value)
|
||||||
if get_esp32_variant() == VARIANT_ESP32C3 and CONF_TOUCH_WAKEUP in config:
|
if value == "ANY_LOW":
|
||||||
raise cv.Invalid("ESP32-C3 does not support wakeup from ext1")
|
esp32.only_on_variant(
|
||||||
return config
|
supported=[
|
||||||
|
VARIANT_ESP32S2,
|
||||||
|
VARIANT_ESP32S3,
|
||||||
|
VARIANT_ESP32C6,
|
||||||
|
VARIANT_ESP32H2,
|
||||||
|
],
|
||||||
|
msg_prefix="ANY_LOW",
|
||||||
|
)(value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
deep_sleep_ns = cg.esphome_ns.namespace("deep_sleep")
|
deep_sleep_ns = cg.esphome_ns.namespace("deep_sleep")
|
||||||
@ -148,6 +156,7 @@ WAKEUP_PIN_MODES = {
|
|||||||
esp_sleep_ext1_wakeup_mode_t = cg.global_ns.enum("esp_sleep_ext1_wakeup_mode_t")
|
esp_sleep_ext1_wakeup_mode_t = cg.global_ns.enum("esp_sleep_ext1_wakeup_mode_t")
|
||||||
Ext1Wakeup = deep_sleep_ns.struct("Ext1Wakeup")
|
Ext1Wakeup = deep_sleep_ns.struct("Ext1Wakeup")
|
||||||
EXT1_WAKEUP_MODES = {
|
EXT1_WAKEUP_MODES = {
|
||||||
|
"ANY_LOW": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ANY_LOW,
|
||||||
"ALL_LOW": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ALL_LOW,
|
"ALL_LOW": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ALL_LOW,
|
||||||
"ANY_HIGH": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ANY_HIGH,
|
"ANY_HIGH": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ANY_HIGH,
|
||||||
}
|
}
|
||||||
@ -187,16 +196,28 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
),
|
),
|
||||||
cv.Optional(CONF_ESP32_EXT1_WAKEUP): cv.All(
|
cv.Optional(CONF_ESP32_EXT1_WAKEUP): cv.All(
|
||||||
cv.only_on_esp32,
|
cv.only_on_esp32,
|
||||||
|
esp32.only_on_variant(
|
||||||
|
unsupported=[VARIANT_ESP32C3], msg_prefix="Wakeup from ext1"
|
||||||
|
),
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_PINS): cv.ensure_list(
|
cv.Required(CONF_PINS): cv.ensure_list(
|
||||||
pins.internal_gpio_input_pin_schema, validate_pin_number
|
pins.internal_gpio_input_pin_schema, validate_pin_number
|
||||||
),
|
),
|
||||||
cv.Required(CONF_MODE): cv.enum(EXT1_WAKEUP_MODES, upper=True),
|
cv.Required(CONF_MODE): cv.All(
|
||||||
|
cv.enum(EXT1_WAKEUP_MODES, upper=True),
|
||||||
|
_validate_ex1_wakeup_mode,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_TOUCH_WAKEUP): cv.All(cv.only_on_esp32, cv.boolean),
|
cv.Optional(CONF_TOUCH_WAKEUP): cv.All(
|
||||||
|
cv.only_on_esp32,
|
||||||
|
esp32.only_on_variant(
|
||||||
|
unsupported=[VARIANT_ESP32C3], msg_prefix="Wakeup from touch"
|
||||||
|
),
|
||||||
|
cv.boolean,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA),
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]),
|
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]),
|
||||||
|
@ -189,7 +189,7 @@ def get_download_types(storage_json):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def only_on_variant(*, supported=None, unsupported=None):
|
def only_on_variant(*, supported=None, unsupported=None, msg_prefix="This feature"):
|
||||||
"""Config validator for features only available on some ESP32 variants."""
|
"""Config validator for features only available on some ESP32 variants."""
|
||||||
if supported is not None and not isinstance(supported, list):
|
if supported is not None and not isinstance(supported, list):
|
||||||
supported = [supported]
|
supported = [supported]
|
||||||
@ -200,11 +200,11 @@ def only_on_variant(*, supported=None, unsupported=None):
|
|||||||
variant = get_esp32_variant()
|
variant = get_esp32_variant()
|
||||||
if supported is not None and variant not in supported:
|
if supported is not None and variant not in supported:
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
f"This feature is only available on {', '.join(supported)}"
|
f"{msg_prefix} is only available on {', '.join(supported)}"
|
||||||
)
|
)
|
||||||
if unsupported is not None and variant in unsupported:
|
if unsupported is not None and variant in unsupported:
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
f"This feature is not available on {', '.join(unsupported)}"
|
f"{msg_prefix} is not available on {', '.join(unsupported)}"
|
||||||
)
|
)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
@ -707,6 +707,7 @@ async def to_code(config):
|
|||||||
cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[config[CONF_VARIANT]])
|
cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[config[CONF_VARIANT]])
|
||||||
|
|
||||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||||
|
cg.add_platformio_option("lib_compat_mode", "strict")
|
||||||
|
|
||||||
framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
|
framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
|
||||||
|
|
||||||
|
@ -114,7 +114,6 @@ void ESP32InternalGPIOPin::setup() {
|
|||||||
if (flags_ & gpio::FLAG_OUTPUT) {
|
if (flags_ & gpio::FLAG_OUTPUT) {
|
||||||
gpio_set_drive_capability(pin_, drive_strength_);
|
gpio_set_drive_capability(pin_, drive_strength_);
|
||||||
}
|
}
|
||||||
ESP_LOGD(TAG, "rtc: %d", SOC_GPIO_SUPPORT_RTC_INDEPENDENT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESP32InternalGPIOPin::pin_mode(gpio::Flags flags) {
|
void ESP32InternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||||
|
@ -308,7 +308,7 @@ async def to_code(config):
|
|||||||
cg.add(var.set_frame_buffer_count(config[CONF_FRAME_BUFFER_COUNT]))
|
cg.add(var.set_frame_buffer_count(config[CONF_FRAME_BUFFER_COUNT]))
|
||||||
cg.add(var.set_frame_size(config[CONF_RESOLUTION]))
|
cg.add(var.set_frame_size(config[CONF_RESOLUTION]))
|
||||||
|
|
||||||
cg.add_define("USE_ESP32_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.0.15")
|
add_idf_component(name="espressif/esp32-camera", ref="2.0.15")
|
||||||
|
@ -109,6 +109,7 @@ void ESP32TouchComponent::loop() {
|
|||||||
|
|
||||||
// Only publish if state changed - this filters out repeated events
|
// Only publish if state changed - this filters out repeated events
|
||||||
if (new_state != child->last_state_) {
|
if (new_state != child->last_state_) {
|
||||||
|
child->initial_state_published_ = true;
|
||||||
child->last_state_ = new_state;
|
child->last_state_ = new_state;
|
||||||
child->publish_state(new_state);
|
child->publish_state(new_state);
|
||||||
// Original ESP32: ISR only fires when touched, release is detected by timeout
|
// Original ESP32: ISR only fires when touched, release is detected by timeout
|
||||||
@ -175,6 +176,9 @@ void ESP32TouchComponent::on_shutdown() {
|
|||||||
void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) {
|
void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) {
|
||||||
ESP32TouchComponent *component = static_cast<ESP32TouchComponent *>(arg);
|
ESP32TouchComponent *component = static_cast<ESP32TouchComponent *>(arg);
|
||||||
|
|
||||||
|
uint32_t mask = 0;
|
||||||
|
touch_ll_read_trigger_status_mask(&mask);
|
||||||
|
touch_ll_clear_trigger_status_mask();
|
||||||
touch_pad_clear_status();
|
touch_pad_clear_status();
|
||||||
|
|
||||||
// INTERRUPT BEHAVIOR: On ESP32 v1 hardware, the interrupt fires when ANY configured
|
// INTERRUPT BEHAVIOR: On ESP32 v1 hardware, the interrupt fires when ANY configured
|
||||||
@ -184,6 +188,11 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) {
|
|||||||
// as any pad remains touched. This allows us to detect both new touches and
|
// as any pad remains touched. This allows us to detect both new touches and
|
||||||
// continued touches, but releases must be detected by timeout in the main loop.
|
// continued touches, but releases must be detected by timeout in the main loop.
|
||||||
|
|
||||||
|
// IMPORTANT: ESP32 v1 touch detection logic - INVERTED compared to v2!
|
||||||
|
// ESP32 v1: Touch is detected when capacitance INCREASES, causing the measured value to DECREASE
|
||||||
|
// Therefore: touched = (value < threshold)
|
||||||
|
// This is opposite to ESP32-S2/S3 v2 where touched = (value > threshold)
|
||||||
|
|
||||||
// Process all configured pads to check their current state
|
// Process all configured pads to check their current state
|
||||||
// Note: ESP32 v1 doesn't tell us which specific pad triggered the interrupt,
|
// Note: ESP32 v1 doesn't tell us which specific pad triggered the interrupt,
|
||||||
// so we must scan all configured pads to find which ones were touched
|
// so we must scan all configured pads to find which ones were touched
|
||||||
@ -201,19 +210,12 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) {
|
|||||||
value = touch_ll_read_raw_data(pad);
|
value = touch_ll_read_raw_data(pad);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip pads with 0 value - they haven't been measured in this cycle
|
// Skip pads that aren’t in the trigger mask
|
||||||
// This is important: not all pads are measured every interrupt cycle,
|
bool is_touched = (mask >> pad) & 1;
|
||||||
// only those that the hardware has updated
|
if (!is_touched) {
|
||||||
if (value == 0) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// IMPORTANT: ESP32 v1 touch detection logic - INVERTED compared to v2!
|
|
||||||
// ESP32 v1: Touch is detected when capacitance INCREASES, causing the measured value to DECREASE
|
|
||||||
// Therefore: touched = (value < threshold)
|
|
||||||
// This is opposite to ESP32-S2/S3 v2 where touched = (value > threshold)
|
|
||||||
bool is_touched = value < child->get_threshold();
|
|
||||||
|
|
||||||
// Always send the current state - the main loop will filter for changes
|
// Always send the current state - the main loop will filter for changes
|
||||||
// We send both touched and untouched states because the ISR doesn't
|
// We send both touched and untouched states because the ISR doesn't
|
||||||
// track previous state (to keep ISR fast and simple)
|
// track previous state (to keep ISR fast and simple)
|
||||||
|
@ -180,6 +180,7 @@ async def to_code(config):
|
|||||||
cg.add(esp8266_ns.setup_preferences())
|
cg.add(esp8266_ns.setup_preferences())
|
||||||
|
|
||||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||||
|
cg.add_platformio_option("lib_compat_mode", "strict")
|
||||||
|
|
||||||
cg.add_platformio_option("board", config[CONF_BOARD])
|
cg.add_platformio_option("board", config[CONF_BOARD])
|
||||||
cg.add_build_flag("-DUSE_ESP8266")
|
cg.add_build_flag("-DUSE_ESP8266")
|
||||||
|
@ -342,5 +342,11 @@ async def to_code(config):
|
|||||||
|
|
||||||
cg.add_define("USE_ETHERNET")
|
cg.add_define("USE_ETHERNET")
|
||||||
|
|
||||||
|
# Disable WiFi when using Ethernet to save memory
|
||||||
|
if CORE.using_esp_idf:
|
||||||
|
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_ENABLED", False)
|
||||||
|
# Also disable WiFi/BT coexistence since WiFi is disabled
|
||||||
|
add_idf_sdkconfig_option("CONFIG_SW_COEXIST_ENABLE", False)
|
||||||
|
|
||||||
if CORE.using_arduino:
|
if CORE.using_arduino:
|
||||||
cg.add_library("WiFi", None)
|
cg.add_library("WiFi", None)
|
||||||
|
0
esphome/components/gl_r01_i2c/__init__.py
Normal file
0
esphome/components/gl_r01_i2c/__init__.py
Normal file
68
esphome/components/gl_r01_i2c/gl_r01_i2c.cpp
Normal file
68
esphome/components/gl_r01_i2c/gl_r01_i2c.cpp
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "gl_r01_i2c.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace gl_r01_i2c {
|
||||||
|
|
||||||
|
static const char *const TAG = "gl_r01_i2c";
|
||||||
|
|
||||||
|
// Register definitions from datasheet
|
||||||
|
static const uint8_t REG_VERSION = 0x00;
|
||||||
|
static const uint8_t REG_DISTANCE = 0x02;
|
||||||
|
static const uint8_t REG_TRIGGER = 0x10;
|
||||||
|
static const uint8_t CMD_TRIGGER = 0xB0;
|
||||||
|
static const uint8_t RESTART_CMD1 = 0x5A;
|
||||||
|
static const uint8_t RESTART_CMD2 = 0xA5;
|
||||||
|
static const uint8_t READ_DELAY = 40; // minimum milliseconds from datasheet to safely read measurement result
|
||||||
|
|
||||||
|
void GLR01I2CComponent::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up GL-R01 I2C...");
|
||||||
|
// Verify sensor presence
|
||||||
|
if (!this->read_byte_16(REG_VERSION, &this->version_)) {
|
||||||
|
ESP_LOGE(TAG, "Failed to communicate with GL-R01 I2C sensor!");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ESP_LOGD(TAG, "Found GL-R01 I2C with version 0x%04X", this->version_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLR01I2CComponent::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "GL-R01 I2C:");
|
||||||
|
ESP_LOGCONFIG(TAG, " Firmware Version: 0x%04X", this->version_);
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
LOG_SENSOR(" ", "Distance", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLR01I2CComponent::update() {
|
||||||
|
// Trigger a new measurement
|
||||||
|
if (!this->write_byte(REG_TRIGGER, CMD_TRIGGER)) {
|
||||||
|
ESP_LOGE(TAG, "Failed to trigger measurement!");
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule reading the result after the read delay
|
||||||
|
this->set_timeout(READ_DELAY, [this]() { this->read_distance_(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLR01I2CComponent::read_distance_() {
|
||||||
|
uint16_t distance = 0;
|
||||||
|
if (!this->read_byte_16(REG_DISTANCE, &distance)) {
|
||||||
|
ESP_LOGE(TAG, "Failed to read distance value!");
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (distance == 0xFFFF) {
|
||||||
|
ESP_LOGW(TAG, "Invalid measurement received!");
|
||||||
|
this->status_set_warning();
|
||||||
|
} else {
|
||||||
|
ESP_LOGV(TAG, "Distance: %umm", distance);
|
||||||
|
this->publish_state(distance);
|
||||||
|
this->status_clear_warning();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gl_r01_i2c
|
||||||
|
} // namespace esphome
|
22
esphome/components/gl_r01_i2c/gl_r01_i2c.h
Normal file
22
esphome/components/gl_r01_i2c/gl_r01_i2c.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace gl_r01_i2c {
|
||||||
|
|
||||||
|
class GLR01I2CComponent : public sensor::Sensor, public i2c::I2CDevice, public PollingComponent {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void read_distance_();
|
||||||
|
uint16_t version_{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gl_r01_i2c
|
||||||
|
} // namespace esphome
|
36
esphome/components/gl_r01_i2c/sensor.py
Normal file
36
esphome/components/gl_r01_i2c/sensor.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import i2c, sensor
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
|
DEVICE_CLASS_DISTANCE,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_MILLIMETER,
|
||||||
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@pkejval"]
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
|
gl_r01_i2c_ns = cg.esphome_ns.namespace("gl_r01_i2c")
|
||||||
|
GLR01I2CComponent = gl_r01_i2c_ns.class_(
|
||||||
|
"GLR01I2CComponent", i2c.I2CDevice, cg.PollingComponent
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
sensor.sensor_schema(
|
||||||
|
GLR01I2CComponent,
|
||||||
|
unit_of_measurement=UNIT_MILLIMETER,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_DISTANCE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(i2c.i2c_device_schema(0x74))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await sensor.register_sensor(var, config)
|
||||||
|
await i2c.register_i2c_device(var, config)
|
@ -45,3 +45,4 @@ async def to_code(config):
|
|||||||
cg.add_define("ESPHOME_BOARD", "host")
|
cg.add_define("ESPHOME_BOARD", "host")
|
||||||
cg.add_platformio_option("platform", "platformio/native")
|
cg.add_platformio_option("platform", "platformio/native")
|
||||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||||
|
cg.add_platformio_option("lib_compat_mode", "strict")
|
||||||
|
@ -178,13 +178,8 @@ static constexpr uint8_t NO_MAC[] = {0x08, 0x05, 0x04, 0x03, 0x02, 0x01};
|
|||||||
|
|
||||||
static inline int two_byte_to_int(char firstbyte, char secondbyte) { return (int16_t) (secondbyte << 8) + firstbyte; }
|
static inline int two_byte_to_int(char firstbyte, char secondbyte) { return (int16_t) (secondbyte << 8) + firstbyte; }
|
||||||
|
|
||||||
static bool validate_header_footer(const uint8_t *header_footer, const uint8_t *buffer) {
|
static inline bool validate_header_footer(const uint8_t *header_footer, const uint8_t *buffer) {
|
||||||
for (uint8_t i = 0; i < HEADER_FOOTER_SIZE; i++) {
|
return std::memcmp(header_footer, buffer, HEADER_FOOTER_SIZE) == 0;
|
||||||
if (header_footer[i] != buffer[i]) {
|
|
||||||
return false; // Mismatch in header/footer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true; // Valid header/footer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LD2410Component::dump_config() {
|
void LD2410Component::dump_config() {
|
||||||
@ -300,14 +295,12 @@ void LD2410Component::send_command_(uint8_t command, const uint8_t *command_valu
|
|||||||
if (command_value != nullptr) {
|
if (command_value != nullptr) {
|
||||||
len += command_value_len;
|
len += command_value_len;
|
||||||
}
|
}
|
||||||
uint8_t len_cmd[] = {lowbyte(len), highbyte(len), command, 0x00};
|
// 2 length bytes (low, high) + 2 command bytes (low, high)
|
||||||
|
uint8_t len_cmd[] = {len, 0x00, command, 0x00};
|
||||||
this->write_array(len_cmd, sizeof(len_cmd));
|
this->write_array(len_cmd, sizeof(len_cmd));
|
||||||
|
|
||||||
// command value bytes
|
// command value bytes
|
||||||
if (command_value != nullptr) {
|
if (command_value != nullptr) {
|
||||||
for (uint8_t i = 0; i < command_value_len; i++) {
|
this->write_array(command_value, command_value_len);
|
||||||
this->write_byte(command_value[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// frame footer bytes
|
// frame footer bytes
|
||||||
this->write_array(CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER));
|
this->write_array(CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER));
|
||||||
@ -401,7 +394,7 @@ void LD2410Component::handle_periodic_data_() {
|
|||||||
/*
|
/*
|
||||||
Moving distance range: 18th byte
|
Moving distance range: 18th byte
|
||||||
Still distance range: 19th byte
|
Still distance range: 19th byte
|
||||||
Moving enery: 20~28th bytes
|
Moving energy: 20~28th bytes
|
||||||
*/
|
*/
|
||||||
for (std::vector<sensor::Sensor *>::size_type i = 0; i != this->gate_move_sensors_.size(); i++) {
|
for (std::vector<sensor::Sensor *>::size_type i = 0; i != this->gate_move_sensors_.size(); i++) {
|
||||||
sensor::Sensor *s = this->gate_move_sensors_[i];
|
sensor::Sensor *s = this->gate_move_sensors_[i];
|
||||||
@ -480,7 +473,7 @@ bool LD2410Component::handle_ack_data_() {
|
|||||||
ESP_LOGE(TAG, "Invalid status");
|
ESP_LOGE(TAG, "Invalid status");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (ld2410::two_byte_to_int(this->buffer_data_[8], this->buffer_data_[9]) != 0x00) {
|
if (this->buffer_data_[8] || this->buffer_data_[9]) {
|
||||||
ESP_LOGW(TAG, "Invalid command: %02X, %02X", this->buffer_data_[8], this->buffer_data_[9]);
|
ESP_LOGW(TAG, "Invalid command: %02X, %02X", this->buffer_data_[8], this->buffer_data_[9]);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -534,8 +527,8 @@ bool LD2410Component::handle_ack_data_() {
|
|||||||
const auto *light_function_str = find_str(LIGHT_FUNCTIONS_BY_UINT, this->light_function_);
|
const auto *light_function_str = find_str(LIGHT_FUNCTIONS_BY_UINT, this->light_function_);
|
||||||
const auto *out_pin_level_str = find_str(OUT_PIN_LEVELS_BY_UINT, this->out_pin_level_);
|
const auto *out_pin_level_str = find_str(OUT_PIN_LEVELS_BY_UINT, this->out_pin_level_);
|
||||||
ESP_LOGV(TAG,
|
ESP_LOGV(TAG,
|
||||||
"Light function is: %s\n"
|
"Light function: %s\n"
|
||||||
"Light threshold is: %u\n"
|
"Light threshold: %u\n"
|
||||||
"Out pin level: %s",
|
"Out pin level: %s",
|
||||||
light_function_str, this->light_threshold_, out_pin_level_str);
|
light_function_str, this->light_threshold_, out_pin_level_str);
|
||||||
#ifdef USE_SELECT
|
#ifdef USE_SELECT
|
||||||
@ -600,7 +593,7 @@ bool LD2410Component::handle_ack_data_() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case CMD_QUERY: { // Query parameters response
|
case CMD_QUERY: { // Query parameters response
|
||||||
if (this->buffer_data_[10] != 0xAA)
|
if (this->buffer_data_[10] != HEADER)
|
||||||
return true; // value head=0xAA
|
return true; // value head=0xAA
|
||||||
#ifdef USE_NUMBER
|
#ifdef USE_NUMBER
|
||||||
/*
|
/*
|
||||||
@ -656,17 +649,11 @@ void LD2410Component::readline_(int readch) {
|
|||||||
if (this->buffer_pos_ < 4) {
|
if (this->buffer_pos_ < 4) {
|
||||||
return; // Not enough data to process yet
|
return; // Not enough data to process yet
|
||||||
}
|
}
|
||||||
if (this->buffer_data_[this->buffer_pos_ - 4] == DATA_FRAME_FOOTER[0] &&
|
if (ld2410::validate_header_footer(DATA_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
|
||||||
this->buffer_data_[this->buffer_pos_ - 3] == DATA_FRAME_FOOTER[1] &&
|
|
||||||
this->buffer_data_[this->buffer_pos_ - 2] == DATA_FRAME_FOOTER[2] &&
|
|
||||||
this->buffer_data_[this->buffer_pos_ - 1] == DATA_FRAME_FOOTER[3]) {
|
|
||||||
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
|
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
|
||||||
this->handle_periodic_data_();
|
this->handle_periodic_data_();
|
||||||
this->buffer_pos_ = 0; // Reset position index for next message
|
this->buffer_pos_ = 0; // Reset position index for next message
|
||||||
} else if (this->buffer_data_[this->buffer_pos_ - 4] == CMD_FRAME_FOOTER[0] &&
|
} else if (ld2410::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
|
||||||
this->buffer_data_[this->buffer_pos_ - 3] == CMD_FRAME_FOOTER[1] &&
|
|
||||||
this->buffer_data_[this->buffer_pos_ - 2] == CMD_FRAME_FOOTER[2] &&
|
|
||||||
this->buffer_data_[this->buffer_pos_ - 1] == CMD_FRAME_FOOTER[3]) {
|
|
||||||
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
|
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
|
||||||
if (this->handle_ack_data_()) {
|
if (this->handle_ack_data_()) {
|
||||||
this->buffer_pos_ = 0; // Reset position index for next message
|
this->buffer_pos_ = 0; // Reset position index for next message
|
||||||
@ -772,7 +759,6 @@ void LD2410Component::set_max_distances_timeout() {
|
|||||||
0x00};
|
0x00};
|
||||||
this->set_config_mode_(true);
|
this->set_config_mode_(true);
|
||||||
this->send_command_(CMD_MAXDIST_DURATION, value, sizeof(value));
|
this->send_command_(CMD_MAXDIST_DURATION, value, sizeof(value));
|
||||||
delay(50); // NOLINT
|
|
||||||
this->query_parameters_();
|
this->query_parameters_();
|
||||||
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
|
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
|
||||||
this->set_config_mode_(false);
|
this->set_config_mode_(false);
|
||||||
@ -802,7 +788,6 @@ void LD2410Component::set_gate_threshold(uint8_t gate) {
|
|||||||
0x01, 0x00, lowbyte(motion), highbyte(motion), 0x00, 0x00,
|
0x01, 0x00, lowbyte(motion), highbyte(motion), 0x00, 0x00,
|
||||||
0x02, 0x00, lowbyte(still), highbyte(still), 0x00, 0x00};
|
0x02, 0x00, lowbyte(still), highbyte(still), 0x00, 0x00};
|
||||||
this->send_command_(CMD_GATE_SENS, value, sizeof(value));
|
this->send_command_(CMD_GATE_SENS, value, sizeof(value));
|
||||||
delay(50); // NOLINT
|
|
||||||
this->query_parameters_();
|
this->query_parameters_();
|
||||||
this->set_config_mode_(false);
|
this->set_config_mode_(false);
|
||||||
}
|
}
|
||||||
@ -833,7 +818,6 @@ void LD2410Component::set_light_out_control() {
|
|||||||
this->set_config_mode_(true);
|
this->set_config_mode_(true);
|
||||||
uint8_t value[4] = {this->light_function_, this->light_threshold_, this->out_pin_level_, 0x00};
|
uint8_t value[4] = {this->light_function_, this->light_threshold_, this->out_pin_level_, 0x00};
|
||||||
this->send_command_(CMD_SET_LIGHT_CONTROL, value, sizeof(value));
|
this->send_command_(CMD_SET_LIGHT_CONTROL, value, sizeof(value));
|
||||||
delay(50); // NOLINT
|
|
||||||
this->query_light_control_();
|
this->query_light_control_();
|
||||||
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
|
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
|
||||||
this->set_config_mode_(false);
|
this->set_config_mode_(false);
|
||||||
|
@ -5,10 +5,10 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ld2420 {
|
namespace ld2420 {
|
||||||
|
|
||||||
static const char *const TAG = "LD2420.binary_sensor";
|
static const char *const TAG = "ld2420.binary_sensor";
|
||||||
|
|
||||||
void LD2420BinarySensor::dump_config() {
|
void LD2420BinarySensor::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "LD2420 BinarySensor:");
|
ESP_LOGCONFIG(TAG, "Binary Sensor:");
|
||||||
LOG_BINARY_SENSOR(" ", "Presence", this->presence_bsensor_);
|
LOG_BINARY_SENSOR(" ", "Presence", this->presence_bsensor_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
static const char *const TAG = "LD2420.button";
|
static const char *const TAG = "ld2420.button";
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ld2420 {
|
namespace ld2420 {
|
||||||
|
@ -137,7 +137,7 @@ static const std::string OP_SIMPLE_MODE_STRING = "Simple";
|
|||||||
// Memory-efficient lookup tables
|
// Memory-efficient lookup tables
|
||||||
struct StringToUint8 {
|
struct StringToUint8 {
|
||||||
const char *str;
|
const char *str;
|
||||||
uint8_t value;
|
const uint8_t value;
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr StringToUint8 OP_MODE_BY_STR[] = {
|
static constexpr StringToUint8 OP_MODE_BY_STR[] = {
|
||||||
@ -155,9 +155,10 @@ static constexpr const char *ERR_MESSAGE[] = {
|
|||||||
// Helper function for lookups
|
// Helper function for lookups
|
||||||
template<size_t N> uint8_t find_uint8(const StringToUint8 (&arr)[N], const std::string &str) {
|
template<size_t N> uint8_t find_uint8(const StringToUint8 (&arr)[N], const std::string &str) {
|
||||||
for (const auto &entry : arr) {
|
for (const auto &entry : arr) {
|
||||||
if (str == entry.str)
|
if (str == entry.str) {
|
||||||
return entry.value;
|
return entry.value;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return 0xFF; // Not found
|
return 0xFF; // Not found
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,15 +327,8 @@ void LD2420Component::revert_config_action() {
|
|||||||
|
|
||||||
void LD2420Component::loop() {
|
void LD2420Component::loop() {
|
||||||
// If there is a active send command do not process it here, the send command call will handle it.
|
// If there is a active send command do not process it here, the send command call will handle it.
|
||||||
if (!this->get_cmd_active_()) {
|
while (!this->cmd_active_ && this->available()) {
|
||||||
if (!this->available())
|
this->readline_(this->read(), this->buffer_data_, MAX_LINE_LENGTH);
|
||||||
return;
|
|
||||||
static uint8_t buffer[2048];
|
|
||||||
static uint8_t rx_data;
|
|
||||||
while (this->available()) {
|
|
||||||
rx_data = this->read();
|
|
||||||
this->readline_(rx_data, buffer, sizeof(buffer));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,8 +359,9 @@ void LD2420Component::auto_calibrate_sensitivity() {
|
|||||||
|
|
||||||
// Store average and peak values
|
// Store average and peak values
|
||||||
this->gate_avg[gate] = sum / CALIBRATE_SAMPLES;
|
this->gate_avg[gate] = sum / CALIBRATE_SAMPLES;
|
||||||
if (this->gate_peak[gate] < peak)
|
if (this->gate_peak[gate] < peak) {
|
||||||
this->gate_peak[gate] = peak;
|
this->gate_peak[gate] = peak;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t calculated_value =
|
uint32_t calculated_value =
|
||||||
(static_cast<uint32_t>(this->gate_peak[gate]) + (move_factor * static_cast<uint32_t>(this->gate_peak[gate])));
|
(static_cast<uint32_t>(this->gate_peak[gate]) + (move_factor * static_cast<uint32_t>(this->gate_peak[gate])));
|
||||||
@ -403,8 +398,9 @@ void LD2420Component::set_operating_mode(const std::string &state) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Set the current data back so we don't have new data that can be applied in error.
|
// Set the current data back so we don't have new data that can be applied in error.
|
||||||
if (this->get_calibration_())
|
if (this->get_calibration_()) {
|
||||||
memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
|
memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
|
||||||
|
}
|
||||||
this->set_calibration_(false);
|
this->set_calibration_(false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -414,30 +410,32 @@ void LD2420Component::set_operating_mode(const std::string &state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LD2420Component::readline_(int rx_data, uint8_t *buffer, int len) {
|
void LD2420Component::readline_(int rx_data, uint8_t *buffer, int len) {
|
||||||
static int pos = 0;
|
if (rx_data < 0) {
|
||||||
|
return; // No data available
|
||||||
if (rx_data >= 0) {
|
}
|
||||||
if (pos < len - 1) {
|
if (this->buffer_pos_ < len - 1) {
|
||||||
buffer[pos++] = rx_data;
|
buffer[this->buffer_pos_++] = rx_data;
|
||||||
buffer[pos] = 0;
|
buffer[this->buffer_pos_] = 0;
|
||||||
} else {
|
} else {
|
||||||
pos = 0;
|
// We should never get here, but just in case...
|
||||||
|
ESP_LOGW(TAG, "Max command length exceeded; ignoring");
|
||||||
|
this->buffer_pos_ = 0;
|
||||||
}
|
}
|
||||||
if (pos >= 4) {
|
if (this->buffer_pos_ < 4) {
|
||||||
if (memcmp(&buffer[pos - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) {
|
return; // Not enough data to process yet
|
||||||
this->set_cmd_active_(false); // Set command state to inactive after responce.
|
}
|
||||||
this->handle_ack_data_(buffer, pos);
|
if (memcmp(&buffer[this->buffer_pos_ - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) {
|
||||||
pos = 0;
|
this->cmd_active_ = false; // Set command state to inactive after response
|
||||||
} else if ((buffer[pos - 2] == 0x0D && buffer[pos - 1] == 0x0A) &&
|
this->handle_ack_data_(buffer, this->buffer_pos_);
|
||||||
|
this->buffer_pos_ = 0;
|
||||||
|
} else if ((buffer[this->buffer_pos_ - 2] == 0x0D && buffer[this->buffer_pos_ - 1] == 0x0A) &&
|
||||||
(this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) {
|
(this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) {
|
||||||
this->handle_simple_mode_(buffer, pos);
|
this->handle_simple_mode_(buffer, this->buffer_pos_);
|
||||||
pos = 0;
|
this->buffer_pos_ = 0;
|
||||||
} else if ((memcmp(&buffer[pos - 4], &ENERGY_FRAME_FOOTER, sizeof(ENERGY_FRAME_FOOTER)) == 0) &&
|
} else if ((memcmp(&buffer[this->buffer_pos_ - 4], &ENERGY_FRAME_FOOTER, sizeof(ENERGY_FRAME_FOOTER)) == 0) &&
|
||||||
(this->get_mode_() == CMD_SYSTEM_MODE_ENERGY)) {
|
(this->get_mode_() == CMD_SYSTEM_MODE_ENERGY)) {
|
||||||
this->handle_energy_mode_(buffer, pos);
|
this->handle_energy_mode_(buffer, this->buffer_pos_);
|
||||||
pos = 0;
|
this->buffer_pos_ = 0;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -462,8 +460,9 @@ void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) {
|
|||||||
|
|
||||||
// Resonable refresh rate for home assistant database size health
|
// Resonable refresh rate for home assistant database size health
|
||||||
const int32_t current_millis = App.get_loop_component_start_time();
|
const int32_t current_millis = App.get_loop_component_start_time();
|
||||||
if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS)
|
if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
this->last_periodic_millis = current_millis;
|
this->last_periodic_millis = current_millis;
|
||||||
for (auto &listener : this->listeners_) {
|
for (auto &listener : this->listeners_) {
|
||||||
listener->on_distance(this->get_distance_());
|
listener->on_distance(this->get_distance_());
|
||||||
@ -506,14 +505,16 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
outbuf[index] = '\0';
|
outbuf[index] = '\0';
|
||||||
if (index > 1)
|
if (index > 1) {
|
||||||
this->set_distance_(strtol(outbuf, &endptr, 10));
|
this->set_distance_(strtol(outbuf, &endptr, 10));
|
||||||
|
}
|
||||||
|
|
||||||
if (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE) {
|
if (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE) {
|
||||||
// Resonable refresh rate for home assistant database size health
|
// Resonable refresh rate for home assistant database size health
|
||||||
const int32_t current_millis = App.get_loop_component_start_time();
|
const int32_t current_millis = App.get_loop_component_start_time();
|
||||||
if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS)
|
if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
this->last_normal_periodic_millis = current_millis;
|
this->last_normal_periodic_millis = current_millis;
|
||||||
for (auto &listener : this->listeners_)
|
for (auto &listener : this->listeners_)
|
||||||
listener->on_distance(this->get_distance_());
|
listener->on_distance(this->get_distance_());
|
||||||
@ -593,11 +594,12 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
|
|||||||
int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
|
int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
|
||||||
uint32_t start_millis = millis();
|
uint32_t start_millis = millis();
|
||||||
uint8_t error = 0;
|
uint8_t error = 0;
|
||||||
uint8_t ack_buffer[64];
|
uint8_t ack_buffer[MAX_LINE_LENGTH];
|
||||||
uint8_t cmd_buffer[64];
|
uint8_t cmd_buffer[MAX_LINE_LENGTH];
|
||||||
this->cmd_reply_.ack = false;
|
this->cmd_reply_.ack = false;
|
||||||
if (frame.command != CMD_RESTART)
|
if (frame.command != CMD_RESTART) {
|
||||||
this->set_cmd_active_(true); // Restart does not reply, thus no ack state required.
|
this->cmd_active_ = true;
|
||||||
|
} // Restart does not reply, thus no ack state required
|
||||||
uint8_t retry = 3;
|
uint8_t retry = 3;
|
||||||
while (retry) {
|
while (retry) {
|
||||||
frame.length = 0;
|
frame.length = 0;
|
||||||
@ -619,9 +621,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
|
|||||||
|
|
||||||
memcpy(cmd_buffer + frame.length, &frame.footer, sizeof(frame.footer));
|
memcpy(cmd_buffer + frame.length, &frame.footer, sizeof(frame.footer));
|
||||||
frame.length += sizeof(frame.footer);
|
frame.length += sizeof(frame.footer);
|
||||||
for (uint16_t index = 0; index < frame.length; index++) {
|
this->write_array(cmd_buffer, frame.length);
|
||||||
this->write_byte(cmd_buffer[index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
error = 0;
|
error = 0;
|
||||||
if (frame.command == CMD_RESTART) {
|
if (frame.command == CMD_RESTART) {
|
||||||
@ -630,7 +630,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
|
|||||||
|
|
||||||
while (!this->cmd_reply_.ack) {
|
while (!this->cmd_reply_.ack) {
|
||||||
while (this->available()) {
|
while (this->available()) {
|
||||||
this->readline_(read(), ack_buffer, sizeof(ack_buffer));
|
this->readline_(this->read(), ack_buffer, sizeof(ack_buffer));
|
||||||
}
|
}
|
||||||
delay_microseconds_safe(1450);
|
delay_microseconds_safe(1450);
|
||||||
// Wait on an Rx from the LD2420 for up to 3 1 second loops, otherwise it could trigger a WDT.
|
// Wait on an Rx from the LD2420 for up to 3 1 second loops, otherwise it could trigger a WDT.
|
||||||
@ -641,11 +641,13 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this->cmd_reply_.ack)
|
if (this->cmd_reply_.ack) {
|
||||||
retry = 0;
|
retry = 0;
|
||||||
if (this->cmd_reply_.error > 0)
|
}
|
||||||
|
if (this->cmd_reply_.error > 0) {
|
||||||
this->handle_cmd_error(error);
|
this->handle_cmd_error(error);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -764,9 +766,10 @@ void LD2420Component::set_system_mode(uint16_t mode) {
|
|||||||
cmd_frame.data_length += sizeof(unknown_parm);
|
cmd_frame.data_length += sizeof(unknown_parm);
|
||||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||||
ESP_LOGV(TAG, "Sending write system mode command: %2X", cmd_frame.command);
|
ESP_LOGV(TAG, "Sending write system mode command: %2X", cmd_frame.command);
|
||||||
if (this->send_cmd_from_array(cmd_frame) == 0)
|
if (this->send_cmd_from_array(cmd_frame) == 0) {
|
||||||
this->set_mode_(mode);
|
this->set_mode_(mode);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void LD2420Component::get_firmware_version_() {
|
void LD2420Component::get_firmware_version_() {
|
||||||
CmdFrameT cmd_frame;
|
CmdFrameT cmd_frame;
|
||||||
@ -840,18 +843,24 @@ void LD2420Component::set_gate_threshold(uint8_t gate) {
|
|||||||
|
|
||||||
#ifdef USE_NUMBER
|
#ifdef USE_NUMBER
|
||||||
void LD2420Component::init_gate_config_numbers() {
|
void LD2420Component::init_gate_config_numbers() {
|
||||||
if (this->gate_timeout_number_ != nullptr)
|
if (this->gate_timeout_number_ != nullptr) {
|
||||||
this->gate_timeout_number_->publish_state(static_cast<uint16_t>(this->current_config.timeout));
|
this->gate_timeout_number_->publish_state(static_cast<uint16_t>(this->current_config.timeout));
|
||||||
if (this->gate_select_number_ != nullptr)
|
}
|
||||||
|
if (this->gate_select_number_ != nullptr) {
|
||||||
this->gate_select_number_->publish_state(0);
|
this->gate_select_number_->publish_state(0);
|
||||||
if (this->min_gate_distance_number_ != nullptr)
|
}
|
||||||
|
if (this->min_gate_distance_number_ != nullptr) {
|
||||||
this->min_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.min_gate));
|
this->min_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.min_gate));
|
||||||
if (this->max_gate_distance_number_ != nullptr)
|
}
|
||||||
|
if (this->max_gate_distance_number_ != nullptr) {
|
||||||
this->max_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.max_gate));
|
this->max_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.max_gate));
|
||||||
if (this->gate_move_sensitivity_factor_number_ != nullptr)
|
}
|
||||||
|
if (this->gate_move_sensitivity_factor_number_ != nullptr) {
|
||||||
this->gate_move_sensitivity_factor_number_->publish_state(this->gate_move_sensitivity_factor);
|
this->gate_move_sensitivity_factor_number_->publish_state(this->gate_move_sensitivity_factor);
|
||||||
if (this->gate_still_sensitivity_factor_number_ != nullptr)
|
}
|
||||||
|
if (this->gate_still_sensitivity_factor_number_ != nullptr) {
|
||||||
this->gate_still_sensitivity_factor_number_->publish_state(this->gate_still_sensitivity_factor);
|
this->gate_still_sensitivity_factor_number_->publish_state(this->gate_still_sensitivity_factor);
|
||||||
|
}
|
||||||
for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) {
|
for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) {
|
||||||
if (this->gate_still_threshold_numbers_[gate] != nullptr) {
|
if (this->gate_still_threshold_numbers_[gate] != nullptr) {
|
||||||
this->gate_still_threshold_numbers_[gate]->publish_state(
|
this->gate_still_threshold_numbers_[gate]->publish_state(
|
||||||
|
@ -20,8 +20,9 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ld2420 {
|
namespace ld2420 {
|
||||||
|
|
||||||
static const uint8_t TOTAL_GATES = 16;
|
|
||||||
static const uint8_t CALIBRATE_SAMPLES = 64;
|
static const uint8_t CALIBRATE_SAMPLES = 64;
|
||||||
|
static const uint8_t MAX_LINE_LENGTH = 46; // Max characters for serial buffer
|
||||||
|
static const uint8_t TOTAL_GATES = 16;
|
||||||
|
|
||||||
enum OpMode : uint8_t {
|
enum OpMode : uint8_t {
|
||||||
OP_NORMAL_MODE = 1,
|
OP_NORMAL_MODE = 1,
|
||||||
@ -118,10 +119,10 @@ class LD2420Component : public Component, public uart::UARTDevice {
|
|||||||
|
|
||||||
float gate_move_sensitivity_factor{0.5};
|
float gate_move_sensitivity_factor{0.5};
|
||||||
float gate_still_sensitivity_factor{0.5};
|
float gate_still_sensitivity_factor{0.5};
|
||||||
int32_t last_periodic_millis = millis();
|
int32_t last_periodic_millis{0};
|
||||||
int32_t report_periodic_millis = millis();
|
int32_t report_periodic_millis{0};
|
||||||
int32_t monitor_periodic_millis = millis();
|
int32_t monitor_periodic_millis{0};
|
||||||
int32_t last_normal_periodic_millis = millis();
|
int32_t last_normal_periodic_millis{0};
|
||||||
uint16_t radar_data[TOTAL_GATES][CALIBRATE_SAMPLES];
|
uint16_t radar_data[TOTAL_GATES][CALIBRATE_SAMPLES];
|
||||||
uint16_t gate_avg[TOTAL_GATES];
|
uint16_t gate_avg[TOTAL_GATES];
|
||||||
uint16_t gate_peak[TOTAL_GATES];
|
uint16_t gate_peak[TOTAL_GATES];
|
||||||
@ -161,8 +162,6 @@ class LD2420Component : public Component, public uart::UARTDevice {
|
|||||||
void set_presence_(bool presence) { this->presence_ = presence; };
|
void set_presence_(bool presence) { this->presence_ = presence; };
|
||||||
uint16_t get_distance_() { return this->distance_; };
|
uint16_t get_distance_() { return this->distance_; };
|
||||||
void set_distance_(uint16_t distance) { this->distance_ = distance; };
|
void set_distance_(uint16_t distance) { this->distance_ = distance; };
|
||||||
bool get_cmd_active_() { return this->cmd_active_; };
|
|
||||||
void set_cmd_active_(bool active) { this->cmd_active_ = active; };
|
|
||||||
void handle_simple_mode_(const uint8_t *inbuf, int len);
|
void handle_simple_mode_(const uint8_t *inbuf, int len);
|
||||||
void handle_energy_mode_(uint8_t *buffer, int len);
|
void handle_energy_mode_(uint8_t *buffer, int len);
|
||||||
void handle_ack_data_(uint8_t *buffer, int len);
|
void handle_ack_data_(uint8_t *buffer, int len);
|
||||||
@ -181,12 +180,11 @@ class LD2420Component : public Component, public uart::UARTDevice {
|
|||||||
std::vector<number::Number *> gate_move_threshold_numbers_ = std::vector<number::Number *>(16);
|
std::vector<number::Number *> gate_move_threshold_numbers_ = std::vector<number::Number *>(16);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
uint32_t max_distance_gate_;
|
uint16_t distance_{0};
|
||||||
uint32_t min_distance_gate_;
|
|
||||||
uint16_t system_mode_;
|
uint16_t system_mode_;
|
||||||
uint16_t gate_energy_[TOTAL_GATES];
|
uint16_t gate_energy_[TOTAL_GATES];
|
||||||
uint16_t distance_{0};
|
uint8_t buffer_pos_{0}; // where to resume processing/populating buffer
|
||||||
uint8_t config_checksum_{0};
|
uint8_t buffer_data_[MAX_LINE_LENGTH];
|
||||||
char firmware_ver_[8]{"v0.0.0"};
|
char firmware_ver_[8]{"v0.0.0"};
|
||||||
bool cmd_active_{false};
|
bool cmd_active_{false};
|
||||||
bool presence_{false};
|
bool presence_{false};
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
static const char *const TAG = "LD2420.number";
|
static const char *const TAG = "ld2420.number";
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ld2420 {
|
namespace ld2420 {
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ld2420 {
|
namespace ld2420 {
|
||||||
|
|
||||||
static const char *const TAG = "LD2420.select";
|
static const char *const TAG = "ld2420.select";
|
||||||
|
|
||||||
void LD2420Select::control(const std::string &value) {
|
void LD2420Select::control(const std::string &value) {
|
||||||
this->publish_state(value);
|
this->publish_state(value);
|
||||||
|
@ -5,10 +5,10 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ld2420 {
|
namespace ld2420 {
|
||||||
|
|
||||||
static const char *const TAG = "LD2420.sensor";
|
static const char *const TAG = "ld2420.sensor";
|
||||||
|
|
||||||
void LD2420Sensor::dump_config() {
|
void LD2420Sensor::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "LD2420 Sensor:");
|
ESP_LOGCONFIG(TAG, "Sensor:");
|
||||||
LOG_SENSOR(" ", "Distance", this->distance_sensor_);
|
LOG_SENSOR(" ", "Distance", this->distance_sensor_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,10 +5,10 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ld2420 {
|
namespace ld2420 {
|
||||||
|
|
||||||
static const char *const TAG = "LD2420.text_sensor";
|
static const char *const TAG = "ld2420.text_sensor";
|
||||||
|
|
||||||
void LD2420TextSensor::dump_config() {
|
void LD2420TextSensor::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "LD2420 TextSensor:");
|
ESP_LOGCONFIG(TAG, "Text Sensor:");
|
||||||
LOG_TEXT_SENSOR(" ", "Firmware", this->fw_version_text_sensor_);
|
LOG_TEXT_SENSOR(" ", "Firmware", this->fw_version_text_sensor_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,6 +268,7 @@ async def component_to_code(config):
|
|||||||
|
|
||||||
# disable library compatibility checks
|
# disable library compatibility checks
|
||||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||||
|
cg.add_platformio_option("lib_compat_mode", "strict")
|
||||||
# include <Arduino.h> in every file
|
# include <Arduino.h> in every file
|
||||||
cg.add_platformio_option("build_src_flags", "-include Arduino.h")
|
cg.add_platformio_option("build_src_flags", "-include Arduino.h")
|
||||||
# dummy version code
|
# dummy version code
|
||||||
|
0
esphome/components/lps22/__init__.py
Normal file
0
esphome/components/lps22/__init__.py
Normal file
75
esphome/components/lps22/lps22.cpp
Normal file
75
esphome/components/lps22/lps22.cpp
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
#include "lps22.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace lps22 {
|
||||||
|
|
||||||
|
static constexpr const char *const TAG = "lps22";
|
||||||
|
|
||||||
|
static constexpr uint8_t WHO_AM_I = 0x0F;
|
||||||
|
static constexpr uint8_t LPS22HB_ID = 0xB1;
|
||||||
|
static constexpr uint8_t LPS22HH_ID = 0xB3;
|
||||||
|
static constexpr uint8_t CTRL_REG2 = 0x11;
|
||||||
|
static constexpr uint8_t CTRL_REG2_ONE_SHOT_MASK = 0b1;
|
||||||
|
static constexpr uint8_t STATUS = 0x27;
|
||||||
|
static constexpr uint8_t STATUS_T_DA_MASK = 0b10;
|
||||||
|
static constexpr uint8_t STATUS_P_DA_MASK = 0b01;
|
||||||
|
static constexpr uint8_t TEMP_L = 0x2b;
|
||||||
|
static constexpr uint8_t PRES_OUT_XL = 0x28;
|
||||||
|
static constexpr uint8_t REF_P_XL = 0x28;
|
||||||
|
static constexpr uint8_t READ_ATTEMPTS = 10;
|
||||||
|
static constexpr uint8_t READ_INTERVAL = 5;
|
||||||
|
static constexpr float PRESSURE_SCALE = 1.0f / 4096.0f;
|
||||||
|
static constexpr float TEMPERATURE_SCALE = 0.01f;
|
||||||
|
|
||||||
|
void LPS22Component::setup() {
|
||||||
|
uint8_t value = 0x00;
|
||||||
|
this->read_register(WHO_AM_I, &value, 1);
|
||||||
|
if (value != LPS22HB_ID && value != LPS22HH_ID) {
|
||||||
|
ESP_LOGW(TAG, "device IDs as %02x, which isn't a known LPS22HB or LPS22HH ID", value);
|
||||||
|
this->mark_failed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LPS22Component::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "LPS22:");
|
||||||
|
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LPS22Component::update() {
|
||||||
|
uint8_t value = 0x00;
|
||||||
|
this->read_register(CTRL_REG2, &value, 1);
|
||||||
|
value |= CTRL_REG2_ONE_SHOT_MASK;
|
||||||
|
this->write_register(CTRL_REG2, &value, 1);
|
||||||
|
this->set_retry(READ_INTERVAL, READ_ATTEMPTS, [this](uint8_t _) { return this->try_read_(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
RetryResult LPS22Component::try_read_() {
|
||||||
|
uint8_t value = 0x00;
|
||||||
|
this->read_register(STATUS, &value, 1);
|
||||||
|
const uint8_t expected_status_mask = STATUS_T_DA_MASK | STATUS_P_DA_MASK;
|
||||||
|
if ((value & expected_status_mask) != expected_status_mask) {
|
||||||
|
ESP_LOGD(TAG, "STATUS not ready: %x", value);
|
||||||
|
return RetryResult::RETRY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->temperature_sensor_ != nullptr) {
|
||||||
|
uint8_t t_buf[2]{0};
|
||||||
|
this->read_register(TEMP_L, t_buf, 2);
|
||||||
|
int16_t encoded = static_cast<int16_t>(encode_uint16(t_buf[1], t_buf[0]));
|
||||||
|
float temp = TEMPERATURE_SCALE * static_cast<float>(encoded);
|
||||||
|
this->temperature_sensor_->publish_state(temp);
|
||||||
|
}
|
||||||
|
if (this->pressure_sensor_ != nullptr) {
|
||||||
|
uint8_t p_buf[3]{0};
|
||||||
|
this->read_register(PRES_OUT_XL, p_buf, 3);
|
||||||
|
uint32_t p_lsb = encode_uint24(p_buf[2], p_buf[1], p_buf[0]);
|
||||||
|
this->pressure_sensor_->publish_state(PRESSURE_SCALE * static_cast<float>(p_lsb));
|
||||||
|
}
|
||||||
|
return RetryResult::DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace lps22
|
||||||
|
} // namespace esphome
|
27
esphome/components/lps22/lps22.h
Normal file
27
esphome/components/lps22/lps22.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace lps22 {
|
||||||
|
|
||||||
|
class LPS22Component : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
|
||||||
|
public:
|
||||||
|
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
|
||||||
|
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { this->pressure_sensor_ = pressure_sensor; }
|
||||||
|
|
||||||
|
void setup() override;
|
||||||
|
void update() override;
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
sensor::Sensor *temperature_sensor_{nullptr};
|
||||||
|
sensor::Sensor *pressure_sensor_{nullptr};
|
||||||
|
|
||||||
|
RetryResult try_read_();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace lps22
|
||||||
|
} // namespace esphome
|
58
esphome/components/lps22/sensor.py
Normal file
58
esphome/components/lps22/sensor.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import i2c, sensor
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
|
CONF_PRESSURE,
|
||||||
|
CONF_TEMPERATURE,
|
||||||
|
DEVICE_CLASS_PRESSURE,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
ICON_THERMOMETER,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
UNIT_HECTOPASCAL,
|
||||||
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@nagisa"]
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
|
lps22 = cg.esphome_ns.namespace("lps22")
|
||||||
|
|
||||||
|
LPS22Component = lps22.class_("LPS22Component", cg.PollingComponent, i2c.I2CDevice)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(LPS22Component),
|
||||||
|
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_CELSIUS,
|
||||||
|
icon=ICON_THERMOMETER,
|
||||||
|
accuracy_decimals=2,
|
||||||
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_HECTOPASCAL,
|
||||||
|
accuracy_decimals=2,
|
||||||
|
device_class=DEVICE_CLASS_PRESSURE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(i2c.i2c_device_schema(0x5D)) # can also be 0x5C
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
|
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||||
|
sens = await sensor.new_sensor(temperature_config)
|
||||||
|
cg.add(var.set_temperature_sensor(sens))
|
||||||
|
|
||||||
|
if pressure_config := config.get(CONF_PRESSURE):
|
||||||
|
sens = await sensor.new_sensor(pressure_config)
|
||||||
|
cg.add(var.set_pressure_sensor(sens))
|
@ -153,11 +153,15 @@ void MQTTBackendESP32::mqtt_event_handler_(const Event &event) {
|
|||||||
case MQTT_EVENT_DATA: {
|
case MQTT_EVENT_DATA: {
|
||||||
static std::string topic;
|
static std::string topic;
|
||||||
if (!event.topic.empty()) {
|
if (!event.topic.empty()) {
|
||||||
|
// When a single message arrives as multiple chunks, the topic will be empty
|
||||||
|
// on any but the first message, leading to event.topic being an empty string.
|
||||||
|
// To ensure handlers get the correct topic, cache the last seen topic to
|
||||||
|
// simulate always receiving the topic from underlying library
|
||||||
topic = event.topic;
|
topic = event.topic;
|
||||||
}
|
}
|
||||||
ESP_LOGV(TAG, "MQTT_EVENT_DATA %s", topic.c_str());
|
ESP_LOGV(TAG, "MQTT_EVENT_DATA %s", topic.c_str());
|
||||||
this->on_message_.call(!event.topic.empty() ? topic.c_str() : nullptr, event.data.data(), event.data.size(),
|
this->on_message_.call(topic.c_str(), event.data.data(), event.data.size(), event.current_data_offset,
|
||||||
event.current_data_offset, event.total_data_len);
|
event.total_data_len);
|
||||||
} break;
|
} break;
|
||||||
case MQTT_EVENT_ERROR:
|
case MQTT_EVENT_ERROR:
|
||||||
ESP_LOGE(TAG, "MQTT_EVENT_ERROR");
|
ESP_LOGE(TAG, "MQTT_EVENT_ERROR");
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "nfc.h"
|
#include "nfc.h"
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@ -7,29 +8,9 @@ namespace nfc {
|
|||||||
|
|
||||||
static const char *const TAG = "nfc";
|
static const char *const TAG = "nfc";
|
||||||
|
|
||||||
std::string format_uid(std::vector<uint8_t> &uid) {
|
std::string format_uid(const std::vector<uint8_t> &uid) { return format_hex_pretty(uid, '-', false); }
|
||||||
char buf[(uid.size() * 2) + uid.size() - 1];
|
|
||||||
int offset = 0;
|
|
||||||
for (size_t i = 0; i < uid.size(); i++) {
|
|
||||||
const char *format = "%02X";
|
|
||||||
if (i + 1 < uid.size())
|
|
||||||
format = "%02X-";
|
|
||||||
offset += sprintf(buf + offset, format, uid[i]);
|
|
||||||
}
|
|
||||||
return std::string(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string format_bytes(std::vector<uint8_t> &bytes) {
|
std::string format_bytes(const std::vector<uint8_t> &bytes) { return format_hex_pretty(bytes, ' ', false); }
|
||||||
char buf[(bytes.size() * 2) + bytes.size() - 1];
|
|
||||||
int offset = 0;
|
|
||||||
for (size_t i = 0; i < bytes.size(); i++) {
|
|
||||||
const char *format = "%02X";
|
|
||||||
if (i + 1 < bytes.size())
|
|
||||||
format = "%02X ";
|
|
||||||
offset += sprintf(buf + offset, format, bytes[i]);
|
|
||||||
}
|
|
||||||
return std::string(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t guess_tag_type(uint8_t uid_length) {
|
uint8_t guess_tag_type(uint8_t uid_length) {
|
||||||
if (uid_length == 4) {
|
if (uid_length == 4) {
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "ndef_record.h"
|
|
||||||
#include "ndef_message.h"
|
#include "ndef_message.h"
|
||||||
|
#include "ndef_record.h"
|
||||||
#include "nfc_tag.h"
|
#include "nfc_tag.h"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -53,8 +53,8 @@ static const uint8_t DEFAULT_KEY[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
|||||||
static const uint8_t NDEF_KEY[6] = {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7};
|
static const uint8_t NDEF_KEY[6] = {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7};
|
||||||
static const uint8_t MAD_KEY[6] = {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5};
|
static const uint8_t MAD_KEY[6] = {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5};
|
||||||
|
|
||||||
std::string format_uid(std::vector<uint8_t> &uid);
|
std::string format_uid(const std::vector<uint8_t> &uid);
|
||||||
std::string format_bytes(std::vector<uint8_t> &bytes);
|
std::string format_bytes(const std::vector<uint8_t> &bytes);
|
||||||
|
|
||||||
uint8_t guess_tag_type(uint8_t uid_length);
|
uint8_t guess_tag_type(uint8_t uid_length);
|
||||||
uint8_t get_mifare_classic_ndef_start_index(std::vector<uint8_t> &data);
|
uint8_t get_mifare_classic_ndef_start_index(std::vector<uint8_t> &data);
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
|
||||||
from esphome.components import i2c, sensor
|
from esphome.components import i2c, sensor
|
||||||
from esphome.const import (
|
import esphome.config_validation as cv
|
||||||
DEVICE_CLASS_ILLUMINANCE,
|
from esphome.const import DEVICE_CLASS_ILLUMINANCE, STATE_CLASS_MEASUREMENT, UNIT_LUX
|
||||||
STATE_CLASS_MEASUREMENT,
|
|
||||||
UNIT_LUX,
|
|
||||||
)
|
|
||||||
|
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
CODEOWNERS = ["@ccutrer"]
|
CODEOWNERS = ["@ccutrer"]
|
||||||
|
@ -314,6 +314,9 @@ void PacketTransport::send_data_(bool all) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PacketTransport::update() {
|
void PacketTransport::update() {
|
||||||
|
if (!this->ping_pong_enable_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
auto now = millis() / 1000;
|
auto now = millis() / 1000;
|
||||||
if (this->last_key_time_ + this->ping_pong_recyle_time_ < now) {
|
if (this->last_key_time_ + this->ping_pong_recyle_time_ < now) {
|
||||||
this->resend_ping_key_ = this->ping_pong_enable_;
|
this->resend_ping_key_ = this->ping_pong_enable_;
|
||||||
|
@ -165,6 +165,7 @@ async def to_code(config):
|
|||||||
# Allow LDF to properly discover dependency including those in preprocessor
|
# Allow LDF to properly discover dependency including those in preprocessor
|
||||||
# conditionals
|
# conditionals
|
||||||
cg.add_platformio_option("lib_ldf_mode", "chain+")
|
cg.add_platformio_option("lib_ldf_mode", "chain+")
|
||||||
|
cg.add_platformio_option("lib_compat_mode", "strict")
|
||||||
cg.add_platformio_option("board", config[CONF_BOARD])
|
cg.add_platformio_option("board", config[CONF_BOARD])
|
||||||
cg.add_build_flag("-DUSE_RP2040")
|
cg.add_build_flag("-DUSE_RP2040")
|
||||||
cg.set_cpp_standard("gnu++20")
|
cg.set_cpp_standard("gnu++20")
|
||||||
|
@ -5,13 +5,8 @@ from esphome.config_helpers import Extend, Remove, merge_config
|
|||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_SUBSTITUTIONS, VALID_SUBSTITUTIONS_CHARACTERS
|
from esphome.const import CONF_SUBSTITUTIONS, VALID_SUBSTITUTIONS_CHARACTERS
|
||||||
from esphome.yaml_util import ESPHomeDataBase, make_data_base
|
from esphome.yaml_util import ESPHomeDataBase, make_data_base
|
||||||
from .jinja import (
|
|
||||||
Jinja,
|
from .jinja import Jinja, JinjaStr, TemplateError, TemplateRuntimeError, has_jinja
|
||||||
JinjaStr,
|
|
||||||
has_jinja,
|
|
||||||
TemplateError,
|
|
||||||
TemplateRuntimeError,
|
|
||||||
)
|
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import jinja2 as jinja
|
import jinja2 as jinja
|
||||||
from jinja2.nativetypes import NativeEnvironment
|
from jinja2.nativetypes import NativeEnvironment
|
||||||
|
|
||||||
|
@ -167,8 +167,8 @@ def validate_config(config):
|
|||||||
if config[CONF_MODULATION] == "LORA":
|
if config[CONF_MODULATION] == "LORA":
|
||||||
if config[CONF_BANDWIDTH] not in lora_bws:
|
if config[CONF_BANDWIDTH] not in lora_bws:
|
||||||
raise cv.Invalid(f"{config[CONF_BANDWIDTH]} is not available with LORA")
|
raise cv.Invalid(f"{config[CONF_BANDWIDTH]} is not available with LORA")
|
||||||
if config[CONF_PREAMBLE_SIZE] > 0 and config[CONF_PREAMBLE_SIZE] < 6:
|
if config[CONF_PREAMBLE_SIZE] < 6:
|
||||||
raise cv.Invalid("Minimum preamble size is 6 with LORA")
|
raise cv.Invalid("Minimum 'preamble_size' is 6 with LORA")
|
||||||
if config[CONF_SPREADING_FACTOR] == 6 and config[CONF_PAYLOAD_LENGTH] == 0:
|
if config[CONF_SPREADING_FACTOR] == 6 and config[CONF_PAYLOAD_LENGTH] == 0:
|
||||||
raise cv.Invalid("Payload length must be set when spreading factor is 6")
|
raise cv.Invalid("Payload length must be set when spreading factor is 6")
|
||||||
else:
|
else:
|
||||||
@ -200,7 +200,7 @@ CONFIG_SCHEMA = (
|
|||||||
cv.Optional(CONF_PA_RAMP, default="40us"): cv.enum(RAMP),
|
cv.Optional(CONF_PA_RAMP, default="40us"): cv.enum(RAMP),
|
||||||
cv.Optional(CONF_PAYLOAD_LENGTH, default=0): cv.int_range(min=0, max=256),
|
cv.Optional(CONF_PAYLOAD_LENGTH, default=0): cv.int_range(min=0, max=256),
|
||||||
cv.Optional(CONF_PREAMBLE_DETECT, default=2): cv.int_range(min=0, max=4),
|
cv.Optional(CONF_PREAMBLE_DETECT, default=2): cv.int_range(min=0, max=4),
|
||||||
cv.Required(CONF_PREAMBLE_SIZE): cv.int_range(min=1, max=65535),
|
cv.Optional(CONF_PREAMBLE_SIZE, default=8): cv.int_range(min=1, max=65535),
|
||||||
cv.Required(CONF_RST_PIN): pins.internal_gpio_output_pin_schema,
|
cv.Required(CONF_RST_PIN): pins.internal_gpio_output_pin_schema,
|
||||||
cv.Optional(CONF_RX_START, default=True): cv.boolean,
|
cv.Optional(CONF_RX_START, default=True): cv.boolean,
|
||||||
cv.Required(CONF_RF_SWITCH): cv.boolean,
|
cv.Required(CONF_RF_SWITCH): cv.boolean,
|
||||||
|
@ -164,8 +164,8 @@ def validate_config(config):
|
|||||||
raise cv.Invalid(f"{config[CONF_BANDWIDTH]} is not available with LORA")
|
raise cv.Invalid(f"{config[CONF_BANDWIDTH]} is not available with LORA")
|
||||||
if CONF_DIO0_PIN not in config:
|
if CONF_DIO0_PIN not in config:
|
||||||
raise cv.Invalid("Cannot use LoRa without dio0_pin")
|
raise cv.Invalid("Cannot use LoRa without dio0_pin")
|
||||||
if 0 < config[CONF_PREAMBLE_SIZE] < 6:
|
if config[CONF_PREAMBLE_SIZE] < 6:
|
||||||
raise cv.Invalid("Minimum preamble size is 6 with LORA")
|
raise cv.Invalid("Minimum 'preamble_size' is 6 with LORA")
|
||||||
if config[CONF_SPREADING_FACTOR] == 6 and config[CONF_PAYLOAD_LENGTH] == 0:
|
if config[CONF_SPREADING_FACTOR] == 6 and config[CONF_PAYLOAD_LENGTH] == 0:
|
||||||
raise cv.Invalid("Payload length must be set when spreading factor is 6")
|
raise cv.Invalid("Payload length must be set when spreading factor is 6")
|
||||||
else:
|
else:
|
||||||
|
@ -78,7 +78,7 @@ enum JsonDetail { DETAIL_ALL, DETAIL_STATE };
|
|||||||
This is because only minimal changes were made to the ESPAsyncWebServer lib_dep, it was undesirable to put deferred
|
This is because only minimal changes were made to the ESPAsyncWebServer lib_dep, it was undesirable to put deferred
|
||||||
update logic into that library. We need one deferred queue per connection so instead of one AsyncEventSource with
|
update logic into that library. We need one deferred queue per connection so instead of one AsyncEventSource with
|
||||||
multiple clients, we have multiple event sources with one client each. This is slightly awkward which is why it's
|
multiple clients, we have multiple event sources with one client each. This is slightly awkward which is why it's
|
||||||
implemented in a more straightforward way for ESP-IDF. Arudino platform will eventually go away and this workaround
|
implemented in a more straightforward way for ESP-IDF. Arduino platform will eventually go away and this workaround
|
||||||
can be forgotten.
|
can be forgotten.
|
||||||
*/
|
*/
|
||||||
#ifdef USE_ARDUINO
|
#ifdef USE_ARDUINO
|
||||||
|
@ -1055,6 +1055,7 @@ def float_with_unit(quantity, regex_suffix, optional_unit=False):
|
|||||||
return validator
|
return validator
|
||||||
|
|
||||||
|
|
||||||
|
bps = float_with_unit("bits per second", "(bps|bits/s|bit/s)?")
|
||||||
frequency = float_with_unit("frequency", "(Hz|HZ|hz)?")
|
frequency = float_with_unit("frequency", "(Hz|HZ|hz)?")
|
||||||
resistance = float_with_unit("resistance", "(Ω|Ω|ohm|Ohm|OHM)?")
|
resistance = float_with_unit("resistance", "(Ω|Ω|ohm|Ohm|OHM)?")
|
||||||
current = float_with_unit("current", "(a|A|amp|Amp|amps|Amps|ampere|Ampere)?")
|
current = float_with_unit("current", "(a|A|amp|Amp|amps|Amps|ampere|Ampere)?")
|
||||||
|
@ -4,7 +4,7 @@ from enum import Enum
|
|||||||
|
|
||||||
from esphome.enum import StrEnum
|
from esphome.enum import StrEnum
|
||||||
|
|
||||||
__version__ = "2025.7.0-dev"
|
__version__ = "2025.8.0-dev"
|
||||||
|
|
||||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
#ifdef USE_API
|
#ifdef USE_API
|
||||||
#include "esphome/components/api/api_server.h"
|
#include "esphome/components/api/api_server.h"
|
||||||
|
#endif
|
||||||
|
#ifdef USE_API_SERVICES
|
||||||
#include "esphome/components/api/user_services.h"
|
#include "esphome/components/api/user_services.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -148,7 +150,7 @@ void ComponentIterator::advance() {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API
|
#ifdef USE_API_SERVICES
|
||||||
case IteratorState ::SERVICE:
|
case IteratorState ::SERVICE:
|
||||||
if (this->at_ >= api::global_api_server->get_user_services().size()) {
|
if (this->at_ >= api::global_api_server->get_user_services().size()) {
|
||||||
advance_platform = true;
|
advance_platform = true;
|
||||||
@ -383,7 +385,7 @@ void ComponentIterator::advance() {
|
|||||||
}
|
}
|
||||||
bool ComponentIterator::on_end() { return true; }
|
bool ComponentIterator::on_end() { return true; }
|
||||||
bool ComponentIterator::on_begin() { return true; }
|
bool ComponentIterator::on_begin() { return true; }
|
||||||
#ifdef USE_API
|
#ifdef USE_API_SERVICES
|
||||||
bool ComponentIterator::on_service(api::UserServiceDescriptor *service) { return true; }
|
bool ComponentIterator::on_service(api::UserServiceDescriptor *service) { return true; }
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_CAMERA
|
#ifdef USE_CAMERA
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
|
||||||
#ifdef USE_API
|
#ifdef USE_API_SERVICES
|
||||||
namespace api {
|
namespace api {
|
||||||
class UserServiceDescriptor;
|
class UserServiceDescriptor;
|
||||||
} // namespace api
|
} // namespace api
|
||||||
@ -45,7 +45,7 @@ class ComponentIterator {
|
|||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0;
|
virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API
|
#ifdef USE_API_SERVICES
|
||||||
virtual bool on_service(api::UserServiceDescriptor *service);
|
virtual bool on_service(api::UserServiceDescriptor *service);
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_CAMERA
|
#ifdef USE_CAMERA
|
||||||
@ -122,7 +122,7 @@ class ComponentIterator {
|
|||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
TEXT_SENSOR,
|
TEXT_SENSOR,
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API
|
#ifdef USE_API_SERVICES
|
||||||
SERVICE,
|
SERVICE,
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_CAMERA
|
#ifdef USE_CAMERA
|
||||||
|
@ -108,7 +108,7 @@
|
|||||||
#define USE_API_CLIENT_DISCONNECTED_TRIGGER
|
#define USE_API_CLIENT_DISCONNECTED_TRIGGER
|
||||||
#define USE_API_NOISE
|
#define USE_API_NOISE
|
||||||
#define USE_API_PLAINTEXT
|
#define USE_API_PLAINTEXT
|
||||||
#define USE_API_YAML_SERVICES
|
#define USE_API_SERVICES
|
||||||
#define USE_MD5
|
#define USE_MD5
|
||||||
#define USE_MQTT
|
#define USE_MQTT
|
||||||
#define USE_NETWORK
|
#define USE_NETWORK
|
||||||
|
@ -187,6 +187,12 @@ def entity_duplicate_validator(platform: str) -> Callable[[ConfigType], ConfigTy
|
|||||||
# No name to validate
|
# No name to validate
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
# Skip validation for internal entities
|
||||||
|
# Internal entities are not exposed to Home Assistant and don't use the hash-based
|
||||||
|
# entity state tracking system, so name collisions don't matter for them
|
||||||
|
if config.get(CONF_INTERNAL, False):
|
||||||
|
return config
|
||||||
|
|
||||||
# Get the entity name
|
# Get the entity name
|
||||||
entity_name = config[CONF_NAME]
|
entity_name = config[CONF_NAME]
|
||||||
|
|
||||||
|
@ -263,7 +263,7 @@ std::string format_hex_pretty(const uint8_t *data, size_t length, char separator
|
|||||||
return "";
|
return "";
|
||||||
std::string ret;
|
std::string ret;
|
||||||
uint8_t multiple = separator ? 3 : 2; // 3 if separator is not \0, 2 otherwise
|
uint8_t multiple = separator ? 3 : 2; // 3 if separator is not \0, 2 otherwise
|
||||||
ret.resize(multiple * length - 1);
|
ret.resize(multiple * length - (separator ? 1 : 0));
|
||||||
for (size_t i = 0; i < length; i++) {
|
for (size_t i = 0; i < length; i++) {
|
||||||
ret[multiple * i] = format_hex_pretty_char((data[i] & 0xF0) >> 4);
|
ret[multiple * i] = format_hex_pretty_char((data[i] & 0xF0) >> 4);
|
||||||
ret[multiple * i + 1] = format_hex_pretty_char(data[i] & 0x0F);
|
ret[multiple * i + 1] = format_hex_pretty_char(data[i] & 0x0F);
|
||||||
@ -283,7 +283,7 @@ std::string format_hex_pretty(const uint16_t *data, size_t length, char separato
|
|||||||
return "";
|
return "";
|
||||||
std::string ret;
|
std::string ret;
|
||||||
uint8_t multiple = separator ? 5 : 4; // 5 if separator is not \0, 4 otherwise
|
uint8_t multiple = separator ? 5 : 4; // 5 if separator is not \0, 4 otherwise
|
||||||
ret.resize(multiple * length - 1);
|
ret.resize(multiple * length - (separator ? 1 : 0));
|
||||||
for (size_t i = 0; i < length; i++) {
|
for (size_t i = 0; i < length; i++) {
|
||||||
ret[multiple * i] = format_hex_pretty_char((data[i] & 0xF000) >> 12);
|
ret[multiple * i] = format_hex_pretty_char((data[i] & 0xF000) >> 12);
|
||||||
ret[multiple * i + 1] = format_hex_pretty_char((data[i] & 0x0F00) >> 8);
|
ret[multiple * i + 1] = format_hex_pretty_char((data[i] & 0x0F00) >> 8);
|
||||||
@ -304,7 +304,7 @@ std::string format_hex_pretty(const std::string &data, char separator, bool show
|
|||||||
return "";
|
return "";
|
||||||
std::string ret;
|
std::string ret;
|
||||||
uint8_t multiple = separator ? 3 : 2; // 3 if separator is not \0, 2 otherwise
|
uint8_t multiple = separator ? 3 : 2; // 3 if separator is not \0, 2 otherwise
|
||||||
ret.resize(multiple * data.length() - 1);
|
ret.resize(multiple * data.length() - (separator ? 1 : 0));
|
||||||
for (size_t i = 0; i < data.length(); i++) {
|
for (size_t i = 0; i < data.length(); i++) {
|
||||||
ret[multiple * i] = format_hex_pretty_char((data[i] & 0xF0) >> 4);
|
ret[multiple * i] = format_hex_pretty_char((data[i] & 0xF0) >> 4);
|
||||||
ret[multiple * i + 1] = format_hex_pretty_char(data[i] & 0x0F);
|
ret[multiple * i + 1] = format_hex_pretty_char(data[i] & 0x0F);
|
||||||
|
@ -66,10 +66,8 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
|||||||
|
|
||||||
if (delay == SCHEDULER_DONT_RUN) {
|
if (delay == SCHEDULER_DONT_RUN) {
|
||||||
// Still need to cancel existing timer if name is not empty
|
// Still need to cancel existing timer if name is not empty
|
||||||
if (this->is_name_valid_(name_cstr)) {
|
|
||||||
LockGuard guard{this->lock_};
|
LockGuard guard{this->lock_};
|
||||||
this->cancel_item_locked_(component, name_cstr, type);
|
this->cancel_item_locked_(component, name_cstr, type);
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,10 +123,8 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
|||||||
|
|
||||||
LockGuard guard{this->lock_};
|
LockGuard guard{this->lock_};
|
||||||
// If name is provided, do atomic cancel-and-add
|
// If name is provided, do atomic cancel-and-add
|
||||||
if (this->is_name_valid_(name_cstr)) {
|
|
||||||
// Cancel existing items
|
// Cancel existing items
|
||||||
this->cancel_item_locked_(component, name_cstr, type);
|
this->cancel_item_locked_(component, name_cstr, type);
|
||||||
}
|
|
||||||
// Add new item directly to to_add_
|
// Add new item directly to to_add_
|
||||||
// since we have the lock held
|
// since we have the lock held
|
||||||
this->to_add_.push_back(std::move(item));
|
this->to_add_.push_back(std::move(item));
|
||||||
@ -442,10 +438,6 @@ bool HOT Scheduler::cancel_item_(Component *component, bool is_static_string, co
|
|||||||
// Get the name as const char*
|
// Get the name as const char*
|
||||||
const char *name_cstr = this->get_name_cstr_(is_static_string, name_ptr);
|
const char *name_cstr = this->get_name_cstr_(is_static_string, name_ptr);
|
||||||
|
|
||||||
// Handle null or empty names
|
|
||||||
if (!this->is_name_valid_(name_cstr))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// obtain lock because this function iterates and can be called from non-loop task context
|
// obtain lock because this function iterates and can be called from non-loop task context
|
||||||
LockGuard guard{this->lock_};
|
LockGuard guard{this->lock_};
|
||||||
return this->cancel_item_locked_(component, name_cstr, type);
|
return this->cancel_item_locked_(component, name_cstr, type);
|
||||||
@ -453,6 +445,11 @@ bool HOT Scheduler::cancel_item_(Component *component, bool is_static_string, co
|
|||||||
|
|
||||||
// Helper to cancel items by name - must be called with lock held
|
// Helper to cancel items by name - must be called with lock held
|
||||||
bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_cstr, SchedulerItem::Type type) {
|
bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_cstr, SchedulerItem::Type type) {
|
||||||
|
// Early return if name is invalid - no items to cancel
|
||||||
|
if (name_cstr == nullptr || name_cstr[0] == '\0') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
size_t total_cancelled = 0;
|
size_t total_cancelled = 0;
|
||||||
|
|
||||||
// Check all containers for matching items
|
// Check all containers for matching items
|
||||||
|
@ -150,9 +150,6 @@ class Scheduler {
|
|||||||
return is_static_string ? static_cast<const char *>(name_ptr) : static_cast<const std::string *>(name_ptr)->c_str();
|
return is_static_string ? static_cast<const char *>(name_ptr) : static_cast<const std::string *>(name_ptr)->c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to check if a name is valid (not null and not empty)
|
|
||||||
inline bool is_name_valid_(const char *name) { return name != nullptr && name[0] != '\0'; }
|
|
||||||
|
|
||||||
// Common implementation for cancel operations
|
// Common implementation for cancel operations
|
||||||
bool cancel_item_(Component *component, bool is_static_string, const void *name_ptr, SchedulerItem::Type type);
|
bool cancel_item_(Component *component, bool is_static_string, const void *name_ptr, SchedulerItem::Type type);
|
||||||
|
|
||||||
|
@ -411,7 +411,7 @@ def wizard(path):
|
|||||||
safe_print("Options:")
|
safe_print("Options:")
|
||||||
for board_id, board_data in boards_list:
|
for board_id, board_data in boards_list:
|
||||||
safe_print(f" - {board_id} - {board_data['name']}")
|
safe_print(f" - {board_id} - {board_data['name']}")
|
||||||
boards.append(board_id)
|
boards.append(board_id.lower())
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
board = safe_input(color(AnsiFore.BOLD_WHITE, "(board): "))
|
board = safe_input(color(AnsiFore.BOLD_WHITE, "(board): "))
|
||||||
|
@ -61,6 +61,7 @@ src_filter =
|
|||||||
+<../tests/dummy_main.cpp>
|
+<../tests/dummy_main.cpp>
|
||||||
+<../.temp/all-include.cpp>
|
+<../.temp/all-include.cpp>
|
||||||
lib_ldf_mode = off
|
lib_ldf_mode = off
|
||||||
|
lib_compat_mode = strict
|
||||||
|
|
||||||
; This are common settings for all Arduino-framework based environments.
|
; This are common settings for all Arduino-framework based environments.
|
||||||
[common:arduino]
|
[common:arduino]
|
||||||
|
@ -13,7 +13,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
|
|||||||
esptool==4.9.0
|
esptool==4.9.0
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
esphome-dashboard==20250514.0
|
esphome-dashboard==20250514.0
|
||||||
aioesphomeapi==34.1.0
|
aioesphomeapi==34.2.0
|
||||||
zeroconf==0.147.0
|
zeroconf==0.147.0
|
||||||
puremagic==1.30
|
puremagic==1.30
|
||||||
ruamel.yaml==0.18.14 # dashboard_import
|
ruamel.yaml==0.18.14 # dashboard_import
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
pylint==3.3.7
|
pylint==3.3.7
|
||||||
flake8==7.3.0 # also change in .pre-commit-config.yaml when updating
|
flake8==7.3.0 # also change in .pre-commit-config.yaml when updating
|
||||||
ruff==0.12.2 # also change in .pre-commit-config.yaml when updating
|
ruff==0.12.3 # also change in .pre-commit-config.yaml when updating
|
||||||
pyupgrade==3.20.0 # also change in .pre-commit-config.yaml when updating
|
pyupgrade==3.20.0 # also change in .pre-commit-config.yaml when updating
|
||||||
pre-commit
|
pre-commit
|
||||||
|
|
||||||
|
@ -249,6 +249,42 @@ class TypeInfo(ABC):
|
|||||||
return 4 # 28 bits
|
return 4 # 28 bits
|
||||||
return 5 # 32 bits (maximum for uint32_t)
|
return 5 # 32 bits (maximum for uint32_t)
|
||||||
|
|
||||||
|
def _get_simple_size_calculation(
|
||||||
|
self, name: str, force: bool, base_method: str, value_expr: str = None
|
||||||
|
) -> str:
|
||||||
|
"""Helper for simple size calculations.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Field name
|
||||||
|
force: Whether this is for a repeated field
|
||||||
|
base_method: Base method name (e.g., "add_int32_field")
|
||||||
|
value_expr: Optional value expression (defaults to name)
|
||||||
|
"""
|
||||||
|
field_id_size = self.calculate_field_id_size()
|
||||||
|
method = f"{base_method}_repeated" if force else base_method
|
||||||
|
value = value_expr if value_expr else name
|
||||||
|
return f"ProtoSize::{method}(total_size, {field_id_size}, {value});"
|
||||||
|
|
||||||
|
def _get_fixed_size_calculation(
|
||||||
|
self, name: str, force: bool, num_bytes: int, zero_check: str
|
||||||
|
) -> str:
|
||||||
|
"""Helper for fixed-size field calculations.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Field name
|
||||||
|
force: Whether this is for a repeated field
|
||||||
|
num_bytes: Number of bytes (4 or 8)
|
||||||
|
zero_check: Expression to check for zero value (e.g., "!= 0.0f")
|
||||||
|
"""
|
||||||
|
field_id_size = self.calculate_field_id_size()
|
||||||
|
# Fixed-size repeated fields are handled differently in RepeatedTypeInfo
|
||||||
|
# so we should never get force=True here
|
||||||
|
assert not force, (
|
||||||
|
"Fixed-size repeated fields should be handled by RepeatedTypeInfo"
|
||||||
|
)
|
||||||
|
method = f"add_fixed_field<{num_bytes}>"
|
||||||
|
return f"ProtoSize::{method}(total_size, {field_id_size}, {name} {zero_check});"
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
||||||
"""Calculate the size needed for encoding this field.
|
"""Calculate the size needed for encoding this field.
|
||||||
@ -258,6 +294,14 @@ class TypeInfo(ABC):
|
|||||||
force: Whether to force encoding the field even if it has a default value
|
force: Whether to force encoding the field even if it has a default value
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def get_fixed_size_bytes(self) -> int | None:
|
||||||
|
"""Get the number of bytes for fixed-size fields (float, double, fixed32, etc).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The number of bytes (4 or 8) for fixed-size fields, None for variable-size fields.
|
||||||
|
"""
|
||||||
|
return None
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_estimated_size(self) -> int:
|
def get_estimated_size(self) -> int:
|
||||||
"""Get estimated size in bytes for this field with typical values.
|
"""Get estimated size in bytes for this field with typical values.
|
||||||
@ -295,9 +339,10 @@ class DoubleType(TypeInfo):
|
|||||||
return o
|
return o
|
||||||
|
|
||||||
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
||||||
field_id_size = self.calculate_field_id_size()
|
return self._get_fixed_size_calculation(name, force, 8, "!= 0.0")
|
||||||
o = f"ProtoSize::add_fixed_field<8>(total_size, {field_id_size}, {name} != 0.0, {force_str(force)});"
|
|
||||||
return o
|
def get_fixed_size_bytes(self) -> int:
|
||||||
|
return 8
|
||||||
|
|
||||||
def get_estimated_size(self) -> int:
|
def get_estimated_size(self) -> int:
|
||||||
return self.calculate_field_id_size() + 8 # field ID + 8 bytes for double
|
return self.calculate_field_id_size() + 8 # field ID + 8 bytes for double
|
||||||
@ -317,9 +362,10 @@ class FloatType(TypeInfo):
|
|||||||
return o
|
return o
|
||||||
|
|
||||||
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
||||||
field_id_size = self.calculate_field_id_size()
|
return self._get_fixed_size_calculation(name, force, 4, "!= 0.0f")
|
||||||
o = f"ProtoSize::add_fixed_field<4>(total_size, {field_id_size}, {name} != 0.0f, {force_str(force)});"
|
|
||||||
return o
|
def get_fixed_size_bytes(self) -> int:
|
||||||
|
return 4
|
||||||
|
|
||||||
def get_estimated_size(self) -> int:
|
def get_estimated_size(self) -> int:
|
||||||
return self.calculate_field_id_size() + 4 # field ID + 4 bytes for float
|
return self.calculate_field_id_size() + 4 # field ID + 4 bytes for float
|
||||||
@ -339,9 +385,7 @@ class Int64Type(TypeInfo):
|
|||||||
return o
|
return o
|
||||||
|
|
||||||
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
||||||
field_id_size = self.calculate_field_id_size()
|
return self._get_simple_size_calculation(name, force, "add_int64_field")
|
||||||
o = f"ProtoSize::add_int64_field(total_size, {field_id_size}, {name}, {force_str(force)});"
|
|
||||||
return o
|
|
||||||
|
|
||||||
def get_estimated_size(self) -> int:
|
def get_estimated_size(self) -> int:
|
||||||
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
|
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
|
||||||
@ -361,9 +405,7 @@ class UInt64Type(TypeInfo):
|
|||||||
return o
|
return o
|
||||||
|
|
||||||
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
||||||
field_id_size = self.calculate_field_id_size()
|
return self._get_simple_size_calculation(name, force, "add_uint64_field")
|
||||||
o = f"ProtoSize::add_uint64_field(total_size, {field_id_size}, {name}, {force_str(force)});"
|
|
||||||
return o
|
|
||||||
|
|
||||||
def get_estimated_size(self) -> int:
|
def get_estimated_size(self) -> int:
|
||||||
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
|
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
|
||||||
@ -383,9 +425,7 @@ class Int32Type(TypeInfo):
|
|||||||
return o
|
return o
|
||||||
|
|
||||||
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
||||||
field_id_size = self.calculate_field_id_size()
|
return self._get_simple_size_calculation(name, force, "add_int32_field")
|
||||||
o = f"ProtoSize::add_int32_field(total_size, {field_id_size}, {name}, {force_str(force)});"
|
|
||||||
return o
|
|
||||||
|
|
||||||
def get_estimated_size(self) -> int:
|
def get_estimated_size(self) -> int:
|
||||||
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
|
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
|
||||||
@ -405,9 +445,10 @@ class Fixed64Type(TypeInfo):
|
|||||||
return o
|
return o
|
||||||
|
|
||||||
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
||||||
field_id_size = self.calculate_field_id_size()
|
return self._get_fixed_size_calculation(name, force, 8, "!= 0")
|
||||||
o = f"ProtoSize::add_fixed_field<8>(total_size, {field_id_size}, {name} != 0, {force_str(force)});"
|
|
||||||
return o
|
def get_fixed_size_bytes(self) -> int:
|
||||||
|
return 8
|
||||||
|
|
||||||
def get_estimated_size(self) -> int:
|
def get_estimated_size(self) -> int:
|
||||||
return self.calculate_field_id_size() + 8 # field ID + 8 bytes fixed
|
return self.calculate_field_id_size() + 8 # field ID + 8 bytes fixed
|
||||||
@ -427,9 +468,10 @@ class Fixed32Type(TypeInfo):
|
|||||||
return o
|
return o
|
||||||
|
|
||||||
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
||||||
field_id_size = self.calculate_field_id_size()
|
return self._get_fixed_size_calculation(name, force, 4, "!= 0")
|
||||||
o = f"ProtoSize::add_fixed_field<4>(total_size, {field_id_size}, {name} != 0, {force_str(force)});"
|
|
||||||
return o
|
def get_fixed_size_bytes(self) -> int:
|
||||||
|
return 4
|
||||||
|
|
||||||
def get_estimated_size(self) -> int:
|
def get_estimated_size(self) -> int:
|
||||||
return self.calculate_field_id_size() + 4 # field ID + 4 bytes fixed
|
return self.calculate_field_id_size() + 4 # field ID + 4 bytes fixed
|
||||||
@ -448,9 +490,7 @@ class BoolType(TypeInfo):
|
|||||||
return o
|
return o
|
||||||
|
|
||||||
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
||||||
field_id_size = self.calculate_field_id_size()
|
return self._get_simple_size_calculation(name, force, "add_bool_field")
|
||||||
o = f"ProtoSize::add_bool_field(total_size, {field_id_size}, {name}, {force_str(force)});"
|
|
||||||
return o
|
|
||||||
|
|
||||||
def get_estimated_size(self) -> int:
|
def get_estimated_size(self) -> int:
|
||||||
return self.calculate_field_id_size() + 1 # field ID + 1 byte
|
return self.calculate_field_id_size() + 1 # field ID + 1 byte
|
||||||
@ -471,9 +511,7 @@ class StringType(TypeInfo):
|
|||||||
return o
|
return o
|
||||||
|
|
||||||
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
||||||
field_id_size = self.calculate_field_id_size()
|
return self._get_simple_size_calculation(name, force, "add_string_field")
|
||||||
o = f"ProtoSize::add_string_field(total_size, {field_id_size}, {name}, {force_str(force)});"
|
|
||||||
return o
|
|
||||||
|
|
||||||
def get_estimated_size(self) -> int:
|
def get_estimated_size(self) -> int:
|
||||||
return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical string
|
return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical string
|
||||||
@ -498,20 +536,33 @@ class MessageType(TypeInfo):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def encode_func(self) -> str:
|
def encode_func(self) -> str:
|
||||||
return f"encode_message<{self.cpp_type}>"
|
return "encode_message"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def decode_length(self) -> str:
|
def decode_length(self) -> str:
|
||||||
return f"value.as_message<{self.cpp_type}>()"
|
# Override to return None for message types because we can't use template-based
|
||||||
|
# decoding when the specific message type isn't known at compile time.
|
||||||
|
# Instead, we use the non-template decode_to_message() method which allows
|
||||||
|
# runtime polymorphism through virtual function calls.
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def decode_length_content(self) -> str:
|
||||||
|
# Custom decode that doesn't use templates
|
||||||
|
return dedent(
|
||||||
|
f"""\
|
||||||
|
case {self.number}: {{
|
||||||
|
value.decode_to_message(this->{self.field_name});
|
||||||
|
return true;
|
||||||
|
}}"""
|
||||||
|
)
|
||||||
|
|
||||||
def dump(self, name: str) -> str:
|
def dump(self, name: str) -> str:
|
||||||
o = f"{name}.dump_to(out);"
|
o = f"{name}.dump_to(out);"
|
||||||
return o
|
return o
|
||||||
|
|
||||||
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
||||||
field_id_size = self.calculate_field_id_size()
|
return self._get_simple_size_calculation(name, force, "add_message_object")
|
||||||
o = f"ProtoSize::add_message_object(total_size, {field_id_size}, {name}, {force_str(force)});"
|
|
||||||
return o
|
|
||||||
|
|
||||||
def get_estimated_size(self) -> int:
|
def get_estimated_size(self) -> int:
|
||||||
return (
|
return (
|
||||||
@ -538,9 +589,7 @@ class BytesType(TypeInfo):
|
|||||||
return o
|
return o
|
||||||
|
|
||||||
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
||||||
field_id_size = self.calculate_field_id_size()
|
return self._get_simple_size_calculation(name, force, "add_string_field")
|
||||||
o = f"ProtoSize::add_string_field(total_size, {field_id_size}, {name}, {force_str(force)});"
|
|
||||||
return o
|
|
||||||
|
|
||||||
def get_estimated_size(self) -> int:
|
def get_estimated_size(self) -> int:
|
||||||
return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical bytes
|
return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical bytes
|
||||||
@ -560,9 +609,7 @@ class UInt32Type(TypeInfo):
|
|||||||
return o
|
return o
|
||||||
|
|
||||||
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
||||||
field_id_size = self.calculate_field_id_size()
|
return self._get_simple_size_calculation(name, force, "add_uint32_field")
|
||||||
o = f"ProtoSize::add_uint32_field(total_size, {field_id_size}, {name}, {force_str(force)});"
|
|
||||||
return o
|
|
||||||
|
|
||||||
def get_estimated_size(self) -> int:
|
def get_estimated_size(self) -> int:
|
||||||
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
|
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
|
||||||
@ -576,23 +623,27 @@ class EnumType(TypeInfo):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def decode_varint(self) -> str:
|
def decode_varint(self) -> str:
|
||||||
return f"value.as_enum<{self.cpp_type}>()"
|
return f"static_cast<{self.cpp_type}>(value.as_uint32())"
|
||||||
|
|
||||||
default_value = ""
|
default_value = ""
|
||||||
wire_type = WireType.VARINT # Uses wire type 0
|
wire_type = WireType.VARINT # Uses wire type 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def encode_func(self) -> str:
|
def encode_func(self) -> str:
|
||||||
return f"encode_enum<{self.cpp_type}>"
|
return "encode_uint32"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def encode_content(self) -> str:
|
||||||
|
return f"buffer.{self.encode_func}({self.number}, static_cast<uint32_t>(this->{self.field_name}));"
|
||||||
|
|
||||||
def dump(self, name: str) -> str:
|
def dump(self, name: str) -> str:
|
||||||
o = f"out.append(proto_enum_to_string<{self.cpp_type}>({name}));"
|
o = f"out.append(proto_enum_to_string<{self.cpp_type}>({name}));"
|
||||||
return o
|
return o
|
||||||
|
|
||||||
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
||||||
field_id_size = self.calculate_field_id_size()
|
return self._get_simple_size_calculation(
|
||||||
o = f"ProtoSize::add_enum_field(total_size, {field_id_size}, static_cast<uint32_t>({name}), {force_str(force)});"
|
name, force, "add_enum_field", f"static_cast<uint32_t>({name})"
|
||||||
return o
|
)
|
||||||
|
|
||||||
def get_estimated_size(self) -> int:
|
def get_estimated_size(self) -> int:
|
||||||
return self.calculate_field_id_size() + 1 # field ID + 1 byte typical enum
|
return self.calculate_field_id_size() + 1 # field ID + 1 byte typical enum
|
||||||
@ -612,9 +663,10 @@ class SFixed32Type(TypeInfo):
|
|||||||
return o
|
return o
|
||||||
|
|
||||||
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
||||||
field_id_size = self.calculate_field_id_size()
|
return self._get_fixed_size_calculation(name, force, 4, "!= 0")
|
||||||
o = f"ProtoSize::add_fixed_field<4>(total_size, {field_id_size}, {name} != 0, {force_str(force)});"
|
|
||||||
return o
|
def get_fixed_size_bytes(self) -> int:
|
||||||
|
return 4
|
||||||
|
|
||||||
def get_estimated_size(self) -> int:
|
def get_estimated_size(self) -> int:
|
||||||
return self.calculate_field_id_size() + 4 # field ID + 4 bytes fixed
|
return self.calculate_field_id_size() + 4 # field ID + 4 bytes fixed
|
||||||
@ -634,9 +686,10 @@ class SFixed64Type(TypeInfo):
|
|||||||
return o
|
return o
|
||||||
|
|
||||||
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
||||||
field_id_size = self.calculate_field_id_size()
|
return self._get_fixed_size_calculation(name, force, 8, "!= 0")
|
||||||
o = f"ProtoSize::add_fixed_field<8>(total_size, {field_id_size}, {name} != 0, {force_str(force)});"
|
|
||||||
return o
|
def get_fixed_size_bytes(self) -> int:
|
||||||
|
return 8
|
||||||
|
|
||||||
def get_estimated_size(self) -> int:
|
def get_estimated_size(self) -> int:
|
||||||
return self.calculate_field_id_size() + 8 # field ID + 8 bytes fixed
|
return self.calculate_field_id_size() + 8 # field ID + 8 bytes fixed
|
||||||
@ -656,9 +709,7 @@ class SInt32Type(TypeInfo):
|
|||||||
return o
|
return o
|
||||||
|
|
||||||
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
||||||
field_id_size = self.calculate_field_id_size()
|
return self._get_simple_size_calculation(name, force, "add_sint32_field")
|
||||||
o = f"ProtoSize::add_sint32_field(total_size, {field_id_size}, {name}, {force_str(force)});"
|
|
||||||
return o
|
|
||||||
|
|
||||||
def get_estimated_size(self) -> int:
|
def get_estimated_size(self) -> int:
|
||||||
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
|
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
|
||||||
@ -678,9 +729,7 @@ class SInt64Type(TypeInfo):
|
|||||||
return o
|
return o
|
||||||
|
|
||||||
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
||||||
field_id_size = self.calculate_field_id_size()
|
return self._get_simple_size_calculation(name, force, "add_sint64_field")
|
||||||
o = f"ProtoSize::add_sint64_field(total_size, {field_id_size}, {name}, {force_str(force)});"
|
|
||||||
return o
|
|
||||||
|
|
||||||
def get_estimated_size(self) -> int:
|
def get_estimated_size(self) -> int:
|
||||||
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
|
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
|
||||||
@ -727,6 +776,16 @@ class RepeatedTypeInfo(TypeInfo):
|
|||||||
@property
|
@property
|
||||||
def decode_length_content(self) -> str:
|
def decode_length_content(self) -> str:
|
||||||
content = self._ti.decode_length
|
content = self._ti.decode_length
|
||||||
|
if content is None and isinstance(self._ti, MessageType):
|
||||||
|
# Special handling for non-template message decoding
|
||||||
|
return dedent(
|
||||||
|
f"""\
|
||||||
|
case {self.number}: {{
|
||||||
|
this->{self.field_name}.emplace_back();
|
||||||
|
value.decode_to_message(this->{self.field_name}.back());
|
||||||
|
return true;
|
||||||
|
}}"""
|
||||||
|
)
|
||||||
if content is None:
|
if content is None:
|
||||||
return None
|
return None
|
||||||
return dedent(
|
return dedent(
|
||||||
@ -771,6 +830,9 @@ class RepeatedTypeInfo(TypeInfo):
|
|||||||
@property
|
@property
|
||||||
def encode_content(self) -> str:
|
def encode_content(self) -> str:
|
||||||
o = f"for (auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{\n"
|
o = f"for (auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{\n"
|
||||||
|
if isinstance(self._ti, EnumType):
|
||||||
|
o += f" buffer.{self._ti.encode_func}({self.number}, static_cast<uint32_t>(it), true);\n"
|
||||||
|
else:
|
||||||
o += f" buffer.{self._ti.encode_func}({self.number}, it, true);\n"
|
o += f" buffer.{self._ti.encode_func}({self.number}, it, true);\n"
|
||||||
o += "}"
|
o += "}"
|
||||||
return o
|
return o
|
||||||
@ -795,8 +857,20 @@ class RepeatedTypeInfo(TypeInfo):
|
|||||||
field_id_size = self._ti.calculate_field_id_size()
|
field_id_size = self._ti.calculate_field_id_size()
|
||||||
o = f"ProtoSize::add_repeated_message(total_size, {field_id_size}, {name});"
|
o = f"ProtoSize::add_repeated_message(total_size, {field_id_size}, {name});"
|
||||||
return o
|
return o
|
||||||
|
|
||||||
# For other repeated types, use the underlying type's size calculation with force=True
|
# For other repeated types, use the underlying type's size calculation with force=True
|
||||||
o = f"if (!{name}.empty()) {{\n"
|
o = f"if (!{name}.empty()) {{\n"
|
||||||
|
|
||||||
|
# Check if this is a fixed-size type by seeing if it has a fixed byte count
|
||||||
|
num_bytes = self._ti.get_fixed_size_bytes()
|
||||||
|
if num_bytes is not None:
|
||||||
|
# Fixed types have constant size per element, so we can multiply
|
||||||
|
field_id_size = self._ti.calculate_field_id_size()
|
||||||
|
# Pre-calculate the total bytes per element
|
||||||
|
bytes_per_element = field_id_size + num_bytes
|
||||||
|
o += f" total_size += {name}.size() * {bytes_per_element};\n"
|
||||||
|
else:
|
||||||
|
# Other types need the actual value
|
||||||
o += f" for (const auto {'' if self._ti_is_bool else '&'}it : {name}) {{\n"
|
o += f" for (const auto {'' if self._ti_is_bool else '&'}it : {name}) {{\n"
|
||||||
o += f" {self._ti.get_size_calculation('it', True)}\n"
|
o += f" {self._ti.get_size_calculation('it', True)}\n"
|
||||||
o += " }\n"
|
o += " }\n"
|
||||||
@ -987,13 +1061,24 @@ def build_message_type(
|
|||||||
|
|
||||||
# Add MESSAGE_TYPE method if this is a service message
|
# Add MESSAGE_TYPE method if this is a service message
|
||||||
if message_id is not None:
|
if message_id is not None:
|
||||||
|
# Validate that message_id fits in uint8_t
|
||||||
|
if message_id > 255:
|
||||||
|
raise ValueError(
|
||||||
|
f"Message ID {message_id} for {desc.name} exceeds uint8_t maximum (255)"
|
||||||
|
)
|
||||||
|
|
||||||
# Add static constexpr for message type
|
# Add static constexpr for message type
|
||||||
public_content.append(f"static constexpr uint16_t MESSAGE_TYPE = {message_id};")
|
public_content.append(f"static constexpr uint8_t MESSAGE_TYPE = {message_id};")
|
||||||
|
|
||||||
# Add estimated size constant
|
# Add estimated size constant
|
||||||
estimated_size = calculate_message_estimated_size(desc)
|
estimated_size = calculate_message_estimated_size(desc)
|
||||||
|
# Validate that estimated_size fits in uint8_t
|
||||||
|
if estimated_size > 255:
|
||||||
|
raise ValueError(
|
||||||
|
f"Estimated size {estimated_size} for {desc.name} exceeds uint8_t maximum (255)"
|
||||||
|
)
|
||||||
public_content.append(
|
public_content.append(
|
||||||
f"static constexpr uint16_t ESTIMATED_SIZE = {estimated_size};"
|
f"static constexpr uint8_t ESTIMATED_SIZE = {estimated_size};"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add message_name method inline in header
|
# Add message_name method inline in header
|
||||||
@ -1701,7 +1786,6 @@ static const char *const TAG = "api.service";
|
|||||||
exec_clang_format(root / "api_pb2_service.cpp")
|
exec_clang_format(root / "api_pb2_service.cpp")
|
||||||
exec_clang_format(root / "api_pb2.h")
|
exec_clang_format(root / "api_pb2.h")
|
||||||
exec_clang_format(root / "api_pb2.cpp")
|
exec_clang_format(root / "api_pb2.cpp")
|
||||||
exec_clang_format(root / "api_pb2_dump.h")
|
|
||||||
exec_clang_format(root / "api_pb2_dump.cpp")
|
exec_clang_format(root / "api_pb2_dump.cpp")
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
@ -270,7 +270,7 @@ def lint_newline(fname):
|
|||||||
return "File contains Windows newline. Please set your editor to Unix newline mode."
|
return "File contains Windows newline. Please set your editor to Unix newline mode."
|
||||||
|
|
||||||
|
|
||||||
@lint_content_check(exclude=["*.svg"])
|
@lint_content_check(exclude=["*.svg", ".clang-tidy.hash"])
|
||||||
def lint_end_newline(fname, content):
|
def lint_end_newline(fname, content):
|
||||||
if content and not content.endswith("\n"):
|
if content and not content.endswith("\n"):
|
||||||
return "File does not end with a newline, please add an empty line at the end of the file."
|
return "File does not end with a newline, please add an empty line at the end of the file."
|
||||||
|
@ -22,6 +22,7 @@ from helpers import (
|
|||||||
git_ls_files,
|
git_ls_files,
|
||||||
load_idedata,
|
load_idedata,
|
||||||
print_error_for_file,
|
print_error_for_file,
|
||||||
|
print_file_list,
|
||||||
root_path,
|
root_path,
|
||||||
temp_header_file,
|
temp_header_file,
|
||||||
)
|
)
|
||||||
@ -218,13 +219,14 @@ def main():
|
|||||||
)
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
idedata = load_idedata(args.environment)
|
|
||||||
options = clang_options(idedata)
|
|
||||||
|
|
||||||
files = []
|
files = []
|
||||||
for path in git_ls_files(["*.cpp"]):
|
for path in git_ls_files(["*.cpp"]):
|
||||||
files.append(os.path.relpath(path, os.getcwd()))
|
files.append(os.path.relpath(path, os.getcwd()))
|
||||||
|
|
||||||
|
# Print initial file count if it's large
|
||||||
|
if len(files) > 50:
|
||||||
|
print(f"Found {len(files)} total files to process")
|
||||||
|
|
||||||
if args.files:
|
if args.files:
|
||||||
# Match against files specified on command-line
|
# Match against files specified on command-line
|
||||||
file_name_re = re.compile("|".join(args.files))
|
file_name_re = re.compile("|".join(args.files))
|
||||||
@ -240,10 +242,28 @@ def main():
|
|||||||
|
|
||||||
if args.split_num:
|
if args.split_num:
|
||||||
files = split_list(files, args.split_num)[args.split_at - 1]
|
files = split_list(files, args.split_num)[args.split_at - 1]
|
||||||
|
print(f"Split {args.split_at}/{args.split_num}: checking {len(files)} files")
|
||||||
|
|
||||||
|
# Print file count before adding header file
|
||||||
|
print(f"\nTotal files to check: {len(files)}")
|
||||||
|
|
||||||
|
# Early exit if no files to check
|
||||||
|
if not files:
|
||||||
|
print("No files to check - exiting early")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Only build header file if we have actual files to check
|
||||||
if args.all_headers and args.split_at in (None, 1):
|
if args.all_headers and args.split_at in (None, 1):
|
||||||
build_all_include()
|
build_all_include()
|
||||||
files.insert(0, temp_header_file)
|
files.insert(0, temp_header_file)
|
||||||
|
print(f"Added all-include header file, new total: {len(files)}")
|
||||||
|
|
||||||
|
# Print final file list before loading idedata
|
||||||
|
print_file_list(files, "Final files to process:")
|
||||||
|
|
||||||
|
# Load idedata and options only if we have files to check
|
||||||
|
idedata = load_idedata(args.environment)
|
||||||
|
options = clang_options(idedata)
|
||||||
|
|
||||||
tmpdir = None
|
tmpdir = None
|
||||||
if args.fix:
|
if args.fix:
|
||||||
|
188
script/clang_tidy_hash.py
Executable file
188
script/clang_tidy_hash.py
Executable file
@ -0,0 +1,188 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Calculate and manage hash for clang-tidy configuration."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Add the script directory to path to import helpers
|
||||||
|
script_dir = Path(__file__).parent
|
||||||
|
sys.path.insert(0, str(script_dir))
|
||||||
|
|
||||||
|
|
||||||
|
def read_file_lines(path: Path) -> list[str]:
|
||||||
|
"""Read lines from a file."""
|
||||||
|
with open(path) as f:
|
||||||
|
return f.readlines()
|
||||||
|
|
||||||
|
|
||||||
|
def parse_requirement_line(line: str) -> tuple[str, str] | None:
|
||||||
|
"""Parse a requirement line and return (package, original_line) or None.
|
||||||
|
|
||||||
|
Handles formats like:
|
||||||
|
- package==1.2.3
|
||||||
|
- package==1.2.3 # comment
|
||||||
|
- package>=1.2.3,<2.0.0
|
||||||
|
"""
|
||||||
|
original_line = line.strip()
|
||||||
|
|
||||||
|
# Extract the part before any comment for parsing
|
||||||
|
parse_line = line
|
||||||
|
if "#" in parse_line:
|
||||||
|
parse_line = parse_line[: parse_line.index("#")]
|
||||||
|
|
||||||
|
parse_line = parse_line.strip()
|
||||||
|
if not parse_line:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Use regex to extract package name
|
||||||
|
# This matches package names followed by version operators
|
||||||
|
match = re.match(r"^([a-zA-Z0-9_-]+)(==|>=|<=|>|<|!=|~=)(.+)$", parse_line)
|
||||||
|
if match:
|
||||||
|
return (match.group(1), original_line) # Return package name and original line
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_clang_tidy_version_from_requirements() -> str:
|
||||||
|
"""Get clang-tidy version from requirements_dev.txt"""
|
||||||
|
requirements_path = Path(__file__).parent.parent / "requirements_dev.txt"
|
||||||
|
lines = read_file_lines(requirements_path)
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
parsed = parse_requirement_line(line)
|
||||||
|
if parsed and parsed[0] == "clang-tidy":
|
||||||
|
# Return the original line (preserves comments)
|
||||||
|
return parsed[1]
|
||||||
|
|
||||||
|
return "clang-tidy version not found"
|
||||||
|
|
||||||
|
|
||||||
|
def extract_platformio_flags() -> str:
|
||||||
|
"""Extract clang-tidy related flags from platformio.ini"""
|
||||||
|
flags: list[str] = []
|
||||||
|
in_clangtidy_section = False
|
||||||
|
|
||||||
|
platformio_path = Path(__file__).parent.parent / "platformio.ini"
|
||||||
|
lines = read_file_lines(platformio_path)
|
||||||
|
for line in lines:
|
||||||
|
line = line.strip()
|
||||||
|
if line.startswith("[flags:clangtidy]"):
|
||||||
|
in_clangtidy_section = True
|
||||||
|
continue
|
||||||
|
elif line.startswith("[") and in_clangtidy_section:
|
||||||
|
break
|
||||||
|
elif in_clangtidy_section and line and not line.startswith("#"):
|
||||||
|
flags.append(line)
|
||||||
|
|
||||||
|
return "\n".join(sorted(flags))
|
||||||
|
|
||||||
|
|
||||||
|
def read_file_bytes(path: Path) -> bytes:
|
||||||
|
"""Read bytes from a file."""
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_clang_tidy_hash() -> str:
|
||||||
|
"""Calculate hash of clang-tidy configuration and version"""
|
||||||
|
hasher = hashlib.sha256()
|
||||||
|
|
||||||
|
# Hash .clang-tidy file
|
||||||
|
clang_tidy_path = Path(__file__).parent.parent / ".clang-tidy"
|
||||||
|
content = read_file_bytes(clang_tidy_path)
|
||||||
|
hasher.update(content)
|
||||||
|
|
||||||
|
# Hash clang-tidy version from requirements_dev.txt
|
||||||
|
version = get_clang_tidy_version_from_requirements()
|
||||||
|
hasher.update(version.encode())
|
||||||
|
|
||||||
|
# Hash relevant platformio.ini sections
|
||||||
|
pio_flags = extract_platformio_flags()
|
||||||
|
hasher.update(pio_flags.encode())
|
||||||
|
|
||||||
|
return hasher.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def read_stored_hash() -> str | None:
|
||||||
|
"""Read the stored hash from file"""
|
||||||
|
hash_file = Path(__file__).parent.parent / ".clang-tidy.hash"
|
||||||
|
if hash_file.exists():
|
||||||
|
lines = read_file_lines(hash_file)
|
||||||
|
return lines[0].strip() if lines else None
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def write_file_content(path: Path, content: str) -> None:
|
||||||
|
"""Write content to a file."""
|
||||||
|
with open(path, "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
|
||||||
|
def write_hash(hash_value: str) -> None:
|
||||||
|
"""Write hash to file"""
|
||||||
|
hash_file = Path(__file__).parent.parent / ".clang-tidy.hash"
|
||||||
|
write_file_content(hash_file, hash_value)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(description="Manage clang-tidy configuration hash")
|
||||||
|
parser.add_argument(
|
||||||
|
"--check",
|
||||||
|
action="store_true",
|
||||||
|
help="Check if full scan needed (exit 0 if needed)",
|
||||||
|
)
|
||||||
|
parser.add_argument("--update", action="store_true", help="Update the hash file")
|
||||||
|
parser.add_argument(
|
||||||
|
"--update-if-changed",
|
||||||
|
action="store_true",
|
||||||
|
help="Update hash only if configuration changed (for pre-commit)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--verify", action="store_true", help="Verify hash matches (for CI)"
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
current_hash = calculate_clang_tidy_hash()
|
||||||
|
stored_hash = read_stored_hash()
|
||||||
|
|
||||||
|
if args.check:
|
||||||
|
# Exit 0 if full scan needed (hash changed or no hash file)
|
||||||
|
sys.exit(0 if current_hash != stored_hash else 1)
|
||||||
|
|
||||||
|
elif args.update:
|
||||||
|
write_hash(current_hash)
|
||||||
|
print(f"Hash updated: {current_hash}")
|
||||||
|
|
||||||
|
elif args.update_if_changed:
|
||||||
|
if current_hash != stored_hash:
|
||||||
|
write_hash(current_hash)
|
||||||
|
print(f"Clang-tidy hash updated: {current_hash}")
|
||||||
|
# Exit 0 so pre-commit can stage the file
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
print("Clang-tidy hash unchanged")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
elif args.verify:
|
||||||
|
if current_hash != stored_hash:
|
||||||
|
print("ERROR: Clang-tidy configuration has changed but hash not updated!")
|
||||||
|
print(f"Expected: {current_hash}")
|
||||||
|
print(f"Found: {stored_hash}")
|
||||||
|
print("\nPlease run: script/clang_tidy_hash.py --update")
|
||||||
|
sys.exit(1)
|
||||||
|
print("Hash verification passed")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f"Current hash: {current_hash}")
|
||||||
|
print(f"Stored hash: {stored_hash}")
|
||||||
|
print(f"Match: {current_hash == stored_hash}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
245
script/determine-jobs.py
Executable file
245
script/determine-jobs.py
Executable file
@ -0,0 +1,245 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Determine which CI jobs should run based on changed files.
|
||||||
|
|
||||||
|
This script is a centralized way to determine which CI jobs need to run based on
|
||||||
|
what files have changed. It outputs JSON with the following structure:
|
||||||
|
|
||||||
|
{
|
||||||
|
"integration_tests": true/false,
|
||||||
|
"clang_tidy": true/false,
|
||||||
|
"clang_format": true/false,
|
||||||
|
"python_linters": true/false,
|
||||||
|
"changed_components": ["component1", "component2", ...],
|
||||||
|
"component_test_count": 5
|
||||||
|
}
|
||||||
|
|
||||||
|
The CI workflow uses this information to:
|
||||||
|
- Skip or run integration tests
|
||||||
|
- Skip or run clang-tidy (and whether to do a full scan)
|
||||||
|
- Skip or run clang-format
|
||||||
|
- Skip or run Python linters (ruff, flake8, pylint, pyupgrade)
|
||||||
|
- Determine which components to test individually
|
||||||
|
- Decide how to split component tests (if there are many)
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python script/determine-jobs.py [-b BRANCH]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-b, --branch BRANCH Branch to compare against (default: dev)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from helpers import (
|
||||||
|
CPP_FILE_EXTENSIONS,
|
||||||
|
ESPHOME_COMPONENTS_PATH,
|
||||||
|
PYTHON_FILE_EXTENSIONS,
|
||||||
|
changed_files,
|
||||||
|
get_all_dependencies,
|
||||||
|
get_components_from_integration_fixtures,
|
||||||
|
parse_list_components_output,
|
||||||
|
root_path,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def should_run_integration_tests(branch: str | None = None) -> bool:
|
||||||
|
"""Determine if integration tests should run based on changed files.
|
||||||
|
|
||||||
|
This function is used by the CI workflow to intelligently skip integration tests when they're
|
||||||
|
not needed, saving significant CI time and resources.
|
||||||
|
|
||||||
|
Integration tests will run when ANY of the following conditions are met:
|
||||||
|
|
||||||
|
1. Core C++ files changed (esphome/core/*)
|
||||||
|
- Any .cpp, .h, .tcc files in the core directory
|
||||||
|
- These files contain fundamental functionality used throughout ESPHome
|
||||||
|
- Examples: esphome/core/component.cpp, esphome/core/application.h
|
||||||
|
|
||||||
|
2. Core Python files changed (esphome/core/*.py)
|
||||||
|
- Only .py files in the esphome/core/ directory
|
||||||
|
- These are core Python files that affect the entire system
|
||||||
|
- Examples: esphome/core/config.py, esphome/core/__init__.py
|
||||||
|
- NOT included: esphome/*.py, esphome/dashboard/*.py, esphome/components/*/*.py
|
||||||
|
|
||||||
|
3. Integration test files changed
|
||||||
|
- Any file in tests/integration/ directory
|
||||||
|
- This includes test files themselves and fixture YAML files
|
||||||
|
- Examples: tests/integration/test_api.py, tests/integration/fixtures/api.yaml
|
||||||
|
|
||||||
|
4. Components used by integration tests (or their dependencies) changed
|
||||||
|
- The function parses all YAML files in tests/integration/fixtures/
|
||||||
|
- Extracts which components are used in integration tests
|
||||||
|
- Recursively finds all dependencies of those components
|
||||||
|
- If any of these components have changes, tests must run
|
||||||
|
- Example: If api.yaml uses 'sensor' and 'api' components, and 'api' depends on 'socket',
|
||||||
|
then changes to sensor/, api/, or socket/ components trigger tests
|
||||||
|
|
||||||
|
Args:
|
||||||
|
branch: Branch to compare against. If None, uses default.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if integration tests should run, False otherwise.
|
||||||
|
"""
|
||||||
|
files = changed_files(branch)
|
||||||
|
|
||||||
|
# Check if any core files changed (esphome/core/*)
|
||||||
|
for file in files:
|
||||||
|
if file.startswith("esphome/core/"):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check if any integration test files changed
|
||||||
|
if any("tests/integration" in file for file in files):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Get all components used in integration tests and their dependencies
|
||||||
|
fixture_components = get_components_from_integration_fixtures()
|
||||||
|
all_required_components = get_all_dependencies(fixture_components)
|
||||||
|
|
||||||
|
# Check if any required components changed
|
||||||
|
for file in files:
|
||||||
|
if file.startswith(ESPHOME_COMPONENTS_PATH):
|
||||||
|
parts = file.split("/")
|
||||||
|
if len(parts) >= 3:
|
||||||
|
component = parts[2]
|
||||||
|
if component in all_required_components:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def should_run_clang_tidy(branch: str | None = None) -> bool:
|
||||||
|
"""Determine if clang-tidy should run based on changed files.
|
||||||
|
|
||||||
|
This function is used by the CI workflow to intelligently skip clang-tidy checks when they're
|
||||||
|
not needed, saving significant CI time and resources.
|
||||||
|
|
||||||
|
Clang-tidy will run when ANY of the following conditions are met:
|
||||||
|
|
||||||
|
1. Clang-tidy configuration changed
|
||||||
|
- The hash of .clang-tidy configuration file has changed
|
||||||
|
- The hash includes the .clang-tidy file, clang-tidy version from requirements_dev.txt,
|
||||||
|
and relevant platformio.ini sections
|
||||||
|
- When configuration changes, a full scan is needed to ensure all code complies
|
||||||
|
with the new rules
|
||||||
|
- Detected by script/clang_tidy_hash.py --check returning exit code 0
|
||||||
|
|
||||||
|
2. Any C++ source files changed
|
||||||
|
- Any file with C++ extensions: .cpp, .h, .hpp, .cc, .cxx, .c, .tcc
|
||||||
|
- Includes files anywhere in the repository, not just in esphome/
|
||||||
|
- This ensures all C++ code is checked, including tests, examples, etc.
|
||||||
|
- Examples: esphome/core/component.cpp, tests/custom/my_component.h
|
||||||
|
|
||||||
|
If the hash check fails for any reason, clang-tidy runs as a safety measure to ensure
|
||||||
|
code quality is maintained.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
branch: Branch to compare against. If None, uses default.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if clang-tidy should run, False otherwise.
|
||||||
|
"""
|
||||||
|
# First check if clang-tidy configuration changed (full scan needed)
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
[os.path.join(root_path, "script", "clang_tidy_hash.py"), "--check"],
|
||||||
|
capture_output=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
# Exit 0 means hash changed (full scan needed)
|
||||||
|
if result.returncode == 0:
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
# If hash check fails, run clang-tidy to be safe
|
||||||
|
return True
|
||||||
|
|
||||||
|
return _any_changed_file_endswith(branch, CPP_FILE_EXTENSIONS)
|
||||||
|
|
||||||
|
|
||||||
|
def should_run_clang_format(branch: str | None = None) -> bool:
|
||||||
|
"""Determine if clang-format should run based on changed files.
|
||||||
|
|
||||||
|
This function is used by the CI workflow to skip clang-format checks when no C++ files
|
||||||
|
have changed, saving CI time and resources.
|
||||||
|
|
||||||
|
Clang-format will run when any C++ source files have changed.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
branch: Branch to compare against. If None, uses default.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if clang-format should run, False otherwise.
|
||||||
|
"""
|
||||||
|
return _any_changed_file_endswith(branch, CPP_FILE_EXTENSIONS)
|
||||||
|
|
||||||
|
|
||||||
|
def should_run_python_linters(branch: str | None = None) -> bool:
|
||||||
|
"""Determine if Python linters (ruff, flake8, pylint, pyupgrade) should run based on changed files.
|
||||||
|
|
||||||
|
This function is used by the CI workflow to skip Python linting checks when no Python files
|
||||||
|
have changed, saving CI time and resources.
|
||||||
|
|
||||||
|
Python linters will run when any Python source files have changed.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
branch: Branch to compare against. If None, uses default.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if Python linters should run, False otherwise.
|
||||||
|
"""
|
||||||
|
return _any_changed_file_endswith(branch, PYTHON_FILE_EXTENSIONS)
|
||||||
|
|
||||||
|
|
||||||
|
def _any_changed_file_endswith(branch: str | None, extensions: tuple[str, ...]) -> bool:
|
||||||
|
"""Check if a changed file ends with any of the specified extensions."""
|
||||||
|
return any(file.endswith(extensions) for file in changed_files(branch))
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""Main function that determines which CI jobs to run."""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Determine which CI jobs should run based on changed files"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-b", "--branch", help="Branch to compare changed files against"
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Determine what should run
|
||||||
|
run_integration = should_run_integration_tests(args.branch)
|
||||||
|
run_clang_tidy = should_run_clang_tidy(args.branch)
|
||||||
|
run_clang_format = should_run_clang_format(args.branch)
|
||||||
|
run_python_linters = should_run_python_linters(args.branch)
|
||||||
|
|
||||||
|
# Get changed components using list-components.py for exact compatibility
|
||||||
|
script_path = Path(__file__).parent / "list-components.py"
|
||||||
|
cmd = [sys.executable, str(script_path), "--changed"]
|
||||||
|
if args.branch:
|
||||||
|
cmd.extend(["-b", args.branch])
|
||||||
|
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||||
|
changed_components = parse_list_components_output(result.stdout)
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
output: dict[str, Any] = {
|
||||||
|
"integration_tests": run_integration,
|
||||||
|
"clang_tidy": run_clang_tidy,
|
||||||
|
"clang_format": run_clang_format,
|
||||||
|
"python_linters": run_python_linters,
|
||||||
|
"changed_components": changed_components,
|
||||||
|
"component_test_count": len(changed_components),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Output as JSON
|
||||||
|
print(json.dumps(output))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@ -1,8 +1,14 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from functools import cache
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import time
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import colorama
|
import colorama
|
||||||
|
|
||||||
@ -11,14 +17,42 @@ basepath = os.path.join(root_path, "esphome")
|
|||||||
temp_folder = os.path.join(root_path, ".temp")
|
temp_folder = os.path.join(root_path, ".temp")
|
||||||
temp_header_file = os.path.join(temp_folder, "all-include.cpp")
|
temp_header_file = os.path.join(temp_folder, "all-include.cpp")
|
||||||
|
|
||||||
|
# C++ file extensions used for clang-tidy and clang-format checks
|
||||||
|
CPP_FILE_EXTENSIONS = (".cpp", ".h", ".hpp", ".cc", ".cxx", ".c", ".tcc")
|
||||||
|
|
||||||
def styled(color, msg, reset=True):
|
# Python file extensions
|
||||||
|
PYTHON_FILE_EXTENSIONS = (".py", ".pyi")
|
||||||
|
|
||||||
|
# YAML file extensions
|
||||||
|
YAML_FILE_EXTENSIONS = (".yaml", ".yml")
|
||||||
|
|
||||||
|
# Component path prefix
|
||||||
|
ESPHOME_COMPONENTS_PATH = "esphome/components/"
|
||||||
|
|
||||||
|
|
||||||
|
def parse_list_components_output(output: str) -> list[str]:
|
||||||
|
"""Parse the output from list-components.py script.
|
||||||
|
|
||||||
|
The script outputs one component name per line.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
output: The stdout from list-components.py
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of component names, or empty list if no output
|
||||||
|
"""
|
||||||
|
if not output or not output.strip():
|
||||||
|
return []
|
||||||
|
return [c.strip() for c in output.strip().split("\n") if c.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
def styled(color: str | tuple[str, ...], msg: str, reset: bool = True) -> str:
|
||||||
prefix = "".join(color) if isinstance(color, tuple) else color
|
prefix = "".join(color) if isinstance(color, tuple) else color
|
||||||
suffix = colorama.Style.RESET_ALL if reset else ""
|
suffix = colorama.Style.RESET_ALL if reset else ""
|
||||||
return prefix + msg + suffix
|
return prefix + msg + suffix
|
||||||
|
|
||||||
|
|
||||||
def print_error_for_file(file, body):
|
def print_error_for_file(file: str, body: str | None) -> None:
|
||||||
print(
|
print(
|
||||||
styled(colorama.Fore.GREEN, "### File ")
|
styled(colorama.Fore.GREEN, "### File ")
|
||||||
+ styled((colorama.Fore.GREEN, colorama.Style.BRIGHT), file)
|
+ styled((colorama.Fore.GREEN, colorama.Style.BRIGHT), file)
|
||||||
@ -29,17 +63,22 @@ def print_error_for_file(file, body):
|
|||||||
print()
|
print()
|
||||||
|
|
||||||
|
|
||||||
def build_all_include():
|
def build_all_include() -> None:
|
||||||
# Build a cpp file that includes all header files in this repo.
|
# Build a cpp file that includes all header files in this repo.
|
||||||
# Otherwise header-only integrations would not be tested by clang-tidy
|
# Otherwise header-only integrations would not be tested by clang-tidy
|
||||||
headers = []
|
|
||||||
for path in walk_files(basepath):
|
# Use git ls-files to find all .h files in the esphome directory
|
||||||
filetypes = (".h",)
|
# This is much faster than walking the filesystem
|
||||||
ext = os.path.splitext(path)[1]
|
cmd = ["git", "ls-files", "esphome/**/*.h"]
|
||||||
if ext in filetypes:
|
proc = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||||
path = os.path.relpath(path, root_path)
|
|
||||||
include_p = path.replace(os.path.sep, "/")
|
# Process git output - git already returns paths relative to repo root
|
||||||
headers.append(f'#include "{include_p}"')
|
headers = [
|
||||||
|
f'#include "{include_p}"'
|
||||||
|
for line in proc.stdout.strip().split("\n")
|
||||||
|
if (include_p := line.replace(os.path.sep, "/"))
|
||||||
|
]
|
||||||
|
|
||||||
headers.sort()
|
headers.sort()
|
||||||
headers.append("")
|
headers.append("")
|
||||||
content = "\n".join(headers)
|
content = "\n".join(headers)
|
||||||
@ -48,29 +87,87 @@ def build_all_include():
|
|||||||
p.write_text(content, encoding="utf-8")
|
p.write_text(content, encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
def walk_files(path):
|
def get_output(*args: str) -> str:
|
||||||
for root, _, files in os.walk(path):
|
|
||||||
for name in files:
|
|
||||||
yield os.path.join(root, name)
|
|
||||||
|
|
||||||
|
|
||||||
def get_output(*args):
|
|
||||||
with subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
|
with subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
|
||||||
output, _ = proc.communicate()
|
output, _ = proc.communicate()
|
||||||
return output.decode("utf-8")
|
return output.decode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
def get_err(*args):
|
def get_err(*args: str) -> str:
|
||||||
with subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
|
with subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
|
||||||
_, err = proc.communicate()
|
_, err = proc.communicate()
|
||||||
return err.decode("utf-8")
|
return err.decode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
def splitlines_no_ends(string):
|
def splitlines_no_ends(string: str) -> list[str]:
|
||||||
return [s.strip() for s in string.splitlines()]
|
return [s.strip() for s in string.splitlines()]
|
||||||
|
|
||||||
|
|
||||||
def changed_files(branch="dev"):
|
def _get_pr_number_from_github_env() -> str | None:
|
||||||
|
"""Extract PR number from GitHub environment variables.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PR number as string, or None if not found
|
||||||
|
"""
|
||||||
|
# First try parsing GITHUB_REF (fastest)
|
||||||
|
github_ref = os.environ.get("GITHUB_REF", "")
|
||||||
|
if "/pull/" in github_ref:
|
||||||
|
return github_ref.split("/pull/")[1].split("/")[0]
|
||||||
|
|
||||||
|
# Fallback to GitHub event file
|
||||||
|
github_event_path = os.environ.get("GITHUB_EVENT_PATH")
|
||||||
|
if github_event_path and os.path.exists(github_event_path):
|
||||||
|
with open(github_event_path) as f:
|
||||||
|
event_data = json.load(f)
|
||||||
|
pr_data = event_data.get("pull_request", {})
|
||||||
|
if pr_number := pr_data.get("number"):
|
||||||
|
return str(pr_number)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
|
def _get_changed_files_github_actions() -> list[str] | None:
|
||||||
|
"""Get changed files in GitHub Actions environment.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of changed files, or None if should fall back to git method
|
||||||
|
"""
|
||||||
|
event_name = os.environ.get("GITHUB_EVENT_NAME")
|
||||||
|
|
||||||
|
# For pull requests
|
||||||
|
if event_name == "pull_request":
|
||||||
|
pr_number = _get_pr_number_from_github_env()
|
||||||
|
if pr_number:
|
||||||
|
# Use GitHub CLI to get changed files directly
|
||||||
|
cmd = ["gh", "pr", "diff", pr_number, "--name-only"]
|
||||||
|
return _get_changed_files_from_command(cmd)
|
||||||
|
|
||||||
|
# For pushes (including squash-and-merge)
|
||||||
|
elif event_name == "push":
|
||||||
|
# For push events, we want to check what changed in this commit
|
||||||
|
try:
|
||||||
|
# Get the changed files in the last commit
|
||||||
|
return _get_changed_files_from_command(
|
||||||
|
["git", "diff", "HEAD~1..HEAD", "--name-only"]
|
||||||
|
)
|
||||||
|
except: # noqa: E722
|
||||||
|
# Fall back to the original method if this fails
|
||||||
|
pass
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def changed_files(branch: str | None = None) -> list[str]:
|
||||||
|
# In GitHub Actions, we can use the API to get changed files more efficiently
|
||||||
|
if os.environ.get("GITHUB_ACTIONS") == "true":
|
||||||
|
github_files = _get_changed_files_github_actions()
|
||||||
|
if github_files is not None:
|
||||||
|
return github_files
|
||||||
|
|
||||||
|
# Original implementation for local development
|
||||||
|
if not branch: # Treat None and empty string the same
|
||||||
|
branch = "dev"
|
||||||
check_remotes = ["upstream", "origin"]
|
check_remotes = ["upstream", "origin"]
|
||||||
check_remotes.extend(splitlines_no_ends(get_output("git", "remote")))
|
check_remotes.extend(splitlines_no_ends(get_output("git", "remote")))
|
||||||
for remote in check_remotes:
|
for remote in check_remotes:
|
||||||
@ -83,25 +180,165 @@ def changed_files(branch="dev"):
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise ValueError("Git not configured")
|
raise ValueError("Git not configured")
|
||||||
command = ["git", "diff", merge_base, "--name-only"]
|
return _get_changed_files_from_command(["git", "diff", merge_base, "--name-only"])
|
||||||
changed = splitlines_no_ends(get_output(*command))
|
|
||||||
changed = [os.path.relpath(f, os.getcwd()) for f in changed]
|
|
||||||
changed.sort()
|
|
||||||
return changed
|
|
||||||
|
|
||||||
|
|
||||||
def filter_changed(files):
|
def _get_changed_files_from_command(command: list[str]) -> list[str]:
|
||||||
|
"""Run a git command to get changed files and return them as a list."""
|
||||||
|
proc = subprocess.run(command, capture_output=True, text=True, check=False)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
raise Exception(f"Command failed: {' '.join(command)}\nstderr: {proc.stderr}")
|
||||||
|
|
||||||
|
changed_files = splitlines_no_ends(proc.stdout)
|
||||||
|
changed_files = [os.path.relpath(f, os.getcwd()) for f in changed_files if f]
|
||||||
|
changed_files.sort()
|
||||||
|
return changed_files
|
||||||
|
|
||||||
|
|
||||||
|
def get_changed_components() -> list[str] | None:
|
||||||
|
"""Get list of changed components using list-components.py script.
|
||||||
|
|
||||||
|
This function:
|
||||||
|
1. First checks if any core C++/header files (esphome/core/*.{cpp,h,hpp,cc,cxx,c}) changed - if so, returns None
|
||||||
|
2. Otherwise delegates to ./script/list-components.py --changed which:
|
||||||
|
- Analyzes all changed files
|
||||||
|
- Determines which components are affected (including dependencies)
|
||||||
|
- Returns a list of component names that need to be checked
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- None: Core C++/header files changed, need full scan
|
||||||
|
- Empty list: No components changed (only non-component files changed)
|
||||||
|
- List of strings: Names of components that need checking (e.g., ["wifi", "mqtt"])
|
||||||
|
"""
|
||||||
|
# Check if any core C++ or header files changed first
|
||||||
changed = changed_files()
|
changed = changed_files()
|
||||||
files = [f for f in files if f in changed]
|
core_cpp_changed = any(
|
||||||
print("Changed files:")
|
f.startswith("esphome/core/")
|
||||||
|
and f.endswith(CPP_FILE_EXTENSIONS[:-1]) # Exclude .tcc for core files
|
||||||
|
for f in changed
|
||||||
|
)
|
||||||
|
if core_cpp_changed:
|
||||||
|
print("Core C++/header files changed - will run full clang-tidy scan")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Use list-components.py to get changed components
|
||||||
|
script_path = os.path.join(root_path, "script", "list-components.py")
|
||||||
|
cmd = [script_path, "--changed"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd, capture_output=True, text=True, check=True, close_fds=False
|
||||||
|
)
|
||||||
|
return parse_list_components_output(result.stdout)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
# If the script fails, fall back to full scan
|
||||||
|
print("Could not determine changed components - will run full clang-tidy scan")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _filter_changed_ci(files: list[str]) -> list[str]:
|
||||||
|
"""Filter files based on changed components in CI environment.
|
||||||
|
|
||||||
|
This function implements intelligent filtering to reduce CI runtime by only
|
||||||
|
checking files that could be affected by the changes. It handles three scenarios:
|
||||||
|
|
||||||
|
1. Core C++/header files changed (returns None from get_changed_components):
|
||||||
|
- Triggered when any C++/header file in esphome/core/ is modified
|
||||||
|
- Action: Check ALL files (full scan)
|
||||||
|
- Reason: Core C++/header files are used throughout the codebase
|
||||||
|
|
||||||
|
2. No components changed (returns empty list from get_changed_components):
|
||||||
|
- Triggered when only non-component files changed (e.g., scripts, configs)
|
||||||
|
- Action: Check only the specific non-component files that changed
|
||||||
|
- Example: If only script/clang-tidy changed, only check that file
|
||||||
|
|
||||||
|
3. Specific components changed (returns list of component names):
|
||||||
|
- Component detection done by: ./script/list-components.py --changed
|
||||||
|
- That script analyzes which components are affected by the changed files
|
||||||
|
INCLUDING their dependencies
|
||||||
|
- Action: Check ALL files in each component that list-components.py identifies
|
||||||
|
- Example: If wifi.cpp changed, list-components.py might return ["wifi", "network"]
|
||||||
|
if network depends on wifi. We then check ALL files in both
|
||||||
|
esphome/components/wifi/ and esphome/components/network/
|
||||||
|
- Reason: Component files often have interdependencies (headers, base classes)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
files: List of all files that clang-tidy would normally check
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Filtered list of files to check
|
||||||
|
"""
|
||||||
|
components = get_changed_components()
|
||||||
|
if components is None:
|
||||||
|
# Scenario 1: Core files changed or couldn't determine components
|
||||||
|
# Action: Return all files for full scan
|
||||||
|
return files
|
||||||
|
|
||||||
|
if not components:
|
||||||
|
# Scenario 2: No components changed - only non-component files changed
|
||||||
|
# Action: Check only the specific non-component files that changed
|
||||||
|
changed = changed_files()
|
||||||
|
files = [
|
||||||
|
f
|
||||||
|
for f in files
|
||||||
|
if f in changed and not f.startswith(ESPHOME_COMPONENTS_PATH)
|
||||||
|
]
|
||||||
if not files:
|
if not files:
|
||||||
print(" No changed files!")
|
print("No files changed")
|
||||||
for c in files:
|
return files
|
||||||
print(f" {c}")
|
|
||||||
|
# Scenario 3: Specific components changed
|
||||||
|
# Action: Check ALL files in each changed component
|
||||||
|
# Convert component list to set for O(1) lookups
|
||||||
|
component_set = set(components)
|
||||||
|
print(f"Changed components: {', '.join(sorted(components))}")
|
||||||
|
|
||||||
|
# The 'files' parameter contains ALL files in the codebase that clang-tidy would check.
|
||||||
|
# We filter this down to only files in the changed components.
|
||||||
|
# We check ALL files in each changed component (not just the changed files)
|
||||||
|
# because changes in one file can affect other files in the same component.
|
||||||
|
filtered_files = []
|
||||||
|
for f in files:
|
||||||
|
if f.startswith(ESPHOME_COMPONENTS_PATH):
|
||||||
|
# Check if file belongs to any of the changed components
|
||||||
|
parts = f.split("/")
|
||||||
|
if len(parts) >= 3 and parts[2] in component_set:
|
||||||
|
filtered_files.append(f)
|
||||||
|
|
||||||
|
return filtered_files
|
||||||
|
|
||||||
|
|
||||||
|
def _filter_changed_local(files: list[str]) -> list[str]:
|
||||||
|
"""Filter files based on git changes for local development.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
files: List of all files to filter
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Filtered list of files to check
|
||||||
|
"""
|
||||||
|
# For local development, just check changed files directly
|
||||||
|
changed = changed_files()
|
||||||
|
return [f for f in files if f in changed]
|
||||||
|
|
||||||
|
|
||||||
|
def filter_changed(files: list[str]) -> list[str]:
|
||||||
|
"""Filter files to only those that changed or are in changed components.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
files: List of files to filter
|
||||||
|
"""
|
||||||
|
# When running from CI, use component-based filtering
|
||||||
|
if os.environ.get("GITHUB_ACTIONS") == "true":
|
||||||
|
files = _filter_changed_ci(files)
|
||||||
|
else:
|
||||||
|
files = _filter_changed_local(files)
|
||||||
|
|
||||||
|
print_file_list(files, "Files to check after filtering:")
|
||||||
return files
|
return files
|
||||||
|
|
||||||
|
|
||||||
def filter_grep(files, value):
|
def filter_grep(files: list[str], value: str) -> list[str]:
|
||||||
matched = []
|
matched = []
|
||||||
for file in files:
|
for file in files:
|
||||||
with open(file, encoding="utf-8") as handle:
|
with open(file, encoding="utf-8") as handle:
|
||||||
@ -111,7 +348,7 @@ def filter_grep(files, value):
|
|||||||
return matched
|
return matched
|
||||||
|
|
||||||
|
|
||||||
def git_ls_files(patterns=None):
|
def git_ls_files(patterns: list[str] | None = None) -> dict[str, int]:
|
||||||
command = ["git", "ls-files", "-s"]
|
command = ["git", "ls-files", "-s"]
|
||||||
if patterns is not None:
|
if patterns is not None:
|
||||||
command.extend(patterns)
|
command.extend(patterns)
|
||||||
@ -121,7 +358,10 @@ def git_ls_files(patterns=None):
|
|||||||
return {s[3].strip(): int(s[0]) for s in lines}
|
return {s[3].strip(): int(s[0]) for s in lines}
|
||||||
|
|
||||||
|
|
||||||
def load_idedata(environment):
|
def load_idedata(environment: str) -> dict[str, Any]:
|
||||||
|
start_time = time.time()
|
||||||
|
print(f"Loading IDE data for environment '{environment}'...")
|
||||||
|
|
||||||
platformio_ini = Path(root_path) / "platformio.ini"
|
platformio_ini = Path(root_path) / "platformio.ini"
|
||||||
temp_idedata = Path(temp_folder) / f"idedata-{environment}.json"
|
temp_idedata = Path(temp_folder) / f"idedata-{environment}.json"
|
||||||
changed = False
|
changed = False
|
||||||
@ -142,7 +382,10 @@ def load_idedata(environment):
|
|||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
if not changed:
|
if not changed:
|
||||||
return json.loads(temp_idedata.read_text())
|
data = json.loads(temp_idedata.read_text())
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
print(f"IDE data loaded from cache in {elapsed:.2f} seconds")
|
||||||
|
return data
|
||||||
|
|
||||||
# ensure temp directory exists before running pio, as it writes sdkconfig to it
|
# ensure temp directory exists before running pio, as it writes sdkconfig to it
|
||||||
Path(temp_folder).mkdir(exist_ok=True)
|
Path(temp_folder).mkdir(exist_ok=True)
|
||||||
@ -158,6 +401,9 @@ def load_idedata(environment):
|
|||||||
match = re.search(r'{\s*".*}', stdout.decode("utf-8"))
|
match = re.search(r'{\s*".*}', stdout.decode("utf-8"))
|
||||||
data = json.loads(match.group())
|
data = json.loads(match.group())
|
||||||
temp_idedata.write_text(json.dumps(data, indent=2) + "\n")
|
temp_idedata.write_text(json.dumps(data, indent=2) + "\n")
|
||||||
|
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
print(f"IDE data generated and cached in {elapsed:.2f} seconds")
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
@ -196,6 +442,29 @@ def get_binary(name: str, version: str) -> str:
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def print_file_list(
|
||||||
|
files: list[str], title: str = "Files:", max_files: int = 20
|
||||||
|
) -> None:
|
||||||
|
"""Print a list of files with optional truncation for large lists.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
files: List of file paths to print
|
||||||
|
title: Title to print before the list
|
||||||
|
max_files: Maximum number of files to show before truncating (default: 20)
|
||||||
|
"""
|
||||||
|
print(title)
|
||||||
|
if not files:
|
||||||
|
print(" No files to check!")
|
||||||
|
elif len(files) <= max_files:
|
||||||
|
for f in sorted(files):
|
||||||
|
print(f" {f}")
|
||||||
|
else:
|
||||||
|
sorted_files = sorted(files)
|
||||||
|
for f in sorted_files[:10]:
|
||||||
|
print(f" {f}")
|
||||||
|
print(f" ... and {len(files) - 10} more files")
|
||||||
|
|
||||||
|
|
||||||
def get_usable_cpu_count() -> int:
|
def get_usable_cpu_count() -> int:
|
||||||
"""Return the number of CPUs that can be used for processes.
|
"""Return the number of CPUs that can be used for processes.
|
||||||
|
|
||||||
@ -205,3 +474,83 @@ def get_usable_cpu_count() -> int:
|
|||||||
return (
|
return (
|
||||||
os.process_cpu_count() if hasattr(os, "process_cpu_count") else os.cpu_count()
|
os.process_cpu_count() if hasattr(os, "process_cpu_count") else os.cpu_count()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_dependencies(component_names: set[str]) -> set[str]:
|
||||||
|
"""Get all dependencies for a set of components.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
component_names: Set of component names to get dependencies for
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Set of all components including dependencies and auto-loaded components
|
||||||
|
"""
|
||||||
|
from esphome.const import KEY_CORE
|
||||||
|
from esphome.core import CORE
|
||||||
|
from esphome.loader import get_component
|
||||||
|
|
||||||
|
all_components: set[str] = set(component_names)
|
||||||
|
|
||||||
|
# Reset CORE to ensure clean state
|
||||||
|
CORE.reset()
|
||||||
|
|
||||||
|
# Set up fake config path for component loading
|
||||||
|
root = Path(__file__).parent.parent
|
||||||
|
CORE.config_path = str(root)
|
||||||
|
CORE.data[KEY_CORE] = {}
|
||||||
|
|
||||||
|
# Keep finding dependencies until no new ones are found
|
||||||
|
while True:
|
||||||
|
new_components: set[str] = set()
|
||||||
|
|
||||||
|
for comp_name in all_components:
|
||||||
|
comp = get_component(comp_name)
|
||||||
|
if not comp:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Add dependencies (extract component name before '.')
|
||||||
|
new_components.update(dep.split(".")[0] for dep in comp.dependencies)
|
||||||
|
|
||||||
|
# Add auto_load components
|
||||||
|
new_components.update(comp.auto_load)
|
||||||
|
|
||||||
|
# Check if we found any new components
|
||||||
|
new_components -= all_components
|
||||||
|
if not new_components:
|
||||||
|
break
|
||||||
|
|
||||||
|
all_components.update(new_components)
|
||||||
|
|
||||||
|
return all_components
|
||||||
|
|
||||||
|
|
||||||
|
def get_components_from_integration_fixtures() -> set[str]:
|
||||||
|
"""Extract all components used in integration test fixtures.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Set of component names used in integration test fixtures
|
||||||
|
"""
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
components: set[str] = set()
|
||||||
|
fixtures_dir = Path(__file__).parent.parent / "tests" / "integration" / "fixtures"
|
||||||
|
|
||||||
|
for yaml_file in fixtures_dir.glob("*.yaml"):
|
||||||
|
with open(yaml_file) as f:
|
||||||
|
config: dict[str, any] | None = yaml.safe_load(f)
|
||||||
|
if not config:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Add all top-level component keys
|
||||||
|
components.update(config.keys())
|
||||||
|
|
||||||
|
# Add platform components (e.g., output.template)
|
||||||
|
for value in config.values():
|
||||||
|
if not isinstance(value, list):
|
||||||
|
continue
|
||||||
|
|
||||||
|
for item in value:
|
||||||
|
if isinstance(item, dict) and "platform" in item:
|
||||||
|
components.add(item["platform"])
|
||||||
|
|
||||||
|
return components
|
||||||
|
@ -20,6 +20,12 @@ def filter_component_files(str):
|
|||||||
return str.startswith("esphome/components/") | str.startswith("tests/components/")
|
return str.startswith("esphome/components/") | str.startswith("tests/components/")
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_component_files() -> list[str]:
|
||||||
|
"""Get all component files from git."""
|
||||||
|
files = git_ls_files()
|
||||||
|
return list(filter(filter_component_files, files))
|
||||||
|
|
||||||
|
|
||||||
def extract_component_names_array_from_files_array(files):
|
def extract_component_names_array_from_files_array(files):
|
||||||
components = []
|
components = []
|
||||||
for file in files:
|
for file in files:
|
||||||
@ -165,17 +171,20 @@ def main():
|
|||||||
if args.branch and not args.changed:
|
if args.branch and not args.changed:
|
||||||
parser.error("--branch requires --changed")
|
parser.error("--branch requires --changed")
|
||||||
|
|
||||||
files = git_ls_files()
|
|
||||||
files = filter(filter_component_files, files)
|
|
||||||
|
|
||||||
if args.changed:
|
if args.changed:
|
||||||
if args.branch:
|
# When --changed is passed, only get the changed files
|
||||||
changed = changed_files(args.branch)
|
changed = changed_files(args.branch)
|
||||||
else:
|
|
||||||
changed = changed_files()
|
|
||||||
# If any base test file(s) changed, there's no need to filter out components
|
# If any base test file(s) changed, there's no need to filter out components
|
||||||
if not any("tests/test_build_components" in file for file in changed):
|
if any("tests/test_build_components" in file for file in changed):
|
||||||
files = [f for f in files if f in changed]
|
# Need to get all component files
|
||||||
|
files = get_all_component_files()
|
||||||
|
else:
|
||||||
|
# Only look at changed component files
|
||||||
|
files = [f for f in changed if filter_component_files(f)]
|
||||||
|
else:
|
||||||
|
# Get all component files
|
||||||
|
files = get_all_component_files()
|
||||||
|
|
||||||
for c in get_components(files, args.changed):
|
for c in get_components(files, args.changed):
|
||||||
print(c)
|
print(c)
|
||||||
|
12
tests/components/gl_r01_i2c/common.yaml
Normal file
12
tests/components/gl_r01_i2c/common.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
i2c:
|
||||||
|
- id: i2c_gl_r01_i2c
|
||||||
|
scl: ${scl_pin}
|
||||||
|
sda: ${sda_pin}
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
- platform: gl_r01_i2c
|
||||||
|
id: tof
|
||||||
|
name: "ToF sensor"
|
||||||
|
i2c_id: i2c_gl_r01_i2c
|
||||||
|
address: 0x74
|
||||||
|
update_interval: 15s
|
5
tests/components/gl_r01_i2c/test.esp32-ard.yaml
Normal file
5
tests/components/gl_r01_i2c/test.esp32-ard.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
substitutions:
|
||||||
|
scl_pin: GPIO16
|
||||||
|
sda_pin: GPIO17
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
5
tests/components/gl_r01_i2c/test.esp32-c3-ard.yaml
Normal file
5
tests/components/gl_r01_i2c/test.esp32-c3-ard.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
substitutions:
|
||||||
|
scl_pin: GPIO5
|
||||||
|
sda_pin: GPIO4
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
5
tests/components/gl_r01_i2c/test.esp32-c3-idf.yaml
Normal file
5
tests/components/gl_r01_i2c/test.esp32-c3-idf.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
substitutions:
|
||||||
|
scl_pin: GPIO5
|
||||||
|
sda_pin: GPIO4
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
5
tests/components/gl_r01_i2c/test.esp32-idf.yaml
Normal file
5
tests/components/gl_r01_i2c/test.esp32-idf.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
substitutions:
|
||||||
|
scl_pin: GPIO16
|
||||||
|
sda_pin: GPIO17
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
5
tests/components/gl_r01_i2c/test.esp8266-ard.yaml
Normal file
5
tests/components/gl_r01_i2c/test.esp8266-ard.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
substitutions:
|
||||||
|
scl_pin: GPIO5
|
||||||
|
sda_pin: GPIO4
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
5
tests/components/gl_r01_i2c/test.rp2040-ard.yaml
Normal file
5
tests/components/gl_r01_i2c/test.rp2040-ard.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
substitutions:
|
||||||
|
scl_pin: GPIO5
|
||||||
|
sda_pin: GPIO4
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
8
tests/components/lps22/common.yaml
Normal file
8
tests/components/lps22/common.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
sensor:
|
||||||
|
- platform: lps22
|
||||||
|
address: 0x5d
|
||||||
|
update_interval: 10s
|
||||||
|
temperature:
|
||||||
|
name: "LPS22 Temperature"
|
||||||
|
pressure:
|
||||||
|
name: "LPS22 Pressure"
|
6
tests/components/lps22/test.esp32-ard.yaml
Normal file
6
tests/components/lps22/test.esp32-ard.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
i2c:
|
||||||
|
- id: i2c_lps22
|
||||||
|
scl: 16
|
||||||
|
sda: 17
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
6
tests/components/lps22/test.esp32-c3-ard.yaml
Normal file
6
tests/components/lps22/test.esp32-c3-ard.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
i2c:
|
||||||
|
- id: i2c_lps22
|
||||||
|
scl: 5
|
||||||
|
sda: 4
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
6
tests/components/lps22/test.esp32-c3-idf.yaml
Normal file
6
tests/components/lps22/test.esp32-c3-idf.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
i2c:
|
||||||
|
- id: i2c_lps22
|
||||||
|
scl: 5
|
||||||
|
sda: 4
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user