mirror of
https://github.com/esphome/esphome.git
synced 2025-08-02 16:37:46 +00:00
Merge remote-tracking branch 'upstream/dev' into drop_unique_id
This commit is contained in:
commit
db8767fb05
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.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
192
.github/workflows/ci.yml
vendored
192
.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,23 +222,108 @@ 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
|
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'
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pytest -vv --cov-report=xml --tb=native -n auto tests
|
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
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:
|
||||||
|
name: Run integration tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- common
|
||||||
|
- determine-jobs
|
||||||
|
if: needs.determine-jobs.outputs.integration-tests == 'true'
|
||||||
|
steps:
|
||||||
|
- name: Check out code from GitHub
|
||||||
|
uses: actions/checkout@v4.2.2
|
||||||
|
- name: Set up Python 3.13
|
||||||
|
id: python
|
||||||
|
uses: actions/setup-python@v5.6.0
|
||||||
|
with:
|
||||||
|
python-version: "3.13"
|
||||||
|
- name: Restore Python virtual environment
|
||||||
|
id: cache-venv
|
||||||
|
uses: actions/cache@v4.2.3
|
||||||
|
with:
|
||||||
|
path: venv
|
||||||
|
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||||
|
- name: Create Python virtual environment
|
||||||
|
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||||
|
run: |
|
||||||
|
python -m venv venv
|
||||||
|
. venv/bin/activate
|
||||||
|
python --version
|
||||||
|
pip install -r requirements.txt -r requirements_test.txt
|
||||||
|
pip install -e .
|
||||||
|
- name: Register matcher
|
||||||
|
run: echo "::add-matcher::.github/workflows/matchers/pytest.json"
|
||||||
|
- name: Run integration tests
|
||||||
|
run: |
|
||||||
|
. venv/bin/activate
|
||||||
|
pytest -vv --no-cov --tb=native -n auto tests/integration/
|
||||||
|
|
||||||
clang-format:
|
clang-format:
|
||||||
name: Check clang-format
|
name: Check clang-format
|
||||||
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
|
||||||
@ -263,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
|
||||||
@ -301,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:
|
||||||
@ -312,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: |
|
||||||
@ -333,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
|
||||||
script/clang-tidy --all-headers --fix ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
|
if [ "${{ steps.check_full_scan.outputs.full_scan }}" = "true" ]; then
|
||||||
|
echo "Running FULL clang-tidy scan (hash changed)"
|
||||||
|
script/clang-tidy --all-headers --fix ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
|
||||||
|
else
|
||||||
|
echo "Running clang-tidy on changed files only"
|
||||||
|
script/clang-tidy --all-headers --fix --changed ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
|
||||||
|
fi
|
||||||
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
|
||||||
@ -346,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: |
|
||||||
@ -426,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:
|
||||||
@ -436,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:
|
||||||
@ -444,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
|
||||||
@ -494,9 +573,10 @@ jobs:
|
|||||||
- flake8
|
- flake8
|
||||||
- pylint
|
- pylint
|
||||||
- pytest
|
- pytest
|
||||||
|
- 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
|
||||||
|
@ -10,8 +10,15 @@ from esphome.components.esp32.const import (
|
|||||||
VARIANT_ESP32S2,
|
VARIANT_ESP32S2,
|
||||||
VARIANT_ESP32S3,
|
VARIANT_ESP32S3,
|
||||||
)
|
)
|
||||||
|
from esphome.config_helpers import filter_source_files_from_platform
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER, PLATFORM_ESP8266
|
from esphome.const import (
|
||||||
|
CONF_ANALOG,
|
||||||
|
CONF_INPUT,
|
||||||
|
CONF_NUMBER,
|
||||||
|
PLATFORM_ESP8266,
|
||||||
|
PlatformFramework,
|
||||||
|
)
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
@ -229,3 +236,20 @@ def validate_adc_pin(value):
|
|||||||
)(value)
|
)(value)
|
||||||
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||||
|
{
|
||||||
|
"adc_sensor_esp32.cpp": {
|
||||||
|
PlatformFramework.ESP32_ARDUINO,
|
||||||
|
PlatformFramework.ESP32_IDF,
|
||||||
|
},
|
||||||
|
"adc_sensor_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
|
||||||
|
"adc_sensor_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
|
||||||
|
"adc_sensor_libretiny.cpp": {
|
||||||
|
PlatformFramework.BK72XX_ARDUINO,
|
||||||
|
PlatformFramework.RTL87XX_ARDUINO,
|
||||||
|
PlatformFramework.LN882X_ARDUINO,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -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,35 +27,59 @@ 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:
|
||||||
cv.GenerateID(): cv.declare_id(AirthingsWavePlus),
|
"""Validate that Wave Gen2 devices don't have CO2 or TVOC sensors."""
|
||||||
cv.Optional(CONF_RADON): sensor.sensor_schema(
|
if config[CONF_DEVICE_TYPE] == "WAVE_GEN2":
|
||||||
unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
|
if CONF_CO2 in config:
|
||||||
icon=ICON_RADIOACTIVE,
|
raise cv.Invalid("Wave Gen2 devices do not support CO2 sensor")
|
||||||
accuracy_decimals=0,
|
# Check for TVOC in the base schema config
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
if CONF_TVOC in config:
|
||||||
),
|
raise cv.Invalid("Wave Gen2 devices do not support TVOC sensor")
|
||||||
cv.Optional(CONF_RADON_LONG_TERM): sensor.sensor_schema(
|
return config
|
||||||
unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
|
|
||||||
icon=ICON_RADIOACTIVE,
|
|
||||||
accuracy_decimals=0,
|
CONFIG_SCHEMA = cv.All(
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
airthings_wave_base.BASE_SCHEMA.extend(
|
||||||
),
|
{
|
||||||
cv.Optional(CONF_CO2): sensor.sensor_schema(
|
cv.GenerateID(): cv.declare_id(AirthingsWavePlus),
|
||||||
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
cv.Optional(CONF_RADON): sensor.sensor_schema(
|
||||||
accuracy_decimals=0,
|
unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
|
||||||
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
|
icon=ICON_RADIOACTIVE,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
accuracy_decimals=0,
|
||||||
),
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
|
),
|
||||||
unit_of_measurement=UNIT_LUX,
|
cv.Optional(CONF_RADON_LONG_TERM): sensor.sensor_schema(
|
||||||
accuracy_decimals=0,
|
unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
|
||||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
icon=ICON_RADIOACTIVE,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
accuracy_decimals=0,
|
||||||
),
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
}
|
),
|
||||||
|
cv.Optional(CONF_CO2): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_LUX,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_DEVICE_TYPE, default="WAVE_PLUS"): cv.enum(
|
||||||
|
DEVICE_TYPES, upper=True
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
validate_wave_gen2_config,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -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;
|
||||||
|
@ -3,6 +3,7 @@ import base64
|
|||||||
from esphome import automation
|
from esphome import automation
|
||||||
from esphome.automation import Condition
|
from esphome.automation import Condition
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
from esphome.config_helpers import get_logger_level
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ACTION,
|
CONF_ACTION,
|
||||||
@ -23,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"]
|
||||||
@ -50,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):
|
||||||
@ -114,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
|
||||||
),
|
),
|
||||||
@ -138,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 = []
|
||||||
@ -313,3 +320,25 @@ async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, arg
|
|||||||
@automation.register_condition("api.connected", APIConnectedCondition, {})
|
@automation.register_condition("api.connected", APIConnectedCondition, {})
|
||||||
async def api_connected_to_code(config, condition_id, template_arg, args):
|
async def api_connected_to_code(config, condition_id, template_arg, args):
|
||||||
return cg.new_Pvariable(condition_id, template_arg)
|
return cg.new_Pvariable(condition_id, template_arg)
|
||||||
|
|
||||||
|
|
||||||
|
def FILTER_SOURCE_FILES() -> list[str]:
|
||||||
|
"""Filter out api_pb2_dump.cpp when proto message dumping is not enabled
|
||||||
|
and user_services.cpp when no services are defined."""
|
||||||
|
files_to_filter = []
|
||||||
|
|
||||||
|
# api_pb2_dump.cpp is only needed when HAS_PROTO_MESSAGE_DUMP is defined
|
||||||
|
# This is a particularly large file that still needs to be opened and read
|
||||||
|
# all the way to the end even when ifdef'd out
|
||||||
|
#
|
||||||
|
# HAS_PROTO_MESSAGE_DUMP is defined when ESPHOME_LOG_HAS_VERY_VERBOSE is set,
|
||||||
|
# which happens when the logger level is VERY_VERBOSE
|
||||||
|
if get_logger_level() != "VERY_VERBOSE":
|
||||||
|
files_to_filter.append("api_pb2_dump.cpp")
|
||||||
|
|
||||||
|
# user_services.cpp is only needed when services are defined
|
||||||
|
config = CORE.config.get(DOMAIN, {})
|
||||||
|
if config and not config.get(CONF_ACTIONS) and not config[CONF_CUSTOM_SERVICES]:
|
||||||
|
files_to_filter.append("user_services.cpp")
|
||||||
|
|
||||||
|
return files_to_filter
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,19 @@ static const char *const TAG = "api.connection";
|
|||||||
static const int CAMERA_STOP_STREAM = 5000;
|
static const int CAMERA_STOP_STREAM = 5000;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Helper macro for entity command handlers - gets entity by key, returns if not found, and creates call object
|
||||||
|
#define ENTITY_COMMAND_MAKE_CALL(entity_type, entity_var, getter_name) \
|
||||||
|
entity_type *entity_var = App.get_##getter_name##_by_key(msg.key); \
|
||||||
|
if ((entity_var) == nullptr) \
|
||||||
|
return; \
|
||||||
|
auto call = (entity_var)->make_call();
|
||||||
|
|
||||||
|
// Helper macro for entity command handlers that don't use make_call() - gets entity by key and returns if not found
|
||||||
|
#define ENTITY_COMMAND_GET(entity_type, entity_var, getter_name) \
|
||||||
|
entity_type *entity_var = App.get_##getter_name##_by_key(msg.key); \
|
||||||
|
if ((entity_var) == nullptr) \
|
||||||
|
return;
|
||||||
|
|
||||||
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
|
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
|
||||||
: parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) {
|
: parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) {
|
||||||
#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
|
#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
|
||||||
@ -180,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);
|
||||||
@ -248,7 +262,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
|
||||||
@ -299,7 +313,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,
|
||||||
@ -325,7 +339,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) {
|
||||||
@ -355,11 +370,7 @@ uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *c
|
|||||||
return encode_message_to_buffer(msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return encode_message_to_buffer(msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
void APIConnection::cover_command(const CoverCommandRequest &msg) {
|
void APIConnection::cover_command(const CoverCommandRequest &msg) {
|
||||||
cover::Cover *cover = App.get_cover_by_key(msg.key);
|
ENTITY_COMMAND_MAKE_CALL(cover::Cover, cover, cover)
|
||||||
if (cover == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto call = cover->make_call();
|
|
||||||
if (msg.has_legacy_command) {
|
if (msg.has_legacy_command) {
|
||||||
switch (msg.legacy_command) {
|
switch (msg.legacy_command) {
|
||||||
case enums::LEGACY_COVER_COMMAND_OPEN:
|
case enums::LEGACY_COVER_COMMAND_OPEN:
|
||||||
@ -385,7 +396,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) {
|
||||||
@ -420,11 +432,7 @@ uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *con
|
|||||||
return encode_message_to_buffer(msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return encode_message_to_buffer(msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
void APIConnection::fan_command(const FanCommandRequest &msg) {
|
void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||||
fan::Fan *fan = App.get_fan_by_key(msg.key);
|
ENTITY_COMMAND_MAKE_CALL(fan::Fan, fan, fan)
|
||||||
if (fan == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto call = fan->make_call();
|
|
||||||
if (msg.has_state)
|
if (msg.has_state)
|
||||||
call.set_state(msg.state);
|
call.set_state(msg.state);
|
||||||
if (msg.has_oscillating)
|
if (msg.has_oscillating)
|
||||||
@ -443,7 +451,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) {
|
||||||
@ -496,11 +505,7 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
|
|||||||
return encode_message_to_buffer(msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return encode_message_to_buffer(msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
void APIConnection::light_command(const LightCommandRequest &msg) {
|
void APIConnection::light_command(const LightCommandRequest &msg) {
|
||||||
light::LightState *light = App.get_light_by_key(msg.key);
|
ENTITY_COMMAND_MAKE_CALL(light::LightState, light, light)
|
||||||
if (light == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto call = light->make_call();
|
|
||||||
if (msg.has_state)
|
if (msg.has_state)
|
||||||
call.set_state(msg.state);
|
call.set_state(msg.state);
|
||||||
if (msg.has_brightness)
|
if (msg.has_brightness)
|
||||||
@ -534,7 +539,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,
|
||||||
@ -563,7 +569,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,
|
||||||
@ -585,9 +592,7 @@ uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection *
|
|||||||
return encode_message_to_buffer(msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return encode_message_to_buffer(msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
void APIConnection::switch_command(const SwitchCommandRequest &msg) {
|
void APIConnection::switch_command(const SwitchCommandRequest &msg) {
|
||||||
switch_::Switch *a_switch = App.get_switch_by_key(msg.key);
|
ENTITY_COMMAND_GET(switch_::Switch, a_switch, switch)
|
||||||
if (a_switch == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (msg.state) {
|
if (msg.state) {
|
||||||
a_switch->turn_on();
|
a_switch->turn_on();
|
||||||
@ -600,7 +605,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,
|
||||||
@ -624,7 +629,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) {
|
||||||
@ -692,11 +698,7 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection
|
|||||||
return encode_message_to_buffer(msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return encode_message_to_buffer(msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
||||||
climate::Climate *climate = App.get_climate_by_key(msg.key);
|
ENTITY_COMMAND_MAKE_CALL(climate::Climate, climate, climate)
|
||||||
if (climate == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto call = climate->make_call();
|
|
||||||
if (msg.has_mode)
|
if (msg.has_mode)
|
||||||
call.set_mode(static_cast<climate::ClimateMode>(msg.mode));
|
call.set_mode(static_cast<climate::ClimateMode>(msg.mode));
|
||||||
if (msg.has_target_temperature)
|
if (msg.has_target_temperature)
|
||||||
@ -723,7 +725,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,
|
||||||
@ -750,11 +753,7 @@ uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection *
|
|||||||
return encode_message_to_buffer(msg, ListEntitiesNumberResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return encode_message_to_buffer(msg, ListEntitiesNumberResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
void APIConnection::number_command(const NumberCommandRequest &msg) {
|
void APIConnection::number_command(const NumberCommandRequest &msg) {
|
||||||
number::Number *number = App.get_number_by_key(msg.key);
|
ENTITY_COMMAND_MAKE_CALL(number::Number, number, number)
|
||||||
if (number == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto call = number->make_call();
|
|
||||||
call.set_value(msg.state);
|
call.set_value(msg.state);
|
||||||
call.perform();
|
call.perform();
|
||||||
}
|
}
|
||||||
@ -762,7 +761,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) {
|
||||||
@ -783,11 +783,7 @@ uint16_t APIConnection::try_send_date_info(EntityBase *entity, APIConnection *co
|
|||||||
return encode_message_to_buffer(msg, ListEntitiesDateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return encode_message_to_buffer(msg, ListEntitiesDateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
void APIConnection::date_command(const DateCommandRequest &msg) {
|
void APIConnection::date_command(const DateCommandRequest &msg) {
|
||||||
datetime::DateEntity *date = App.get_date_by_key(msg.key);
|
ENTITY_COMMAND_MAKE_CALL(datetime::DateEntity, date, date)
|
||||||
if (date == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto call = date->make_call();
|
|
||||||
call.set_date(msg.year, msg.month, msg.day);
|
call.set_date(msg.year, msg.month, msg.day);
|
||||||
call.perform();
|
call.perform();
|
||||||
}
|
}
|
||||||
@ -795,7 +791,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) {
|
||||||
@ -816,11 +813,7 @@ uint16_t APIConnection::try_send_time_info(EntityBase *entity, APIConnection *co
|
|||||||
return encode_message_to_buffer(msg, ListEntitiesTimeResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return encode_message_to_buffer(msg, ListEntitiesTimeResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
void APIConnection::time_command(const TimeCommandRequest &msg) {
|
void APIConnection::time_command(const TimeCommandRequest &msg) {
|
||||||
datetime::TimeEntity *time = App.get_time_by_key(msg.key);
|
ENTITY_COMMAND_MAKE_CALL(datetime::TimeEntity, time, time)
|
||||||
if (time == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto call = time->make_call();
|
|
||||||
call.set_time(msg.hour, msg.minute, msg.second);
|
call.set_time(msg.hour, msg.minute, msg.second);
|
||||||
call.perform();
|
call.perform();
|
||||||
}
|
}
|
||||||
@ -829,7 +822,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) {
|
||||||
@ -851,11 +844,7 @@ uint16_t APIConnection::try_send_datetime_info(EntityBase *entity, APIConnection
|
|||||||
return encode_message_to_buffer(msg, ListEntitiesDateTimeResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return encode_message_to_buffer(msg, ListEntitiesDateTimeResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
void APIConnection::datetime_command(const DateTimeCommandRequest &msg) {
|
void APIConnection::datetime_command(const DateTimeCommandRequest &msg) {
|
||||||
datetime::DateTimeEntity *datetime = App.get_datetime_by_key(msg.key);
|
ENTITY_COMMAND_MAKE_CALL(datetime::DateTimeEntity, datetime, datetime)
|
||||||
if (datetime == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto call = datetime->make_call();
|
|
||||||
call.set_datetime(msg.epoch_seconds);
|
call.set_datetime(msg.epoch_seconds);
|
||||||
call.perform();
|
call.perform();
|
||||||
}
|
}
|
||||||
@ -863,7 +852,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,
|
||||||
@ -888,11 +878,7 @@ uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *co
|
|||||||
return encode_message_to_buffer(msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return encode_message_to_buffer(msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
void APIConnection::text_command(const TextCommandRequest &msg) {
|
void APIConnection::text_command(const TextCommandRequest &msg) {
|
||||||
text::Text *text = App.get_text_by_key(msg.key);
|
ENTITY_COMMAND_MAKE_CALL(text::Text, text, text)
|
||||||
if (text == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto call = text->make_call();
|
|
||||||
call.set_value(msg.state);
|
call.set_value(msg.state);
|
||||||
call.perform();
|
call.perform();
|
||||||
}
|
}
|
||||||
@ -900,7 +886,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,
|
||||||
@ -923,11 +910,7 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection *
|
|||||||
return encode_message_to_buffer(msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return encode_message_to_buffer(msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
void APIConnection::select_command(const SelectCommandRequest &msg) {
|
void APIConnection::select_command(const SelectCommandRequest &msg) {
|
||||||
select::Select *select = App.get_select_by_key(msg.key);
|
ENTITY_COMMAND_MAKE_CALL(select::Select, select, select)
|
||||||
if (select == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto call = select->make_call();
|
|
||||||
call.set_option(msg.state);
|
call.set_option(msg.state);
|
||||||
call.perform();
|
call.perform();
|
||||||
}
|
}
|
||||||
@ -943,17 +926,15 @@ uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection *
|
|||||||
return encode_message_to_buffer(msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return encode_message_to_buffer(msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
void esphome::api::APIConnection::button_command(const ButtonCommandRequest &msg) {
|
void esphome::api::APIConnection::button_command(const ButtonCommandRequest &msg) {
|
||||||
button::Button *button = App.get_button_by_key(msg.key);
|
ENTITY_COMMAND_GET(button::Button, button, button)
|
||||||
if (button == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
button->press();
|
button->press();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#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,
|
||||||
@ -976,9 +957,7 @@ uint16_t APIConnection::try_send_lock_info(EntityBase *entity, APIConnection *co
|
|||||||
return encode_message_to_buffer(msg, ListEntitiesLockResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return encode_message_to_buffer(msg, ListEntitiesLockResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
void APIConnection::lock_command(const LockCommandRequest &msg) {
|
void APIConnection::lock_command(const LockCommandRequest &msg) {
|
||||||
lock::Lock *a_lock = App.get_lock_by_key(msg.key);
|
ENTITY_COMMAND_GET(lock::Lock, a_lock, lock)
|
||||||
if (a_lock == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (msg.command) {
|
switch (msg.command) {
|
||||||
case enums::LOCK_UNLOCK:
|
case enums::LOCK_UNLOCK:
|
||||||
@ -996,7 +975,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) {
|
||||||
@ -1020,11 +1000,7 @@ uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *c
|
|||||||
return encode_message_to_buffer(msg, ListEntitiesValveResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return encode_message_to_buffer(msg, ListEntitiesValveResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
void APIConnection::valve_command(const ValveCommandRequest &msg) {
|
void APIConnection::valve_command(const ValveCommandRequest &msg) {
|
||||||
valve::Valve *valve = App.get_valve_by_key(msg.key);
|
ENTITY_COMMAND_MAKE_CALL(valve::Valve, valve, valve)
|
||||||
if (valve == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto call = valve->make_call();
|
|
||||||
if (msg.has_position)
|
if (msg.has_position)
|
||||||
call.set_position(msg.position);
|
call.set_position(msg.position);
|
||||||
if (msg.stop)
|
if (msg.stop)
|
||||||
@ -1036,7 +1012,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) {
|
||||||
@ -1070,11 +1046,7 @@ uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnec
|
|||||||
return encode_message_to_buffer(msg, ListEntitiesMediaPlayerResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return encode_message_to_buffer(msg, ListEntitiesMediaPlayerResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
|
void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
|
||||||
media_player::MediaPlayer *media_player = App.get_media_player_by_key(msg.key);
|
ENTITY_COMMAND_MAKE_CALL(media_player::MediaPlayer, media_player, media_player)
|
||||||
if (media_player == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto call = media_player->make_call();
|
|
||||||
if (msg.has_command) {
|
if (msg.has_command) {
|
||||||
call.set_command(static_cast<media_player::MediaPlayerCommand>(msg.command));
|
call.set_command(static_cast<media_player::MediaPlayerCommand>(msg.command));
|
||||||
}
|
}
|
||||||
@ -1191,66 +1163,53 @@ void APIConnection::bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequ
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_VOICE_ASSISTANT
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
|
bool APIConnection::check_voice_assistant_api_connection_() const {
|
||||||
|
return voice_assistant::global_voice_assistant != nullptr &&
|
||||||
|
voice_assistant::global_voice_assistant->get_api_connection() == this;
|
||||||
|
}
|
||||||
|
|
||||||
void APIConnection::subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) {
|
void APIConnection::subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) {
|
||||||
if (voice_assistant::global_voice_assistant != nullptr) {
|
if (voice_assistant::global_voice_assistant != nullptr) {
|
||||||
voice_assistant::global_voice_assistant->client_subscription(this, msg.subscribe);
|
voice_assistant::global_voice_assistant->client_subscription(this, msg.subscribe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) {
|
void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) {
|
||||||
if (voice_assistant::global_voice_assistant != nullptr) {
|
if (!this->check_voice_assistant_api_connection_()) {
|
||||||
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.error) {
|
if (msg.error) {
|
||||||
voice_assistant::global_voice_assistant->failed_to_start();
|
voice_assistant::global_voice_assistant->failed_to_start();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (msg.port == 0) {
|
if (msg.port == 0) {
|
||||||
// Use API Audio
|
// Use API Audio
|
||||||
voice_assistant::global_voice_assistant->start_streaming();
|
voice_assistant::global_voice_assistant->start_streaming();
|
||||||
} else {
|
} else {
|
||||||
struct sockaddr_storage storage;
|
struct sockaddr_storage storage;
|
||||||
socklen_t len = sizeof(storage);
|
socklen_t len = sizeof(storage);
|
||||||
this->helper_->getpeername((struct sockaddr *) &storage, &len);
|
this->helper_->getpeername((struct sockaddr *) &storage, &len);
|
||||||
voice_assistant::global_voice_assistant->start_streaming(&storage, msg.port);
|
voice_assistant::global_voice_assistant->start_streaming(&storage, msg.port);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) {
|
void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) {
|
||||||
if (voice_assistant::global_voice_assistant != nullptr) {
|
if (this->check_voice_assistant_api_connection_()) {
|
||||||
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
voice_assistant::global_voice_assistant->on_event(msg);
|
voice_assistant::global_voice_assistant->on_event(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void APIConnection::on_voice_assistant_audio(const VoiceAssistantAudio &msg) {
|
void APIConnection::on_voice_assistant_audio(const VoiceAssistantAudio &msg) {
|
||||||
if (voice_assistant::global_voice_assistant != nullptr) {
|
if (this->check_voice_assistant_api_connection_()) {
|
||||||
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
voice_assistant::global_voice_assistant->on_audio(msg);
|
voice_assistant::global_voice_assistant->on_audio(msg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
void APIConnection::on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) {
|
void APIConnection::on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) {
|
||||||
if (voice_assistant::global_voice_assistant != nullptr) {
|
if (this->check_voice_assistant_api_connection_()) {
|
||||||
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
voice_assistant::global_voice_assistant->on_timer_event(msg);
|
voice_assistant::global_voice_assistant->on_timer_event(msg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) {
|
void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) {
|
||||||
if (voice_assistant::global_voice_assistant != nullptr) {
|
if (this->check_voice_assistant_api_connection_()) {
|
||||||
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
voice_assistant::global_voice_assistant->on_announce(msg);
|
voice_assistant::global_voice_assistant->on_announce(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1258,35 +1217,29 @@ void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnno
|
|||||||
VoiceAssistantConfigurationResponse APIConnection::voice_assistant_get_configuration(
|
VoiceAssistantConfigurationResponse APIConnection::voice_assistant_get_configuration(
|
||||||
const VoiceAssistantConfigurationRequest &msg) {
|
const VoiceAssistantConfigurationRequest &msg) {
|
||||||
VoiceAssistantConfigurationResponse resp;
|
VoiceAssistantConfigurationResponse resp;
|
||||||
if (voice_assistant::global_voice_assistant != nullptr) {
|
if (!this->check_voice_assistant_api_connection_()) {
|
||||||
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
|
return resp;
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto &config = voice_assistant::global_voice_assistant->get_configuration();
|
|
||||||
for (auto &wake_word : config.available_wake_words) {
|
|
||||||
VoiceAssistantWakeWord resp_wake_word;
|
|
||||||
resp_wake_word.id = wake_word.id;
|
|
||||||
resp_wake_word.wake_word = wake_word.wake_word;
|
|
||||||
for (const auto &lang : wake_word.trained_languages) {
|
|
||||||
resp_wake_word.trained_languages.push_back(lang);
|
|
||||||
}
|
|
||||||
resp.available_wake_words.push_back(std::move(resp_wake_word));
|
|
||||||
}
|
|
||||||
for (auto &wake_word_id : config.active_wake_words) {
|
|
||||||
resp.active_wake_words.push_back(wake_word_id);
|
|
||||||
}
|
|
||||||
resp.max_active_wake_words = config.max_active_wake_words;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto &config = voice_assistant::global_voice_assistant->get_configuration();
|
||||||
|
for (auto &wake_word : config.available_wake_words) {
|
||||||
|
VoiceAssistantWakeWord resp_wake_word;
|
||||||
|
resp_wake_word.id = wake_word.id;
|
||||||
|
resp_wake_word.wake_word = wake_word.wake_word;
|
||||||
|
for (const auto &lang : wake_word.trained_languages) {
|
||||||
|
resp_wake_word.trained_languages.push_back(lang);
|
||||||
|
}
|
||||||
|
resp.available_wake_words.push_back(std::move(resp_wake_word));
|
||||||
|
}
|
||||||
|
for (auto &wake_word_id : config.active_wake_words) {
|
||||||
|
resp.active_wake_words.push_back(wake_word_id);
|
||||||
|
}
|
||||||
|
resp.max_active_wake_words = config.max_active_wake_words;
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) {
|
void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) {
|
||||||
if (voice_assistant::global_voice_assistant != nullptr) {
|
if (this->check_voice_assistant_api_connection_()) {
|
||||||
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
voice_assistant::global_voice_assistant->on_set_configuration(msg.active_wake_words);
|
voice_assistant::global_voice_assistant->on_set_configuration(msg.active_wake_words);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1296,7 +1249,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) {
|
||||||
@ -1318,11 +1272,7 @@ uint16_t APIConnection::try_send_alarm_control_panel_info(EntityBase *entity, AP
|
|||||||
is_single);
|
is_single);
|
||||||
}
|
}
|
||||||
void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) {
|
void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) {
|
||||||
alarm_control_panel::AlarmControlPanel *a_alarm_control_panel = App.get_alarm_control_panel_by_key(msg.key);
|
ENTITY_COMMAND_MAKE_CALL(alarm_control_panel::AlarmControlPanel, a_alarm_control_panel, alarm_control_panel)
|
||||||
if (a_alarm_control_panel == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto call = a_alarm_control_panel->make_call();
|
|
||||||
switch (msg.command) {
|
switch (msg.command) {
|
||||||
case enums::ALARM_CONTROL_PANEL_DISARM:
|
case enums::ALARM_CONTROL_PANEL_DISARM:
|
||||||
call.disarm();
|
call.disarm();
|
||||||
@ -1353,7 +1303,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) {
|
||||||
@ -1377,7 +1328,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) {
|
||||||
@ -1408,9 +1360,7 @@ uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection *
|
|||||||
return encode_message_to_buffer(msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return encode_message_to_buffer(msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
void APIConnection::update_command(const UpdateCommandRequest &msg) {
|
void APIConnection::update_command(const UpdateCommandRequest &msg) {
|
||||||
update::UpdateEntity *update = App.get_update_by_key(msg.key);
|
ENTITY_COMMAND_GET(update::UpdateEntity, update, update)
|
||||||
if (update == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (msg.command) {
|
switch (msg.command) {
|
||||||
case enums::UPDATE_COMMAND_UPDATE:
|
case enums::UPDATE_COMMAND_UPDATE:
|
||||||
@ -1429,12 +1379,11 @@ void APIConnection::update_command(const UpdateCommandRequest &msg) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool APIConnection::try_send_log_message(int level, const char *tag, const char *line) {
|
bool APIConnection::try_send_log_message(int level, const char *tag, const char *line, size_t message_len) {
|
||||||
if (this->flags_.log_subscription < level)
|
if (this->flags_.log_subscription < level)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Pre-calculate message size to avoid reallocations
|
// Pre-calculate message size to avoid reallocations
|
||||||
const size_t line_length = strlen(line);
|
|
||||||
uint32_t msg_size = 0;
|
uint32_t msg_size = 0;
|
||||||
|
|
||||||
// Add size for level field (field ID 1, varint type)
|
// Add size for level field (field ID 1, varint type)
|
||||||
@ -1443,14 +1392,14 @@ bool APIConnection::try_send_log_message(int level, const char *tag, const char
|
|||||||
|
|
||||||
// Add size for string field (field ID 3, string type)
|
// Add size for string field (field ID 3, string type)
|
||||||
// 1 byte for field tag + size of length varint + string length
|
// 1 byte for field tag + size of length varint + string length
|
||||||
msg_size += 1 + api::ProtoSize::varint(static_cast<uint32_t>(line_length)) + line_length;
|
msg_size += 1 + api::ProtoSize::varint(static_cast<uint32_t>(message_len)) + message_len;
|
||||||
|
|
||||||
// Create a pre-sized buffer
|
// Create a pre-sized buffer
|
||||||
auto buffer = this->create_buffer(msg_size);
|
auto buffer = this->create_buffer(msg_size);
|
||||||
|
|
||||||
// Encode the message (SubscribeLogsResponse)
|
// Encode the message (SubscribeLogsResponse)
|
||||||
buffer.encode_uint32(1, static_cast<uint32_t>(level)); // LogLevel level = 1
|
buffer.encode_uint32(1, static_cast<uint32_t>(level)); // LogLevel level = 1
|
||||||
buffer.encode_string(3, line, line_length); // string message = 3
|
buffer.encode_string(3, line, message_len); // string message = 3
|
||||||
|
|
||||||
// SubscribeLogsResponse - 29
|
// SubscribeLogsResponse - 29
|
||||||
return this->send_buffer(buffer, SubscribeLogsResponse::MESSAGE_TYPE);
|
return this->send_buffer(buffer, SubscribeLogsResponse::MESSAGE_TYPE);
|
||||||
@ -1572,6 +1521,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()) {
|
||||||
@ -1583,6 +1533,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{};
|
||||||
@ -1626,7 +1577,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;
|
||||||
}
|
}
|
||||||
@ -1660,7 +1611,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.
|
||||||
@ -1675,12 +1627,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_() {
|
||||||
@ -1752,7 +1705,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
|
||||||
@ -1790,9 +1743,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
|
||||||
@ -1846,7 +1799,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) {
|
||||||
@ -1877,149 +1830,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);
|
||||||
@ -107,7 +107,7 @@ class APIConnection : public APIServerConnection {
|
|||||||
bool send_media_player_state(media_player::MediaPlayer *media_player);
|
bool send_media_player_state(media_player::MediaPlayer *media_player);
|
||||||
void media_player_command(const MediaPlayerCommandRequest &msg) override;
|
void media_player_command(const MediaPlayerCommandRequest &msg) override;
|
||||||
#endif
|
#endif
|
||||||
bool try_send_log_message(int level, const char *tag, const char *line);
|
bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len);
|
||||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
||||||
if (!this->flags_.service_call_subscription)
|
if (!this->flags_.service_call_subscription)
|
||||||
return;
|
return;
|
||||||
@ -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,9 +300,14 @@ 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
|
||||||
|
// Helper to check voice assistant validity and connection ownership
|
||||||
|
inline bool check_voice_assistant_api_connection_() const;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Helper method to process multiple entities from an iterator in a batch
|
// Helper method to process multiple entities from an iterator in a batch
|
||||||
template<typename Iterator> void process_iterator_batch_(Iterator &iterator) {
|
template<typename Iterator> void process_iterator_batch_(Iterator &iterator) {
|
||||||
size_t initial_size = this->deferred_batch_.size();
|
size_t initial_size = this->deferred_batch_.size();
|
||||||
@ -438,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);
|
||||||
@ -500,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;
|
||||||
@ -524,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;
|
||||||
@ -554,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() {
|
||||||
@ -625,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_();
|
||||||
@ -636,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -649,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)
|
||||||
@ -657,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
|
||||||
@ -670,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_();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "proto.h"
|
#include "proto.h"
|
||||||
#include "api_pb2_size.h"
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
|
||||||
@ -225,6 +224,22 @@ APIError APIFrameHelper::init_common_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->info_.c_str(), ##__VA_ARGS__)
|
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->info_.c_str(), ##__VA_ARGS__)
|
||||||
|
|
||||||
|
APIError APIFrameHelper::handle_socket_read_result_(ssize_t received) {
|
||||||
|
if (received == -1) {
|
||||||
|
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||||
|
return APIError::WOULD_BLOCK;
|
||||||
|
}
|
||||||
|
state_ = State::FAILED;
|
||||||
|
HELPER_LOG("Socket read failed with errno %d", errno);
|
||||||
|
return APIError::SOCKET_READ_FAILED;
|
||||||
|
} else if (received == 0) {
|
||||||
|
state_ = State::FAILED;
|
||||||
|
HELPER_LOG("Connection closed");
|
||||||
|
return APIError::CONNECTION_CLOSED;
|
||||||
|
}
|
||||||
|
return APIError::OK;
|
||||||
|
}
|
||||||
// uncomment to log raw packets
|
// uncomment to log raw packets
|
||||||
//#define HELPER_LOG_PACKETS
|
//#define HELPER_LOG_PACKETS
|
||||||
|
|
||||||
@ -327,17 +342,9 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
|||||||
// no header information yet
|
// no header information yet
|
||||||
uint8_t to_read = 3 - rx_header_buf_len_;
|
uint8_t to_read = 3 - rx_header_buf_len_;
|
||||||
ssize_t received = this->socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
|
ssize_t received = this->socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
|
||||||
if (received == -1) {
|
APIError err = handle_socket_read_result_(received);
|
||||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
if (err != APIError::OK) {
|
||||||
return APIError::WOULD_BLOCK;
|
return err;
|
||||||
}
|
|
||||||
state_ = State::FAILED;
|
|
||||||
HELPER_LOG("Socket read failed with errno %d", errno);
|
|
||||||
return APIError::SOCKET_READ_FAILED;
|
|
||||||
} else if (received == 0) {
|
|
||||||
state_ = State::FAILED;
|
|
||||||
HELPER_LOG("Connection closed");
|
|
||||||
return APIError::CONNECTION_CLOSED;
|
|
||||||
}
|
}
|
||||||
rx_header_buf_len_ += static_cast<uint8_t>(received);
|
rx_header_buf_len_ += static_cast<uint8_t>(received);
|
||||||
if (static_cast<uint8_t>(received) != to_read) {
|
if (static_cast<uint8_t>(received) != to_read) {
|
||||||
@ -372,17 +379,9 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
|||||||
// more data to read
|
// more data to read
|
||||||
uint16_t to_read = msg_size - rx_buf_len_;
|
uint16_t to_read = msg_size - rx_buf_len_;
|
||||||
ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
|
ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
|
||||||
if (received == -1) {
|
APIError err = handle_socket_read_result_(received);
|
||||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
if (err != APIError::OK) {
|
||||||
return APIError::WOULD_BLOCK;
|
return err;
|
||||||
}
|
|
||||||
state_ = State::FAILED;
|
|
||||||
HELPER_LOG("Socket read failed with errno %d", errno);
|
|
||||||
return APIError::SOCKET_READ_FAILED;
|
|
||||||
} else if (received == 0) {
|
|
||||||
state_ = State::FAILED;
|
|
||||||
HELPER_LOG("Connection closed");
|
|
||||||
return APIError::CONNECTION_CLOSED;
|
|
||||||
}
|
}
|
||||||
rx_buf_len_ += static_cast<uint16_t>(received);
|
rx_buf_len_ += static_cast<uint16_t>(received);
|
||||||
if (static_cast<uint16_t>(received) != to_read) {
|
if (static_cast<uint16_t>(received) != to_read) {
|
||||||
@ -613,7 +612,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,
|
||||||
@ -855,17 +854,9 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
|||||||
// Try to get to at least 3 bytes total (indicator + 2 varint bytes), then read one byte at a time
|
// Try to get to at least 3 bytes total (indicator + 2 varint bytes), then read one byte at a time
|
||||||
ssize_t received =
|
ssize_t received =
|
||||||
this->socket_->read(&rx_header_buf_[rx_header_buf_pos_], rx_header_buf_pos_ < 3 ? 3 - rx_header_buf_pos_ : 1);
|
this->socket_->read(&rx_header_buf_[rx_header_buf_pos_], rx_header_buf_pos_ < 3 ? 3 - rx_header_buf_pos_ : 1);
|
||||||
if (received == -1) {
|
APIError err = handle_socket_read_result_(received);
|
||||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
if (err != APIError::OK) {
|
||||||
return APIError::WOULD_BLOCK;
|
return err;
|
||||||
}
|
|
||||||
state_ = State::FAILED;
|
|
||||||
HELPER_LOG("Socket read failed with errno %d", errno);
|
|
||||||
return APIError::SOCKET_READ_FAILED;
|
|
||||||
} else if (received == 0) {
|
|
||||||
state_ = State::FAILED;
|
|
||||||
HELPER_LOG("Connection closed");
|
|
||||||
return APIError::CONNECTION_CLOSED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this was the first read, validate the indicator byte
|
// If this was the first read, validate the indicator byte
|
||||||
@ -949,17 +940,9 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
|||||||
// more data to read
|
// more data to read
|
||||||
uint16_t to_read = rx_header_parsed_len_ - rx_buf_len_;
|
uint16_t to_read = rx_header_parsed_len_ - rx_buf_len_;
|
||||||
ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
|
ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
|
||||||
if (received == -1) {
|
APIError err = handle_socket_read_result_(received);
|
||||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
if (err != APIError::OK) {
|
||||||
return APIError::WOULD_BLOCK;
|
return err;
|
||||||
}
|
|
||||||
state_ = State::FAILED;
|
|
||||||
HELPER_LOG("Socket read failed with errno %d", errno);
|
|
||||||
return APIError::SOCKET_READ_FAILED;
|
|
||||||
} else if (received == 0) {
|
|
||||||
state_ = State::FAILED;
|
|
||||||
HELPER_LOG("Connection closed");
|
|
||||||
return APIError::CONNECTION_CLOSED;
|
|
||||||
}
|
}
|
||||||
rx_buf_len_ += static_cast<uint16_t>(received);
|
rx_buf_len_ += static_cast<uint16_t>(received);
|
||||||
if (static_cast<uint16_t>(received) != to_read) {
|
if (static_cast<uint16_t>(received) != to_read) {
|
||||||
@ -1018,7 +1001,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
|
||||||
@ -176,6 +174,9 @@ class APIFrameHelper {
|
|||||||
|
|
||||||
// Common initialization for both plaintext and noise protocols
|
// Common initialization for both plaintext and noise protocols
|
||||||
APIError init_common_();
|
APIError init_common_();
|
||||||
|
|
||||||
|
// Helper method to handle socket read results
|
||||||
|
APIError handle_socket_read_result_(ssize_t received);
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
@ -194,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_; }
|
||||||
@ -248,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
File diff suppressed because it is too large
Load Diff
@ -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
|
||||||
|
@ -1,359 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "proto.h"
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace esphome {
|
|
||||||
namespace api {
|
|
||||||
|
|
||||||
class ProtoSize {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief ProtoSize class for Protocol Buffer serialization size calculation
|
|
||||||
*
|
|
||||||
* This class provides static methods to calculate the exact byte counts needed
|
|
||||||
* for encoding various Protocol Buffer field types. All methods are designed to be
|
|
||||||
* efficient for the common case where many fields have default values.
|
|
||||||
*
|
|
||||||
* Implements Protocol Buffer encoding size calculation according to:
|
|
||||||
* https://protobuf.dev/programming-guides/encoding/
|
|
||||||
*
|
|
||||||
* Key features:
|
|
||||||
* - Early-return optimization for zero/default values
|
|
||||||
* - Direct total_size updates to avoid unnecessary additions
|
|
||||||
* - Specialized handling for different field types according to protobuf spec
|
|
||||||
* - Templated helpers for repeated fields and messages
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculates the size in bytes needed to encode a uint32_t value as a varint
|
|
||||||
*
|
|
||||||
* @param value The uint32_t value to calculate size for
|
|
||||||
* @return The number of bytes needed to encode the value
|
|
||||||
*/
|
|
||||||
static inline uint32_t varint(uint32_t value) {
|
|
||||||
// Optimized varint size calculation using leading zeros
|
|
||||||
// Each 7 bits requires one byte in the varint encoding
|
|
||||||
if (value < 128)
|
|
||||||
return 1; // 7 bits, common case for small values
|
|
||||||
|
|
||||||
// For larger values, count bytes needed based on the position of the highest bit set
|
|
||||||
if (value < 16384) {
|
|
||||||
return 2; // 14 bits
|
|
||||||
} else if (value < 2097152) {
|
|
||||||
return 3; // 21 bits
|
|
||||||
} else if (value < 268435456) {
|
|
||||||
return 4; // 28 bits
|
|
||||||
} else {
|
|
||||||
return 5; // 32 bits (maximum for uint32_t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculates the size in bytes needed to encode a uint64_t value as a varint
|
|
||||||
*
|
|
||||||
* @param value The uint64_t value to calculate size for
|
|
||||||
* @return The number of bytes needed to encode the value
|
|
||||||
*/
|
|
||||||
static inline uint32_t varint(uint64_t value) {
|
|
||||||
// Handle common case of values fitting in uint32_t (vast majority of use cases)
|
|
||||||
if (value <= UINT32_MAX) {
|
|
||||||
return varint(static_cast<uint32_t>(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
// For larger values, determine size based on highest bit position
|
|
||||||
if (value < (1ULL << 35)) {
|
|
||||||
return 5; // 35 bits
|
|
||||||
} else if (value < (1ULL << 42)) {
|
|
||||||
return 6; // 42 bits
|
|
||||||
} else if (value < (1ULL << 49)) {
|
|
||||||
return 7; // 49 bits
|
|
||||||
} else if (value < (1ULL << 56)) {
|
|
||||||
return 8; // 56 bits
|
|
||||||
} else if (value < (1ULL << 63)) {
|
|
||||||
return 9; // 63 bits
|
|
||||||
} else {
|
|
||||||
return 10; // 64 bits (maximum for uint64_t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculates the size in bytes needed to encode an int32_t value as a varint
|
|
||||||
*
|
|
||||||
* Special handling is needed for negative values, which are sign-extended to 64 bits
|
|
||||||
* in Protocol Buffers, resulting in a 10-byte varint.
|
|
||||||
*
|
|
||||||
* @param value The int32_t value to calculate size for
|
|
||||||
* @return The number of bytes needed to encode the value
|
|
||||||
*/
|
|
||||||
static inline uint32_t varint(int32_t value) {
|
|
||||||
// Negative values are sign-extended to 64 bits in protocol buffers,
|
|
||||||
// which always results in a 10-byte varint for negative int32
|
|
||||||
if (value < 0) {
|
|
||||||
return 10; // Negative int32 is always 10 bytes long
|
|
||||||
}
|
|
||||||
// For non-negative values, use the uint32_t implementation
|
|
||||||
return varint(static_cast<uint32_t>(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculates the size in bytes needed to encode an int64_t value as a varint
|
|
||||||
*
|
|
||||||
* @param value The int64_t value to calculate size for
|
|
||||||
* @return The number of bytes needed to encode the value
|
|
||||||
*/
|
|
||||||
static inline uint32_t varint(int64_t value) {
|
|
||||||
// For int64_t, we convert to uint64_t and calculate the size
|
|
||||||
// This works because the bit pattern determines the encoding size,
|
|
||||||
// and we've handled negative int32 values as a special case above
|
|
||||||
return varint(static_cast<uint64_t>(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculates the size in bytes needed to encode a field ID and wire type
|
|
||||||
*
|
|
||||||
* @param field_id The field identifier
|
|
||||||
* @param type The wire type value (from the WireType enum in the protobuf spec)
|
|
||||||
* @return The number of bytes needed to encode the field ID and wire type
|
|
||||||
*/
|
|
||||||
static inline uint32_t field(uint32_t field_id, uint32_t type) {
|
|
||||||
uint32_t tag = (field_id << 3) | (type & 0b111);
|
|
||||||
return varint(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Common parameters for all add_*_field methods
|
|
||||||
*
|
|
||||||
* All add_*_field methods follow these common patterns:
|
|
||||||
*
|
|
||||||
* @param total_size Reference to the total message size to update
|
|
||||||
* @param field_id_size Pre-calculated size of the field ID in bytes
|
|
||||||
* @param value The value to calculate size for (type varies)
|
|
||||||
* @param force Whether to calculate size even if the value is default/zero/empty
|
|
||||||
*
|
|
||||||
* Each method follows this implementation pattern:
|
|
||||||
* 1. Skip calculation if value is default (0, false, empty) and not forced
|
|
||||||
* 2. Calculate the size based on the field's encoding rules
|
|
||||||
* 3. Add the field_id_size + calculated value size to total_size
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculates and adds the size of an int32 field to the total message size
|
|
||||||
*/
|
|
||||||
static inline void add_int32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value, bool force = false) {
|
|
||||||
// Skip calculation if value is zero and not forced
|
|
||||||
if (value == 0 && !force) {
|
|
||||||
return; // No need to update total_size
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate and directly add to total_size
|
|
||||||
if (value < 0) {
|
|
||||||
// Negative values are encoded as 10-byte varints in protobuf
|
|
||||||
total_size += field_id_size + 10;
|
|
||||||
} else {
|
|
||||||
// For non-negative values, use the standard varint size
|
|
||||||
total_size += field_id_size + varint(static_cast<uint32_t>(value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculates and adds the size of 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,
|
|
||||||
bool force = false) {
|
|
||||||
// Skip calculation if value is zero and not forced
|
|
||||||
if (value == 0 && !force) {
|
|
||||||
return; // No need to update total_size
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate and directly add to total_size
|
|
||||||
total_size += field_id_size + varint(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculates and adds the size of a 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) {
|
|
||||||
// Skip calculation if value is false and not forced
|
|
||||||
if (!value && !force) {
|
|
||||||
return; // No need to update total_size
|
|
||||||
}
|
|
||||||
|
|
||||||
// Boolean fields always use 1 byte when true
|
|
||||||
total_size += field_id_size + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculates and adds the size of a fixed field to the total message size
|
|
||||||
*
|
|
||||||
* Fixed fields always take exactly N bytes (4 for fixed32/float, 8 for fixed64/double).
|
|
||||||
*
|
|
||||||
* @tparam NumBytes The number of bytes for this fixed field (4 or 8)
|
|
||||||
* @param is_nonzero Whether the value is non-zero
|
|
||||||
*/
|
|
||||||
template<uint32_t NumBytes>
|
|
||||||
static inline void add_fixed_field(uint32_t &total_size, uint32_t field_id_size, bool is_nonzero,
|
|
||||||
bool force = false) {
|
|
||||||
// Skip calculation if value is zero and not forced
|
|
||||||
if (!is_nonzero && !force) {
|
|
||||||
return; // No need to update total_size
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fixed fields always take exactly NumBytes
|
|
||||||
total_size += field_id_size + NumBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculates and adds the size of an enum field to the total message size
|
|
||||||
*
|
|
||||||
* Enum fields are encoded as uint32 varints.
|
|
||||||
*/
|
|
||||||
static inline void add_enum_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value, bool force = false) {
|
|
||||||
// Skip calculation if value is zero and not forced
|
|
||||||
if (value == 0 && !force) {
|
|
||||||
return; // No need to update total_size
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enums are encoded as uint32
|
|
||||||
total_size += field_id_size + varint(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculates and adds the size of a sint32 field to the total message size
|
|
||||||
*
|
|
||||||
* Sint32 fields use ZigZag encoding, which is more efficient for negative values.
|
|
||||||
*/
|
|
||||||
static inline void add_sint32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value, bool force = false) {
|
|
||||||
// Skip calculation if value is zero and not forced
|
|
||||||
if (value == 0 && !force) {
|
|
||||||
return; // No need to update total_size
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZigZag encoding for sint32: (n << 1) ^ (n >> 31)
|
|
||||||
uint32_t zigzag = (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
|
|
||||||
total_size += field_id_size + varint(zigzag);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculates and adds the size of 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) {
|
|
||||||
// Skip calculation if value is zero and not forced
|
|
||||||
if (value == 0 && !force) {
|
|
||||||
return; // No need to update total_size
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate and directly add to total_size
|
|
||||||
total_size += field_id_size + varint(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculates and adds the size of a uint64 field to the total message size
|
|
||||||
*/
|
|
||||||
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 and not forced
|
|
||||||
if (value == 0 && !force) {
|
|
||||||
return; // No need to update total_size
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate and directly add to total_size
|
|
||||||
total_size += field_id_size + varint(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculates and adds the size of a sint64 field to the total message size
|
|
||||||
*
|
|
||||||
* Sint64 fields use ZigZag encoding, which is more efficient for negative values.
|
|
||||||
*/
|
|
||||||
static inline void add_sint64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value, bool force = false) {
|
|
||||||
// Skip calculation if value is zero and not forced
|
|
||||||
if (value == 0 && !force) {
|
|
||||||
return; // No need to update total_size
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZigZag encoding for sint64: (n << 1) ^ (n >> 63)
|
|
||||||
uint64_t zigzag = (static_cast<uint64_t>(value) << 1) ^ (static_cast<uint64_t>(value >> 63));
|
|
||||||
total_size += field_id_size + varint(zigzag);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculates and adds the size of a 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,
|
|
||||||
bool force = false) {
|
|
||||||
// Skip calculation if string is empty and not forced
|
|
||||||
if (str.empty() && !force) {
|
|
||||||
return; // No need to update total_size
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate and directly add to total_size
|
|
||||||
const uint32_t str_size = static_cast<uint32_t>(str.size());
|
|
||||||
total_size += field_id_size + varint(str_size) + str_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculates and adds the size of a nested message field to the total message size
|
|
||||||
*
|
|
||||||
* This helper function directly updates the total_size reference if the nested size
|
|
||||||
* is greater than zero or force is true.
|
|
||||||
*
|
|
||||||
* @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,
|
|
||||||
bool force = false) {
|
|
||||||
// Skip calculation if nested message is empty and not forced
|
|
||||||
if (nested_size == 0 && !force) {
|
|
||||||
return; // No need to update total_size
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate and directly add to total_size
|
|
||||||
// Field ID + length varint + nested message content
|
|
||||||
total_size += field_id_size + varint(nested_size) + nested_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculates and adds the size of a nested message field to the total message size
|
|
||||||
*
|
|
||||||
* This version takes a ProtoMessage object, calculates its size internally,
|
|
||||||
* and updates the total_size reference. This eliminates the need for a temporary variable
|
|
||||||
* at the call site.
|
|
||||||
*
|
|
||||||
* @param message The nested message object
|
|
||||||
*/
|
|
||||||
static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const ProtoMessage &message,
|
|
||||||
bool force = false) {
|
|
||||||
uint32_t nested_size = 0;
|
|
||||||
message.calculate_size(nested_size);
|
|
||||||
|
|
||||||
// Use the base implementation with the calculated nested_size
|
|
||||||
add_message_field(total_size, field_id_size, nested_size, force);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculates and adds the sizes of all messages in a repeated field to the total message size
|
|
||||||
*
|
|
||||||
* This helper processes a vector of message objects, calculating the size for each message
|
|
||||||
* and adding it to the total size.
|
|
||||||
*
|
|
||||||
* @tparam MessageType The type of the nested messages in the vector
|
|
||||||
* @param messages Vector of message objects
|
|
||||||
*/
|
|
||||||
template<typename MessageType>
|
|
||||||
static inline void add_repeated_message(uint32_t &total_size, uint32_t field_id_size,
|
|
||||||
const std::vector<MessageType> &messages) {
|
|
||||||
// Skip if the vector is empty
|
|
||||||
if (messages.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For repeated fields, always use force=true
|
|
||||||
for (const auto &message : messages) {
|
|
||||||
add_message_object(total_size, field_id_size, message, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace api
|
|
||||||
} // namespace esphome
|
|
@ -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
|
||||||
@ -104,18 +96,19 @@ void APIServer::setup() {
|
|||||||
|
|
||||||
#ifdef USE_LOGGER
|
#ifdef USE_LOGGER
|
||||||
if (logger::global_logger != nullptr) {
|
if (logger::global_logger != nullptr) {
|
||||||
logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) {
|
logger::global_logger->add_on_log_callback(
|
||||||
if (this->shutting_down_) {
|
[this](int level, const char *tag, const char *message, size_t message_len) {
|
||||||
// Don't try to send logs during shutdown
|
if (this->shutting_down_) {
|
||||||
// as it could result in a recursion and
|
// Don't try to send logs during shutdown
|
||||||
// we would be filling a buffer we are trying to clear
|
// as it could result in a recursion and
|
||||||
return;
|
// we would be filling a buffer we are trying to clear
|
||||||
}
|
return;
|
||||||
for (auto &c : this->clients_) {
|
}
|
||||||
if (!c->flags_.remove)
|
for (auto &c : this->clients_) {
|
||||||
c->try_send_log_message(level, tag, message);
|
if (!c->flags_.remove)
|
||||||
}
|
c->try_send_log_message(level, tag, message, message_len);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -260,180 +253,114 @@ bool APIServer::check_password(const std::string &password) const {
|
|||||||
|
|
||||||
void APIServer::handle_disconnect(APIConnection *conn) {}
|
void APIServer::handle_disconnect(APIConnection *conn) {}
|
||||||
|
|
||||||
|
// Macro for entities without extra parameters
|
||||||
|
#define API_DISPATCH_UPDATE(entity_type, entity_name) \
|
||||||
|
void APIServer::on_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \
|
||||||
|
if (obj->is_internal()) \
|
||||||
|
return; \
|
||||||
|
for (auto &c : this->clients_) \
|
||||||
|
c->send_##entity_name##_state(obj); \
|
||||||
|
}
|
||||||
|
|
||||||
|
// Macro for entities with extra parameters (but parameters not used in send)
|
||||||
|
#define API_DISPATCH_UPDATE_IGNORE_PARAMS(entity_type, entity_name, ...) \
|
||||||
|
void APIServer::on_##entity_name##_update(entity_type *obj, __VA_ARGS__) { /* NOLINT(bugprone-macro-parentheses) */ \
|
||||||
|
if (obj->is_internal()) \
|
||||||
|
return; \
|
||||||
|
for (auto &c : this->clients_) \
|
||||||
|
c->send_##entity_name##_state(obj); \
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj) {
|
API_DISPATCH_UPDATE(binary_sensor::BinarySensor, binary_sensor)
|
||||||
if (obj->is_internal())
|
|
||||||
return;
|
|
||||||
for (auto &c : this->clients_)
|
|
||||||
c->send_binary_sensor_state(obj);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_COVER
|
#ifdef USE_COVER
|
||||||
void APIServer::on_cover_update(cover::Cover *obj) {
|
API_DISPATCH_UPDATE(cover::Cover, cover)
|
||||||
if (obj->is_internal())
|
|
||||||
return;
|
|
||||||
for (auto &c : this->clients_)
|
|
||||||
c->send_cover_state(obj);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_FAN
|
#ifdef USE_FAN
|
||||||
void APIServer::on_fan_update(fan::Fan *obj) {
|
API_DISPATCH_UPDATE(fan::Fan, fan)
|
||||||
if (obj->is_internal())
|
|
||||||
return;
|
|
||||||
for (auto &c : this->clients_)
|
|
||||||
c->send_fan_state(obj);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_LIGHT
|
#ifdef USE_LIGHT
|
||||||
void APIServer::on_light_update(light::LightState *obj) {
|
API_DISPATCH_UPDATE(light::LightState, light)
|
||||||
if (obj->is_internal())
|
|
||||||
return;
|
|
||||||
for (auto &c : this->clients_)
|
|
||||||
c->send_light_state(obj);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
void APIServer::on_sensor_update(sensor::Sensor *obj, float state) {
|
API_DISPATCH_UPDATE_IGNORE_PARAMS(sensor::Sensor, sensor, float state)
|
||||||
if (obj->is_internal())
|
|
||||||
return;
|
|
||||||
for (auto &c : this->clients_)
|
|
||||||
c->send_sensor_state(obj);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_SWITCH
|
#ifdef USE_SWITCH
|
||||||
void APIServer::on_switch_update(switch_::Switch *obj, bool state) {
|
API_DISPATCH_UPDATE_IGNORE_PARAMS(switch_::Switch, switch, bool state)
|
||||||
if (obj->is_internal())
|
|
||||||
return;
|
|
||||||
for (auto &c : this->clients_)
|
|
||||||
c->send_switch_state(obj);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) {
|
API_DISPATCH_UPDATE_IGNORE_PARAMS(text_sensor::TextSensor, text_sensor, const std::string &state)
|
||||||
if (obj->is_internal())
|
|
||||||
return;
|
|
||||||
for (auto &c : this->clients_)
|
|
||||||
c->send_text_sensor_state(obj);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_CLIMATE
|
#ifdef USE_CLIMATE
|
||||||
void APIServer::on_climate_update(climate::Climate *obj) {
|
API_DISPATCH_UPDATE(climate::Climate, climate)
|
||||||
if (obj->is_internal())
|
|
||||||
return;
|
|
||||||
for (auto &c : this->clients_)
|
|
||||||
c->send_climate_state(obj);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_NUMBER
|
#ifdef USE_NUMBER
|
||||||
void APIServer::on_number_update(number::Number *obj, float state) {
|
API_DISPATCH_UPDATE_IGNORE_PARAMS(number::Number, number, float state)
|
||||||
if (obj->is_internal())
|
|
||||||
return;
|
|
||||||
for (auto &c : this->clients_)
|
|
||||||
c->send_number_state(obj);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_DATETIME_DATE
|
#ifdef USE_DATETIME_DATE
|
||||||
void APIServer::on_date_update(datetime::DateEntity *obj) {
|
API_DISPATCH_UPDATE(datetime::DateEntity, date)
|
||||||
if (obj->is_internal())
|
|
||||||
return;
|
|
||||||
for (auto &c : this->clients_)
|
|
||||||
c->send_date_state(obj);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_DATETIME_TIME
|
#ifdef USE_DATETIME_TIME
|
||||||
void APIServer::on_time_update(datetime::TimeEntity *obj) {
|
API_DISPATCH_UPDATE(datetime::TimeEntity, time)
|
||||||
if (obj->is_internal())
|
|
||||||
return;
|
|
||||||
for (auto &c : this->clients_)
|
|
||||||
c->send_time_state(obj);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_DATETIME_DATETIME
|
#ifdef USE_DATETIME_DATETIME
|
||||||
void APIServer::on_datetime_update(datetime::DateTimeEntity *obj) {
|
API_DISPATCH_UPDATE(datetime::DateTimeEntity, datetime)
|
||||||
if (obj->is_internal())
|
|
||||||
return;
|
|
||||||
for (auto &c : this->clients_)
|
|
||||||
c->send_datetime_state(obj);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
void APIServer::on_text_update(text::Text *obj, const std::string &state) {
|
API_DISPATCH_UPDATE_IGNORE_PARAMS(text::Text, text, const std::string &state)
|
||||||
if (obj->is_internal())
|
|
||||||
return;
|
|
||||||
for (auto &c : this->clients_)
|
|
||||||
c->send_text_state(obj);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_SELECT
|
#ifdef USE_SELECT
|
||||||
void APIServer::on_select_update(select::Select *obj, const std::string &state, size_t index) {
|
API_DISPATCH_UPDATE_IGNORE_PARAMS(select::Select, select, const std::string &state, size_t index)
|
||||||
if (obj->is_internal())
|
|
||||||
return;
|
|
||||||
for (auto &c : this->clients_)
|
|
||||||
c->send_select_state(obj);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_LOCK
|
#ifdef USE_LOCK
|
||||||
void APIServer::on_lock_update(lock::Lock *obj) {
|
API_DISPATCH_UPDATE(lock::Lock, lock)
|
||||||
if (obj->is_internal())
|
|
||||||
return;
|
|
||||||
for (auto &c : this->clients_)
|
|
||||||
c->send_lock_state(obj);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_VALVE
|
#ifdef USE_VALVE
|
||||||
void APIServer::on_valve_update(valve::Valve *obj) {
|
API_DISPATCH_UPDATE(valve::Valve, valve)
|
||||||
if (obj->is_internal())
|
|
||||||
return;
|
|
||||||
for (auto &c : this->clients_)
|
|
||||||
c->send_valve_state(obj);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_MEDIA_PLAYER
|
#ifdef USE_MEDIA_PLAYER
|
||||||
void APIServer::on_media_player_update(media_player::MediaPlayer *obj) {
|
API_DISPATCH_UPDATE(media_player::MediaPlayer, media_player)
|
||||||
if (obj->is_internal())
|
|
||||||
return;
|
|
||||||
for (auto &c : this->clients_)
|
|
||||||
c->send_media_player_state(obj);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
|
// Event is a special case - it's the only entity that passes extra parameters to the send method
|
||||||
void APIServer::on_event(event::Event *obj, const std::string &event_type) {
|
void APIServer::on_event(event::Event *obj, const std::string &event_type) {
|
||||||
|
if (obj->is_internal())
|
||||||
|
return;
|
||||||
for (auto &c : this->clients_)
|
for (auto &c : this->clients_)
|
||||||
c->send_event(obj, event_type);
|
c->send_event(obj, event_type);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_UPDATE
|
#ifdef USE_UPDATE
|
||||||
|
// Update is a special case - the method is called on_update, not on_update_update
|
||||||
void APIServer::on_update(update::UpdateEntity *obj) {
|
void APIServer::on_update(update::UpdateEntity *obj) {
|
||||||
|
if (obj->is_internal())
|
||||||
|
return;
|
||||||
for (auto &c : this->clients_)
|
for (auto &c : this->clients_)
|
||||||
c->send_update_state(obj);
|
c->send_update_state(obj);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_ALARM_CONTROL_PANEL
|
#ifdef USE_ALARM_CONTROL_PANEL
|
||||||
void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) {
|
API_DISPATCH_UPDATE(alarm_control_panel::AlarmControlPanel, alarm_control_panel)
|
||||||
if (obj->is_internal())
|
|
||||||
return;
|
|
||||||
for (auto &c : this->clients_)
|
|
||||||
c->send_alarm_control_panel_state(obj);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
||||||
@ -540,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
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
||||||
@ -59,7 +60,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 +133,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 +272,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 +312,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 +340,494 @@ 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; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ProtoSize {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief ProtoSize class for Protocol Buffer serialization size calculation
|
||||||
|
*
|
||||||
|
* This class provides static methods to calculate the exact byte counts needed
|
||||||
|
* for encoding various Protocol Buffer field types. All methods are designed to be
|
||||||
|
* efficient for the common case where many fields have default values.
|
||||||
|
*
|
||||||
|
* Implements Protocol Buffer encoding size calculation according to:
|
||||||
|
* https://protobuf.dev/programming-guides/encoding/
|
||||||
|
*
|
||||||
|
* Key features:
|
||||||
|
* - Early-return optimization for zero/default values
|
||||||
|
* - Direct total_size updates to avoid unnecessary additions
|
||||||
|
* - Specialized handling for different field types according to protobuf spec
|
||||||
|
* - Templated helpers for repeated fields and messages
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates the size in bytes needed to encode a uint32_t value as a varint
|
||||||
|
*
|
||||||
|
* @param value The uint32_t value to calculate size for
|
||||||
|
* @return The number of bytes needed to encode the value
|
||||||
|
*/
|
||||||
|
static inline uint32_t varint(uint32_t value) {
|
||||||
|
// Optimized varint size calculation using leading zeros
|
||||||
|
// Each 7 bits requires one byte in the varint encoding
|
||||||
|
if (value < 128)
|
||||||
|
return 1; // 7 bits, common case for small values
|
||||||
|
|
||||||
|
// For larger values, count bytes needed based on the position of the highest bit set
|
||||||
|
if (value < 16384) {
|
||||||
|
return 2; // 14 bits
|
||||||
|
} else if (value < 2097152) {
|
||||||
|
return 3; // 21 bits
|
||||||
|
} else if (value < 268435456) {
|
||||||
|
return 4; // 28 bits
|
||||||
|
} else {
|
||||||
|
return 5; // 32 bits (maximum for uint32_t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates the size in bytes needed to encode a uint64_t value as a varint
|
||||||
|
*
|
||||||
|
* @param value The uint64_t value to calculate size for
|
||||||
|
* @return The number of bytes needed to encode the value
|
||||||
|
*/
|
||||||
|
static inline uint32_t varint(uint64_t value) {
|
||||||
|
// Handle common case of values fitting in uint32_t (vast majority of use cases)
|
||||||
|
if (value <= UINT32_MAX) {
|
||||||
|
return varint(static_cast<uint32_t>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
// For larger values, determine size based on highest bit position
|
||||||
|
if (value < (1ULL << 35)) {
|
||||||
|
return 5; // 35 bits
|
||||||
|
} else if (value < (1ULL << 42)) {
|
||||||
|
return 6; // 42 bits
|
||||||
|
} else if (value < (1ULL << 49)) {
|
||||||
|
return 7; // 49 bits
|
||||||
|
} else if (value < (1ULL << 56)) {
|
||||||
|
return 8; // 56 bits
|
||||||
|
} else if (value < (1ULL << 63)) {
|
||||||
|
return 9; // 63 bits
|
||||||
|
} else {
|
||||||
|
return 10; // 64 bits (maximum for uint64_t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates the size in bytes needed to encode an int32_t value as a varint
|
||||||
|
*
|
||||||
|
* Special handling is needed for negative values, which are sign-extended to 64 bits
|
||||||
|
* in Protocol Buffers, resulting in a 10-byte varint.
|
||||||
|
*
|
||||||
|
* @param value The int32_t value to calculate size for
|
||||||
|
* @return The number of bytes needed to encode the value
|
||||||
|
*/
|
||||||
|
static inline uint32_t varint(int32_t value) {
|
||||||
|
// Negative values are sign-extended to 64 bits in protocol buffers,
|
||||||
|
// which always results in a 10-byte varint for negative int32
|
||||||
|
if (value < 0) {
|
||||||
|
return 10; // Negative int32 is always 10 bytes long
|
||||||
|
}
|
||||||
|
// For non-negative values, use the uint32_t implementation
|
||||||
|
return varint(static_cast<uint32_t>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates the size in bytes needed to encode an int64_t value as a varint
|
||||||
|
*
|
||||||
|
* @param value The int64_t value to calculate size for
|
||||||
|
* @return The number of bytes needed to encode the value
|
||||||
|
*/
|
||||||
|
static inline uint32_t varint(int64_t value) {
|
||||||
|
// For int64_t, we convert to uint64_t and calculate the size
|
||||||
|
// This works because the bit pattern determines the encoding size,
|
||||||
|
// and we've handled negative int32 values as a special case above
|
||||||
|
return varint(static_cast<uint64_t>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates the size in bytes needed to encode a field ID and wire type
|
||||||
|
*
|
||||||
|
* @param field_id The field identifier
|
||||||
|
* @param type The wire type value (from the WireType enum in the protobuf spec)
|
||||||
|
* @return The number of bytes needed to encode the field ID and wire type
|
||||||
|
*/
|
||||||
|
static inline uint32_t field(uint32_t field_id, uint32_t type) {
|
||||||
|
uint32_t tag = (field_id << 3) | (type & 0b111);
|
||||||
|
return varint(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Common parameters for all add_*_field methods
|
||||||
|
*
|
||||||
|
* All add_*_field methods follow these common patterns:
|
||||||
|
*
|
||||||
|
* @param total_size Reference to the total message size to update
|
||||||
|
* @param field_id_size Pre-calculated size of the field ID in bytes
|
||||||
|
* @param value The value to calculate size for (type varies)
|
||||||
|
* @param force Whether to calculate size even if the value is default/zero/empty
|
||||||
|
*
|
||||||
|
* Each method follows this implementation pattern:
|
||||||
|
* 1. Skip calculation if value is default (0, false, empty) and not forced
|
||||||
|
* 2. Calculate the size based on the field's encoding rules
|
||||||
|
* 3. Add the field_id_size + calculated value size to total_size
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of an int32 field to the total message size
|
||||||
|
*/
|
||||||
|
static inline void add_int32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
|
||||||
|
// Skip calculation if value is zero
|
||||||
|
if (value == 0) {
|
||||||
|
return; // No need to update total_size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate and directly add to total_size
|
||||||
|
if (value < 0) {
|
||||||
|
// Negative values are encoded as 10-byte varints in protobuf
|
||||||
|
total_size += field_id_size + 10;
|
||||||
|
} else {
|
||||||
|
// For non-negative values, use the standard varint size
|
||||||
|
total_size += field_id_size + varint(static_cast<uint32_t>(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of an int32 field to the total message size (repeated field version)
|
||||||
|
*/
|
||||||
|
static inline void add_int32_field_repeated(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
|
||||||
|
// Always calculate size for repeated fields
|
||||||
|
if (value < 0) {
|
||||||
|
// Negative values are encoded as 10-byte varints in protobuf
|
||||||
|
total_size += field_id_size + 10;
|
||||||
|
} else {
|
||||||
|
// For non-negative values, use the standard varint size
|
||||||
|
total_size += field_id_size + varint(static_cast<uint32_t>(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a uint32 field to the total message size
|
||||||
|
*/
|
||||||
|
static inline void add_uint32_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
|
||||||
|
// Skip calculation if value is zero
|
||||||
|
if (value == 0) {
|
||||||
|
return; // No need to update total_size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate and directly add to total_size
|
||||||
|
total_size += field_id_size + varint(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a uint32 field to the total message size (repeated field version)
|
||||||
|
*/
|
||||||
|
static inline void add_uint32_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
|
||||||
|
// Always calculate size for repeated fields
|
||||||
|
total_size += field_id_size + varint(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a boolean field to the total message size
|
||||||
|
*/
|
||||||
|
static inline void add_bool_field(uint32_t &total_size, uint32_t field_id_size, bool value) {
|
||||||
|
// Skip calculation if value is false
|
||||||
|
if (!value) {
|
||||||
|
return; // No need to update total_size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boolean fields always use 1 byte when true
|
||||||
|
total_size += field_id_size + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a boolean field to the total message size (repeated field version)
|
||||||
|
*/
|
||||||
|
static inline void add_bool_field_repeated(uint32_t &total_size, uint32_t field_id_size, bool value) {
|
||||||
|
// Always calculate size for repeated fields
|
||||||
|
// Boolean fields always use 1 byte
|
||||||
|
total_size += field_id_size + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a fixed field to the total message size
|
||||||
|
*
|
||||||
|
* Fixed fields always take exactly N bytes (4 for fixed32/float, 8 for fixed64/double).
|
||||||
|
*
|
||||||
|
* @tparam NumBytes The number of bytes for this fixed field (4 or 8)
|
||||||
|
* @param is_nonzero Whether the value is non-zero
|
||||||
|
*/
|
||||||
|
template<uint32_t NumBytes>
|
||||||
|
static inline void add_fixed_field(uint32_t &total_size, uint32_t field_id_size, bool is_nonzero) {
|
||||||
|
// Skip calculation if value is zero
|
||||||
|
if (!is_nonzero) {
|
||||||
|
return; // No need to update total_size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixed fields always take exactly NumBytes
|
||||||
|
total_size += field_id_size + NumBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of an enum field to the total message size
|
||||||
|
*
|
||||||
|
* Enum fields are encoded as uint32 varints.
|
||||||
|
*/
|
||||||
|
static inline void add_enum_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
|
||||||
|
// Skip calculation if value is zero
|
||||||
|
if (value == 0) {
|
||||||
|
return; // No need to update total_size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enums are encoded as uint32
|
||||||
|
total_size += field_id_size + varint(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of an enum field to the total message size (repeated field version)
|
||||||
|
*
|
||||||
|
* Enum fields are encoded as uint32 varints.
|
||||||
|
*/
|
||||||
|
static inline void add_enum_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
|
||||||
|
// Always calculate size for repeated fields
|
||||||
|
// Enums are encoded as uint32
|
||||||
|
total_size += field_id_size + varint(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a sint32 field to the total message size
|
||||||
|
*
|
||||||
|
* Sint32 fields use ZigZag encoding, which is more efficient for negative values.
|
||||||
|
*/
|
||||||
|
static inline void add_sint32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
|
||||||
|
// Skip calculation if value is zero
|
||||||
|
if (value == 0) {
|
||||||
|
return; // No need to update total_size
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZigZag encoding for sint32: (n << 1) ^ (n >> 31)
|
||||||
|
uint32_t zigzag = (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
|
||||||
|
total_size += field_id_size + varint(zigzag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a sint32 field to the total message size (repeated field version)
|
||||||
|
*
|
||||||
|
* Sint32 fields use ZigZag encoding, which is more efficient for negative values.
|
||||||
|
*/
|
||||||
|
static inline void add_sint32_field_repeated(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
|
||||||
|
// Always calculate size for repeated fields
|
||||||
|
// ZigZag encoding for sint32: (n << 1) ^ (n >> 31)
|
||||||
|
uint32_t zigzag = (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
|
||||||
|
total_size += field_id_size + varint(zigzag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of an int64 field to the total message size
|
||||||
|
*/
|
||||||
|
static inline void add_int64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value) {
|
||||||
|
// Skip calculation if value is zero
|
||||||
|
if (value == 0) {
|
||||||
|
return; // No need to update total_size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate and directly add to total_size
|
||||||
|
total_size += field_id_size + varint(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of an int64 field to the total message size (repeated field version)
|
||||||
|
*/
|
||||||
|
static inline void add_int64_field_repeated(uint32_t &total_size, uint32_t field_id_size, int64_t value) {
|
||||||
|
// Always calculate size for repeated fields
|
||||||
|
total_size += field_id_size + varint(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a uint64 field to the total message size
|
||||||
|
*/
|
||||||
|
static inline void add_uint64_field(uint32_t &total_size, uint32_t field_id_size, uint64_t value) {
|
||||||
|
// Skip calculation if value is zero
|
||||||
|
if (value == 0) {
|
||||||
|
return; // No need to update total_size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate and directly add to total_size
|
||||||
|
total_size += field_id_size + varint(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a uint64 field to the total message size (repeated field version)
|
||||||
|
*/
|
||||||
|
static inline void add_uint64_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint64_t value) {
|
||||||
|
// Always calculate size for repeated fields
|
||||||
|
total_size += field_id_size + varint(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a sint64 field to the total message size
|
||||||
|
*
|
||||||
|
* Sint64 fields use ZigZag encoding, which is more efficient for negative values.
|
||||||
|
*/
|
||||||
|
static inline void add_sint64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value) {
|
||||||
|
// Skip calculation if value is zero
|
||||||
|
if (value == 0) {
|
||||||
|
return; // No need to update total_size
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZigZag encoding for sint64: (n << 1) ^ (n >> 63)
|
||||||
|
uint64_t zigzag = (static_cast<uint64_t>(value) << 1) ^ (static_cast<uint64_t>(value >> 63));
|
||||||
|
total_size += field_id_size + varint(zigzag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a sint64 field to the total message size (repeated field version)
|
||||||
|
*
|
||||||
|
* Sint64 fields use ZigZag encoding, which is more efficient for negative values.
|
||||||
|
*/
|
||||||
|
static inline void add_sint64_field_repeated(uint32_t &total_size, uint32_t field_id_size, int64_t value) {
|
||||||
|
// Always calculate size for repeated fields
|
||||||
|
// ZigZag encoding for sint64: (n << 1) ^ (n >> 63)
|
||||||
|
uint64_t zigzag = (static_cast<uint64_t>(value) << 1) ^ (static_cast<uint64_t>(value >> 63));
|
||||||
|
total_size += field_id_size + varint(zigzag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a string/bytes field to the total message size
|
||||||
|
*/
|
||||||
|
static inline void add_string_field(uint32_t &total_size, uint32_t field_id_size, const std::string &str) {
|
||||||
|
// Skip calculation if string is empty
|
||||||
|
if (str.empty()) {
|
||||||
|
return; // No need to update total_size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate and directly add to total_size
|
||||||
|
const uint32_t str_size = static_cast<uint32_t>(str.size());
|
||||||
|
total_size += field_id_size + varint(str_size) + str_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a string/bytes field to the total message size (repeated field version)
|
||||||
|
*/
|
||||||
|
static inline void add_string_field_repeated(uint32_t &total_size, uint32_t field_id_size, const std::string &str) {
|
||||||
|
// Always calculate size for repeated fields
|
||||||
|
const uint32_t str_size = static_cast<uint32_t>(str.size());
|
||||||
|
total_size += field_id_size + varint(str_size) + str_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a nested message field to the total message size
|
||||||
|
*
|
||||||
|
* This helper function directly updates the total_size reference if the nested size
|
||||||
|
* is greater than zero.
|
||||||
|
*
|
||||||
|
* @param nested_size The pre-calculated size of the nested message
|
||||||
|
*/
|
||||||
|
static inline void add_message_field(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size) {
|
||||||
|
// Skip calculation if nested message is empty
|
||||||
|
if (nested_size == 0) {
|
||||||
|
return; // No need to update total_size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate and directly add to total_size
|
||||||
|
// Field ID + length varint + nested message content
|
||||||
|
total_size += field_id_size + varint(nested_size) + nested_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a nested message field to the total message size (repeated field version)
|
||||||
|
*
|
||||||
|
* @param nested_size The pre-calculated size of the nested message
|
||||||
|
*/
|
||||||
|
static inline void add_message_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size) {
|
||||||
|
// Always calculate size for repeated fields
|
||||||
|
// Field ID + length varint + nested message content
|
||||||
|
total_size += field_id_size + varint(nested_size) + nested_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a nested message field to the total message size
|
||||||
|
*
|
||||||
|
* This version takes a ProtoMessage object, calculates its size internally,
|
||||||
|
* and updates the total_size reference. This eliminates the need for a temporary variable
|
||||||
|
* at the call site.
|
||||||
|
*
|
||||||
|
* @param message The nested message object
|
||||||
|
*/
|
||||||
|
static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const ProtoMessage &message) {
|
||||||
|
uint32_t nested_size = 0;
|
||||||
|
message.calculate_size(nested_size);
|
||||||
|
|
||||||
|
// Use the base implementation with the calculated nested_size
|
||||||
|
add_message_field(total_size, field_id_size, nested_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a nested message field to the total message size (repeated field version)
|
||||||
|
*
|
||||||
|
* @param message The nested message object
|
||||||
|
*/
|
||||||
|
static inline void add_message_object_repeated(uint32_t &total_size, uint32_t field_id_size,
|
||||||
|
const ProtoMessage &message) {
|
||||||
|
uint32_t nested_size = 0;
|
||||||
|
message.calculate_size(nested_size);
|
||||||
|
|
||||||
|
// Use the base implementation with the calculated nested_size
|
||||||
|
add_message_field_repeated(total_size, field_id_size, nested_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the sizes of all messages in a repeated field to the total message size
|
||||||
|
*
|
||||||
|
* This helper processes a vector of message objects, calculating the size for each message
|
||||||
|
* and adding it to the total size.
|
||||||
|
*
|
||||||
|
* @tparam MessageType The type of the nested messages in the vector
|
||||||
|
* @param messages Vector of message objects
|
||||||
|
*/
|
||||||
|
template<typename MessageType>
|
||||||
|
static inline void add_repeated_message(uint32_t &total_size, uint32_t field_id_size,
|
||||||
|
const std::vector<MessageType> &messages) {
|
||||||
|
// Skip if the vector is empty
|
||||||
|
if (messages.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the repeated field version for all messages
|
||||||
|
for (const auto &message : messages) {
|
||||||
|
add_message_object_repeated(total_size, field_id_size, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Implementation of encode_message - must be after ProtoMessage is defined
|
||||||
|
inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessage &value, bool force) {
|
||||||
|
this->encode_field_raw(field_id, 2); // type 2: Length-delimited message
|
||||||
|
|
||||||
|
// Calculate the message size first
|
||||||
|
uint32_t msg_length_bytes = 0;
|
||||||
|
value.calculate_size(msg_length_bytes);
|
||||||
|
|
||||||
|
// Calculate how many bytes the length varint needs
|
||||||
|
uint32_t varint_length_bytes = ProtoSize::varint(msg_length_bytes);
|
||||||
|
|
||||||
|
// Reserve exact space for the length varint
|
||||||
|
size_t begin = this->buffer_->size();
|
||||||
|
this->buffer_->resize(this->buffer_->size() + varint_length_bytes);
|
||||||
|
|
||||||
|
// Write the length varint directly
|
||||||
|
ProtoVarInt(msg_length_bytes).encode_to_buffer_unchecked(this->buffer_->data() + begin, varint_length_bytes);
|
||||||
|
|
||||||
|
// Now encode the message content - it will append to the buffer
|
||||||
|
value.encode(*this);
|
||||||
|
|
||||||
|
// Verify that the encoded size matches what we calculated
|
||||||
|
assert(this->buffer_->size() == begin + varint_length_bytes + msg_length_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementation of decode_to_message - must be after ProtoMessage is defined
|
||||||
|
inline void ProtoLengthDelimited::decode_to_message(ProtoMessage &msg) const {
|
||||||
|
msg.decode(this->value_, this->length_);
|
||||||
|
}
|
||||||
|
|
||||||
template<typename T> const char *proto_enum_to_string(T value);
|
template<typename T> const char *proto_enum_to_string(T value);
|
||||||
|
|
||||||
class ProtoService {
|
class ProtoService {
|
||||||
@ -363,11 +846,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
|
||||||
|
@ -52,11 +52,21 @@ bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr size_t FLUSH_BATCH_SIZE = 8;
|
// Batch size for BLE advertisements to maximize WiFi efficiency
|
||||||
static std::vector<api::BluetoothLERawAdvertisement> &get_batch_buffer() {
|
// Each advertisement is up to 80 bytes when packaged (including protocol overhead)
|
||||||
static std::vector<api::BluetoothLERawAdvertisement> batch_buffer;
|
// Most advertisements are 20-30 bytes, allowing even more to fit per packet
|
||||||
return batch_buffer;
|
// 16 advertisements × 80 bytes (worst case) = 1280 bytes out of ~1320 bytes usable payload
|
||||||
}
|
// This achieves ~97% WiFi MTU utilization while staying under the limit
|
||||||
|
static constexpr size_t FLUSH_BATCH_SIZE = 16;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// Batch buffer in anonymous namespace to avoid guard variable (saves 8 bytes)
|
||||||
|
// This is initialized at program startup before any threads
|
||||||
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
std::vector<api::BluetoothLERawAdvertisement> batch_buffer;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
static std::vector<api::BluetoothLERawAdvertisement> &get_batch_buffer() { return batch_buffer; }
|
||||||
|
|
||||||
bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) {
|
bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) {
|
||||||
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || !this->raw_advertisements_)
|
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || !this->raw_advertisements_)
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
|
|
||||||
|
CONF_BYTE_ORDER = "byte_order"
|
||||||
CONF_DRAW_ROUNDING = "draw_rounding"
|
CONF_DRAW_ROUNDING = "draw_rounding"
|
||||||
CONF_ON_STATE_CHANGE = "on_state_change"
|
CONF_ON_STATE_CHANGE = "on_state_change"
|
||||||
CONF_REQUEST_HEADERS = "request_headers"
|
CONF_REQUEST_HEADERS = "request_headers"
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
from esphome.config_helpers import filter_source_files_from_platform
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_BLOCK,
|
CONF_BLOCK,
|
||||||
@ -7,6 +8,7 @@ from esphome.const import (
|
|||||||
CONF_FREE,
|
CONF_FREE,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_LOOP_TIME,
|
CONF_LOOP_TIME,
|
||||||
|
PlatformFramework,
|
||||||
)
|
)
|
||||||
|
|
||||||
CODEOWNERS = ["@OttoWinter"]
|
CODEOWNERS = ["@OttoWinter"]
|
||||||
@ -44,3 +46,21 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
|
|
||||||
|
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||||
|
{
|
||||||
|
"debug_esp32.cpp": {
|
||||||
|
PlatformFramework.ESP32_ARDUINO,
|
||||||
|
PlatformFramework.ESP32_IDF,
|
||||||
|
},
|
||||||
|
"debug_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
|
||||||
|
"debug_host.cpp": {PlatformFramework.HOST_NATIVE},
|
||||||
|
"debug_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
|
||||||
|
"debug_libretiny.cpp": {
|
||||||
|
PlatformFramework.BK72XX_ARDUINO,
|
||||||
|
PlatformFramework.RTL87XX_ARDUINO,
|
||||||
|
PlatformFramework.LN882X_ARDUINO,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -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,
|
||||||
@ -11,6 +11,7 @@ from esphome.components.esp32.const import (
|
|||||||
VARIANT_ESP32S2,
|
VARIANT_ESP32S2,
|
||||||
VARIANT_ESP32S3,
|
VARIANT_ESP32S3,
|
||||||
)
|
)
|
||||||
|
from esphome.config_helpers import filter_source_files_from_platform
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_DEFAULT,
|
CONF_DEFAULT,
|
||||||
@ -27,6 +28,7 @@ from esphome.const import (
|
|||||||
CONF_WAKEUP_PIN,
|
CONF_WAKEUP_PIN,
|
||||||
PLATFORM_ESP32,
|
PLATFORM_ESP32,
|
||||||
PLATFORM_ESP8266,
|
PLATFORM_ESP8266,
|
||||||
|
PlatformFramework,
|
||||||
)
|
)
|
||||||
|
|
||||||
WAKEUP_PINS = {
|
WAKEUP_PINS = {
|
||||||
@ -114,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")
|
||||||
@ -146,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,
|
||||||
}
|
}
|
||||||
@ -185,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]),
|
||||||
@ -313,3 +336,14 @@ async def deep_sleep_action_to_code(config, action_id, template_arg, args):
|
|||||||
var = cg.new_Pvariable(action_id, template_arg)
|
var = cg.new_Pvariable(action_id, template_arg)
|
||||||
await cg.register_parented(var, config[CONF_ID])
|
await cg.register_parented(var, config[CONF_ID])
|
||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||||
|
{
|
||||||
|
"deep_sleep_esp32.cpp": {
|
||||||
|
PlatformFramework.ESP32_ARDUINO,
|
||||||
|
PlatformFramework.ESP32_IDF,
|
||||||
|
},
|
||||||
|
"deep_sleep_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -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) {
|
||||||
|
@ -25,10 +25,15 @@ namespace esphome {
|
|||||||
namespace esp32_ble {
|
namespace esp32_ble {
|
||||||
|
|
||||||
// Maximum number of BLE scan results to buffer
|
// Maximum number of BLE scan results to buffer
|
||||||
|
// Sized to handle bursts of advertisements while allowing for processing delays
|
||||||
|
// With 16 advertisements per batch and some safety margin:
|
||||||
|
// - Without PSRAM: 24 entries (1.5× batch size)
|
||||||
|
// - With PSRAM: 36 entries (2.25× batch size)
|
||||||
|
// The reduced structure size (~80 bytes vs ~400 bytes) allows for larger buffers
|
||||||
#ifdef USE_PSRAM
|
#ifdef USE_PSRAM
|
||||||
static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 32;
|
static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 36;
|
||||||
#else
|
#else
|
||||||
static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 20;
|
static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 24;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Maximum size of the BLE event queue - must be power of 2 for lock-free queue
|
// Maximum size of the BLE event queue - must be power of 2 for lock-free queue
|
||||||
|
@ -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")
|
||||||
|
@ -2,6 +2,7 @@ from esphome import automation
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import esp32
|
from esphome.components import esp32
|
||||||
from esphome.components.const import CONF_REQUEST_HEADERS
|
from esphome.components.const import CONF_REQUEST_HEADERS
|
||||||
|
from esphome.config_helpers import filter_source_files_from_platform
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ESP8266_DISABLE_SSL_SUPPORT,
|
CONF_ESP8266_DISABLE_SSL_SUPPORT,
|
||||||
@ -13,6 +14,7 @@ from esphome.const import (
|
|||||||
CONF_URL,
|
CONF_URL,
|
||||||
CONF_WATCHDOG_TIMEOUT,
|
CONF_WATCHDOG_TIMEOUT,
|
||||||
PLATFORM_HOST,
|
PLATFORM_HOST,
|
||||||
|
PlatformFramework,
|
||||||
__version__,
|
__version__,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, Lambda
|
from esphome.core import CORE, Lambda
|
||||||
@ -319,3 +321,19 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
|
|||||||
await automation.build_automation(trigger, [], conf)
|
await automation.build_automation(trigger, [], conf)
|
||||||
|
|
||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||||
|
{
|
||||||
|
"http_request_host.cpp": {PlatformFramework.HOST_NATIVE},
|
||||||
|
"http_request_arduino.cpp": {
|
||||||
|
PlatformFramework.ESP32_ARDUINO,
|
||||||
|
PlatformFramework.ESP8266_ARDUINO,
|
||||||
|
PlatformFramework.RP2040_ARDUINO,
|
||||||
|
PlatformFramework.BK72XX_ARDUINO,
|
||||||
|
PlatformFramework.RTL87XX_ARDUINO,
|
||||||
|
PlatformFramework.LN882X_ARDUINO,
|
||||||
|
},
|
||||||
|
"http_request_idf.cpp": {PlatformFramework.ESP32_IDF},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -111,8 +111,8 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Optional(CONF_MOISTURE): sensor.sensor_schema(
|
cv.Optional(CONF_MOISTURE): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_INTENSITY,
|
unit_of_measurement=UNIT_INTENSITY,
|
||||||
accuracy_decimals=0,
|
accuracy_decimals=0,
|
||||||
device_class=DEVICE_CLASS_PRECIPITATION_INTENSITY,
|
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
icon="mdi:weather-rainy",
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_CELSIUS,
|
unit_of_measurement=UNIT_CELSIUS,
|
||||||
|
@ -3,6 +3,7 @@ import logging
|
|||||||
from esphome import pins
|
from esphome import pins
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import esp32
|
from esphome.components import esp32
|
||||||
|
from esphome.config_helpers import filter_source_files_from_platform
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ADDRESS,
|
CONF_ADDRESS,
|
||||||
@ -18,6 +19,7 @@ from esphome.const import (
|
|||||||
PLATFORM_ESP32,
|
PLATFORM_ESP32,
|
||||||
PLATFORM_ESP8266,
|
PLATFORM_ESP8266,
|
||||||
PLATFORM_RP2040,
|
PLATFORM_RP2040,
|
||||||
|
PlatformFramework,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
import esphome.final_validate as fv
|
import esphome.final_validate as fv
|
||||||
@ -205,3 +207,18 @@ def final_validate_device_schema(
|
|||||||
{cv.Required(CONF_I2C_ID): fv.id_declaration_match_schema(hub_schema)},
|
{cv.Required(CONF_I2C_ID): fv.id_declaration_match_schema(hub_schema)},
|
||||||
extra=cv.ALLOW_EXTRA,
|
extra=cv.ALLOW_EXTRA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||||
|
{
|
||||||
|
"i2c_bus_arduino.cpp": {
|
||||||
|
PlatformFramework.ESP32_ARDUINO,
|
||||||
|
PlatformFramework.ESP8266_ARDUINO,
|
||||||
|
PlatformFramework.RP2040_ARDUINO,
|
||||||
|
PlatformFramework.BK72XX_ARDUINO,
|
||||||
|
PlatformFramework.RTL87XX_ARDUINO,
|
||||||
|
PlatformFramework.LN882X_ARDUINO,
|
||||||
|
},
|
||||||
|
"i2c_bus_esp_idf.cpp": {PlatformFramework.ESP32_IDF},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -10,8 +10,10 @@ from PIL import Image, UnidentifiedImageError
|
|||||||
|
|
||||||
from esphome import core, external_files
|
from esphome import core, external_files
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
from esphome.components.const import CONF_BYTE_ORDER
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
|
CONF_DEFAULTS,
|
||||||
CONF_DITHER,
|
CONF_DITHER,
|
||||||
CONF_FILE,
|
CONF_FILE,
|
||||||
CONF_ICON,
|
CONF_ICON,
|
||||||
@ -38,6 +40,7 @@ CONF_OPAQUE = "opaque"
|
|||||||
CONF_CHROMA_KEY = "chroma_key"
|
CONF_CHROMA_KEY = "chroma_key"
|
||||||
CONF_ALPHA_CHANNEL = "alpha_channel"
|
CONF_ALPHA_CHANNEL = "alpha_channel"
|
||||||
CONF_INVERT_ALPHA = "invert_alpha"
|
CONF_INVERT_ALPHA = "invert_alpha"
|
||||||
|
CONF_IMAGES = "images"
|
||||||
|
|
||||||
TRANSPARENCY_TYPES = (
|
TRANSPARENCY_TYPES = (
|
||||||
CONF_OPAQUE,
|
CONF_OPAQUE,
|
||||||
@ -188,6 +191,10 @@ class ImageRGB565(ImageEncoder):
|
|||||||
dither,
|
dither,
|
||||||
invert_alpha,
|
invert_alpha,
|
||||||
)
|
)
|
||||||
|
self.big_endian = True
|
||||||
|
|
||||||
|
def set_big_endian(self, big_endian: bool) -> None:
|
||||||
|
self.big_endian = big_endian
|
||||||
|
|
||||||
def convert(self, image, path):
|
def convert(self, image, path):
|
||||||
return image.convert("RGBA")
|
return image.convert("RGBA")
|
||||||
@ -205,10 +212,16 @@ class ImageRGB565(ImageEncoder):
|
|||||||
g = 1
|
g = 1
|
||||||
b = 0
|
b = 0
|
||||||
rgb = (r << 11) | (g << 5) | b
|
rgb = (r << 11) | (g << 5) | b
|
||||||
self.data[self.index] = rgb >> 8
|
if self.big_endian:
|
||||||
self.index += 1
|
self.data[self.index] = rgb >> 8
|
||||||
self.data[self.index] = rgb & 0xFF
|
self.index += 1
|
||||||
self.index += 1
|
self.data[self.index] = rgb & 0xFF
|
||||||
|
self.index += 1
|
||||||
|
else:
|
||||||
|
self.data[self.index] = rgb & 0xFF
|
||||||
|
self.index += 1
|
||||||
|
self.data[self.index] = rgb >> 8
|
||||||
|
self.index += 1
|
||||||
if self.transparency == CONF_ALPHA_CHANNEL:
|
if self.transparency == CONF_ALPHA_CHANNEL:
|
||||||
if self.invert_alpha:
|
if self.invert_alpha:
|
||||||
a ^= 0xFF
|
a ^= 0xFF
|
||||||
@ -364,7 +377,7 @@ def validate_file_shorthand(value):
|
|||||||
value = cv.string_strict(value)
|
value = cv.string_strict(value)
|
||||||
parts = value.strip().split(":")
|
parts = value.strip().split(":")
|
||||||
if len(parts) == 2 and parts[0] in MDI_SOURCES:
|
if len(parts) == 2 and parts[0] in MDI_SOURCES:
|
||||||
match = re.match(r"[a-zA-Z0-9\-]+", parts[1])
|
match = re.match(r"^[a-zA-Z0-9\-]+$", parts[1])
|
||||||
if match is None:
|
if match is None:
|
||||||
raise cv.Invalid(f"Could not parse mdi icon name from '{value}'.")
|
raise cv.Invalid(f"Could not parse mdi icon name from '{value}'.")
|
||||||
return download_gh_svg(parts[1], parts[0])
|
return download_gh_svg(parts[1], parts[0])
|
||||||
@ -434,20 +447,29 @@ def validate_type(image_types):
|
|||||||
|
|
||||||
|
|
||||||
def validate_settings(value):
|
def validate_settings(value):
|
||||||
type = value[CONF_TYPE]
|
"""
|
||||||
|
Validate the settings for a single image configuration.
|
||||||
|
"""
|
||||||
|
conf_type = value[CONF_TYPE]
|
||||||
|
type_class = IMAGE_TYPE[conf_type]
|
||||||
transparency = value[CONF_TRANSPARENCY].lower()
|
transparency = value[CONF_TRANSPARENCY].lower()
|
||||||
allow_config = IMAGE_TYPE[type].allow_config
|
if transparency not in type_class.allow_config:
|
||||||
if transparency not in allow_config:
|
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
f"Image format '{type}' cannot have transparency: {transparency}"
|
f"Image format '{conf_type}' cannot have transparency: {transparency}"
|
||||||
)
|
)
|
||||||
invert_alpha = value.get(CONF_INVERT_ALPHA, False)
|
invert_alpha = value.get(CONF_INVERT_ALPHA, False)
|
||||||
if (
|
if (
|
||||||
invert_alpha
|
invert_alpha
|
||||||
and transparency != CONF_ALPHA_CHANNEL
|
and transparency != CONF_ALPHA_CHANNEL
|
||||||
and CONF_INVERT_ALPHA not in allow_config
|
and CONF_INVERT_ALPHA not in type_class.allow_config
|
||||||
):
|
):
|
||||||
raise cv.Invalid("No alpha channel to invert")
|
raise cv.Invalid("No alpha channel to invert")
|
||||||
|
if value.get(CONF_BYTE_ORDER) is not None and not callable(
|
||||||
|
getattr(type_class, "set_big_endian", None)
|
||||||
|
):
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"Image format '{conf_type}' does not support byte order configuration"
|
||||||
|
)
|
||||||
if file := value.get(CONF_FILE):
|
if file := value.get(CONF_FILE):
|
||||||
file = Path(file)
|
file = Path(file)
|
||||||
if is_svg_file(file):
|
if is_svg_file(file):
|
||||||
@ -456,31 +478,82 @@ def validate_settings(value):
|
|||||||
try:
|
try:
|
||||||
Image.open(file)
|
Image.open(file)
|
||||||
except UnidentifiedImageError as exc:
|
except UnidentifiedImageError as exc:
|
||||||
raise cv.Invalid(f"File can't be opened as image: {file}") from exc
|
raise cv.Invalid(
|
||||||
|
f"File can't be opened as image: {file.absolute()}"
|
||||||
|
) from exc
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
IMAGE_ID_SCHEMA = {
|
||||||
|
cv.Required(CONF_ID): cv.declare_id(Image_),
|
||||||
|
cv.Required(CONF_FILE): cv.Any(validate_file_shorthand, TYPED_FILE_SCHEMA),
|
||||||
|
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
OPTIONS_SCHEMA = {
|
||||||
|
cv.Optional(CONF_RESIZE): cv.dimensions,
|
||||||
|
cv.Optional(CONF_DITHER, default="NONE"): cv.one_of(
|
||||||
|
"NONE", "FLOYDSTEINBERG", upper=True
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_INVERT_ALPHA, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_BYTE_ORDER): cv.one_of("BIG_ENDIAN", "LITTLE_ENDIAN", upper=True),
|
||||||
|
cv.Optional(CONF_TRANSPARENCY, default=CONF_OPAQUE): validate_transparency(),
|
||||||
|
cv.Optional(CONF_TYPE): validate_type(IMAGE_TYPE),
|
||||||
|
}
|
||||||
|
|
||||||
|
OPTIONS = [key.schema for key in OPTIONS_SCHEMA]
|
||||||
|
|
||||||
|
# image schema with no defaults, used with `CONF_IMAGES` in the config
|
||||||
|
IMAGE_SCHEMA_NO_DEFAULTS = {
|
||||||
|
**IMAGE_ID_SCHEMA,
|
||||||
|
**{cv.Optional(key): OPTIONS_SCHEMA[key] for key in OPTIONS},
|
||||||
|
}
|
||||||
|
|
||||||
BASE_SCHEMA = cv.Schema(
|
BASE_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_ID): cv.declare_id(Image_),
|
**IMAGE_ID_SCHEMA,
|
||||||
cv.Required(CONF_FILE): cv.Any(validate_file_shorthand, TYPED_FILE_SCHEMA),
|
**OPTIONS_SCHEMA,
|
||||||
cv.Optional(CONF_RESIZE): cv.dimensions,
|
|
||||||
cv.Optional(CONF_DITHER, default="NONE"): cv.one_of(
|
|
||||||
"NONE", "FLOYDSTEINBERG", upper=True
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_INVERT_ALPHA, default=False): cv.boolean,
|
|
||||||
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
|
||||||
}
|
}
|
||||||
).add_extra(validate_settings)
|
).add_extra(validate_settings)
|
||||||
|
|
||||||
IMAGE_SCHEMA = BASE_SCHEMA.extend(
|
IMAGE_SCHEMA = BASE_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_TYPE): validate_type(IMAGE_TYPE),
|
cv.Required(CONF_TYPE): validate_type(IMAGE_TYPE),
|
||||||
cv.Optional(CONF_TRANSPARENCY, default=CONF_OPAQUE): validate_transparency(),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_defaults(value):
|
||||||
|
"""
|
||||||
|
Validate the options for images with defaults
|
||||||
|
"""
|
||||||
|
defaults = value[CONF_DEFAULTS]
|
||||||
|
result = []
|
||||||
|
for index, image in enumerate(value[CONF_IMAGES]):
|
||||||
|
type = image.get(CONF_TYPE, defaults.get(CONF_TYPE))
|
||||||
|
if type is None:
|
||||||
|
raise cv.Invalid(
|
||||||
|
"Type is required either in the image config or in the defaults",
|
||||||
|
path=[CONF_IMAGES, index],
|
||||||
|
)
|
||||||
|
type_class = IMAGE_TYPE[type]
|
||||||
|
# A default byte order should be simply ignored if the type does not support it
|
||||||
|
available_options = [*OPTIONS]
|
||||||
|
if (
|
||||||
|
not callable(getattr(type_class, "set_big_endian", None))
|
||||||
|
and CONF_BYTE_ORDER not in image
|
||||||
|
):
|
||||||
|
available_options.remove(CONF_BYTE_ORDER)
|
||||||
|
config = {
|
||||||
|
**{key: image.get(key, defaults.get(key)) for key in available_options},
|
||||||
|
**{key.schema: image[key.schema] for key in IMAGE_ID_SCHEMA},
|
||||||
|
}
|
||||||
|
validate_settings(config)
|
||||||
|
result.append(config)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def typed_image_schema(image_type):
|
def typed_image_schema(image_type):
|
||||||
"""
|
"""
|
||||||
Construct a schema for a specific image type, allowing transparency options
|
Construct a schema for a specific image type, allowing transparency options
|
||||||
@ -523,10 +596,33 @@ def typed_image_schema(image_type):
|
|||||||
|
|
||||||
# The config schema can be a (possibly empty) single list of images,
|
# The config schema can be a (possibly empty) single list of images,
|
||||||
# or a dictionary of image types each with a list of images
|
# or a dictionary of image types each with a list of images
|
||||||
CONFIG_SCHEMA = cv.Any(
|
# or a dictionary with keys `defaults:` and `images:`
|
||||||
cv.Schema({cv.Optional(t.lower()): typed_image_schema(t) for t in IMAGE_TYPE}),
|
|
||||||
cv.ensure_list(IMAGE_SCHEMA),
|
|
||||||
)
|
def _config_schema(config):
|
||||||
|
if isinstance(config, list):
|
||||||
|
return cv.Schema([IMAGE_SCHEMA])(config)
|
||||||
|
if not isinstance(config, dict):
|
||||||
|
raise cv.Invalid(
|
||||||
|
"Badly formed image configuration, expected a list or a dictionary"
|
||||||
|
)
|
||||||
|
if CONF_DEFAULTS in config or CONF_IMAGES in config:
|
||||||
|
return validate_defaults(
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_DEFAULTS): OPTIONS_SCHEMA,
|
||||||
|
cv.Required(CONF_IMAGES): cv.ensure_list(IMAGE_SCHEMA_NO_DEFAULTS),
|
||||||
|
}
|
||||||
|
)(config)
|
||||||
|
)
|
||||||
|
if CONF_ID in config or CONF_FILE in config:
|
||||||
|
return cv.ensure_list(IMAGE_SCHEMA)([config])
|
||||||
|
return cv.Schema(
|
||||||
|
{cv.Optional(t.lower()): typed_image_schema(t) for t in IMAGE_TYPE}
|
||||||
|
)(config)
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = _config_schema
|
||||||
|
|
||||||
|
|
||||||
async def write_image(config, all_frames=False):
|
async def write_image(config, all_frames=False):
|
||||||
@ -585,6 +681,9 @@ async def write_image(config, all_frames=False):
|
|||||||
|
|
||||||
total_rows = height * frame_count
|
total_rows = height * frame_count
|
||||||
encoder = IMAGE_TYPE[type](width, total_rows, transparency, dither, invert_alpha)
|
encoder = IMAGE_TYPE[type](width, total_rows, transparency, dither, invert_alpha)
|
||||||
|
if byte_order := config.get(CONF_BYTE_ORDER):
|
||||||
|
# Check for valid type has already been done in validate_settings
|
||||||
|
encoder.set_big_endian(byte_order == "BIG_ENDIAN")
|
||||||
for frame_index in range(frame_count):
|
for frame_index in range(frame_count):
|
||||||
image.seek(frame_index)
|
image.seek(frame_index)
|
||||||
pixels = encoder.convert(image.resize((width, height)), path).getdata()
|
pixels = encoder.convert(image.resize((width, height)), path).getdata()
|
||||||
|
@ -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,8 +155,9 @@ 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");
|
||||||
if (pos >= 4) {
|
this->buffer_pos_ = 0;
|
||||||
if (memcmp(&buffer[pos - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) {
|
}
|
||||||
this->set_cmd_active_(false); // Set command state to inactive after responce.
|
if (this->buffer_pos_ < 4) {
|
||||||
this->handle_ack_data_(buffer, pos);
|
return; // Not enough data to process yet
|
||||||
pos = 0;
|
}
|
||||||
} else if ((buffer[pos - 2] == 0x0D && buffer[pos - 1] == 0x0A) &&
|
if (memcmp(&buffer[this->buffer_pos_ - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) {
|
||||||
(this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) {
|
this->cmd_active_ = false; // Set command state to inactive after response
|
||||||
this->handle_simple_mode_(buffer, pos);
|
this->handle_ack_data_(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 ((buffer[this->buffer_pos_ - 2] == 0x0D && buffer[this->buffer_pos_ - 1] == 0x0A) &&
|
||||||
(this->get_mode_() == CMD_SYSTEM_MODE_ENERGY)) {
|
(this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) {
|
||||||
this->handle_energy_mode_(buffer, pos);
|
this->handle_simple_mode_(buffer, this->buffer_pos_);
|
||||||
pos = 0;
|
this->buffer_pos_ = 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->handle_energy_mode_(buffer, this->buffer_pos_);
|
||||||
|
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,10 +641,12 @@ 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,8 +766,9 @@ 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_() {
|
||||||
@ -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", "soft")
|
||||||
# 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
|
||||||
|
@ -97,12 +97,12 @@ class AddressableLight : public LightOutput, public Component {
|
|||||||
}
|
}
|
||||||
virtual ESPColorView get_view_internal(int32_t index) const = 0;
|
virtual ESPColorView get_view_internal(int32_t index) const = 0;
|
||||||
|
|
||||||
bool effect_active_{false};
|
|
||||||
ESPColorCorrection correction_{};
|
ESPColorCorrection correction_{};
|
||||||
|
LightState *state_parent_{nullptr};
|
||||||
#ifdef USE_POWER_SUPPLY
|
#ifdef USE_POWER_SUPPLY
|
||||||
power_supply::PowerSupplyRequester power_;
|
power_supply::PowerSupplyRequester power_;
|
||||||
#endif
|
#endif
|
||||||
LightState *state_parent_{nullptr};
|
bool effect_active_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
class AddressableLightTransformer : public LightTransitionTransformer {
|
class AddressableLightTransformer : public LightTransitionTransformer {
|
||||||
@ -114,9 +114,9 @@ class AddressableLightTransformer : public LightTransitionTransformer {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
AddressableLight &light_;
|
AddressableLight &light_;
|
||||||
Color target_color_{};
|
|
||||||
float last_transition_progress_{0.0f};
|
float last_transition_progress_{0.0f};
|
||||||
float accumulated_alpha_{0.0f};
|
float accumulated_alpha_{0.0f};
|
||||||
|
Color target_color_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace light
|
} // namespace light
|
||||||
|
@ -69,8 +69,8 @@ class ESPColorCorrection {
|
|||||||
protected:
|
protected:
|
||||||
uint8_t gamma_table_[256];
|
uint8_t gamma_table_[256];
|
||||||
uint8_t gamma_reverse_table_[256];
|
uint8_t gamma_reverse_table_[256];
|
||||||
uint8_t local_brightness_{255};
|
|
||||||
Color max_brightness_;
|
Color max_brightness_;
|
||||||
|
uint8_t local_brightness_{255};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace light
|
} // namespace light
|
||||||
|
@ -2,12 +2,28 @@
|
|||||||
#include "light_call.h"
|
#include "light_call.h"
|
||||||
#include "light_state.h"
|
#include "light_state.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/optional.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace light {
|
namespace light {
|
||||||
|
|
||||||
static const char *const TAG = "light";
|
static const char *const TAG = "light";
|
||||||
|
|
||||||
|
// Macro to reduce repetitive setter code
|
||||||
|
#define IMPLEMENT_LIGHT_CALL_SETTER(name, type, flag) \
|
||||||
|
LightCall &LightCall::set_##name(optional<type>(name)) { \
|
||||||
|
if ((name).has_value()) { \
|
||||||
|
this->name##_ = (name).value(); \
|
||||||
|
} \
|
||||||
|
this->set_flag_(flag, (name).has_value()); \
|
||||||
|
return *this; \
|
||||||
|
} \
|
||||||
|
LightCall &LightCall::set_##name(type name) { \
|
||||||
|
this->name##_ = name; \
|
||||||
|
this->set_flag_(flag, true); \
|
||||||
|
return *this; \
|
||||||
|
}
|
||||||
|
|
||||||
static const LogString *color_mode_to_human(ColorMode color_mode) {
|
static const LogString *color_mode_to_human(ColorMode color_mode) {
|
||||||
if (color_mode == ColorMode::UNKNOWN)
|
if (color_mode == ColorMode::UNKNOWN)
|
||||||
return LOG_STR("Unknown");
|
return LOG_STR("Unknown");
|
||||||
@ -32,41 +48,43 @@ void LightCall::perform() {
|
|||||||
const char *name = this->parent_->get_name().c_str();
|
const char *name = this->parent_->get_name().c_str();
|
||||||
LightColorValues v = this->validate_();
|
LightColorValues v = this->validate_();
|
||||||
|
|
||||||
if (this->publish_) {
|
if (this->get_publish_()) {
|
||||||
ESP_LOGD(TAG, "'%s' Setting:", name);
|
ESP_LOGD(TAG, "'%s' Setting:", name);
|
||||||
|
|
||||||
// Only print color mode when it's being changed
|
// Only print color mode when it's being changed
|
||||||
ColorMode current_color_mode = this->parent_->remote_values.get_color_mode();
|
ColorMode current_color_mode = this->parent_->remote_values.get_color_mode();
|
||||||
if (this->color_mode_.value_or(current_color_mode) != current_color_mode) {
|
ColorMode target_color_mode = this->has_color_mode() ? this->color_mode_ : current_color_mode;
|
||||||
|
if (target_color_mode != current_color_mode) {
|
||||||
ESP_LOGD(TAG, " Color mode: %s", LOG_STR_ARG(color_mode_to_human(v.get_color_mode())));
|
ESP_LOGD(TAG, " Color mode: %s", LOG_STR_ARG(color_mode_to_human(v.get_color_mode())));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only print state when it's being changed
|
// Only print state when it's being changed
|
||||||
bool current_state = this->parent_->remote_values.is_on();
|
bool current_state = this->parent_->remote_values.is_on();
|
||||||
if (this->state_.value_or(current_state) != current_state) {
|
bool target_state = this->has_state() ? this->state_ : current_state;
|
||||||
|
if (target_state != current_state) {
|
||||||
ESP_LOGD(TAG, " State: %s", ONOFF(v.is_on()));
|
ESP_LOGD(TAG, " State: %s", ONOFF(v.is_on()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->brightness_.has_value()) {
|
if (this->has_brightness()) {
|
||||||
ESP_LOGD(TAG, " Brightness: %.0f%%", v.get_brightness() * 100.0f);
|
ESP_LOGD(TAG, " Brightness: %.0f%%", v.get_brightness() * 100.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->color_brightness_.has_value()) {
|
if (this->has_color_brightness()) {
|
||||||
ESP_LOGD(TAG, " Color brightness: %.0f%%", v.get_color_brightness() * 100.0f);
|
ESP_LOGD(TAG, " Color brightness: %.0f%%", v.get_color_brightness() * 100.0f);
|
||||||
}
|
}
|
||||||
if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
|
if (this->has_red() || this->has_green() || this->has_blue()) {
|
||||||
ESP_LOGD(TAG, " Red: %.0f%%, Green: %.0f%%, Blue: %.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f,
|
ESP_LOGD(TAG, " Red: %.0f%%, Green: %.0f%%, Blue: %.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f,
|
||||||
v.get_blue() * 100.0f);
|
v.get_blue() * 100.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->white_.has_value()) {
|
if (this->has_white()) {
|
||||||
ESP_LOGD(TAG, " White: %.0f%%", v.get_white() * 100.0f);
|
ESP_LOGD(TAG, " White: %.0f%%", v.get_white() * 100.0f);
|
||||||
}
|
}
|
||||||
if (this->color_temperature_.has_value()) {
|
if (this->has_color_temperature()) {
|
||||||
ESP_LOGD(TAG, " Color temperature: %.1f mireds", v.get_color_temperature());
|
ESP_LOGD(TAG, " Color temperature: %.1f mireds", v.get_color_temperature());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->cold_white_.has_value() || this->warm_white_.has_value()) {
|
if (this->has_cold_white() || this->has_warm_white()) {
|
||||||
ESP_LOGD(TAG, " Cold white: %.0f%%, warm white: %.0f%%", v.get_cold_white() * 100.0f,
|
ESP_LOGD(TAG, " Cold white: %.0f%%, warm white: %.0f%%", v.get_cold_white() * 100.0f,
|
||||||
v.get_warm_white() * 100.0f);
|
v.get_warm_white() * 100.0f);
|
||||||
}
|
}
|
||||||
@ -74,58 +92,57 @@ void LightCall::perform() {
|
|||||||
|
|
||||||
if (this->has_flash_()) {
|
if (this->has_flash_()) {
|
||||||
// FLASH
|
// FLASH
|
||||||
if (this->publish_) {
|
if (this->get_publish_()) {
|
||||||
ESP_LOGD(TAG, " Flash length: %.1fs", *this->flash_length_ / 1e3f);
|
ESP_LOGD(TAG, " Flash length: %.1fs", this->flash_length_ / 1e3f);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->parent_->start_flash_(v, *this->flash_length_, this->publish_);
|
this->parent_->start_flash_(v, this->flash_length_, this->get_publish_());
|
||||||
} else if (this->has_transition_()) {
|
} else if (this->has_transition_()) {
|
||||||
// TRANSITION
|
// TRANSITION
|
||||||
if (this->publish_) {
|
if (this->get_publish_()) {
|
||||||
ESP_LOGD(TAG, " Transition length: %.1fs", *this->transition_length_ / 1e3f);
|
ESP_LOGD(TAG, " Transition length: %.1fs", this->transition_length_ / 1e3f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special case: Transition and effect can be set when turning off
|
// Special case: Transition and effect can be set when turning off
|
||||||
if (this->has_effect_()) {
|
if (this->has_effect_()) {
|
||||||
if (this->publish_) {
|
if (this->get_publish_()) {
|
||||||
ESP_LOGD(TAG, " Effect: 'None'");
|
ESP_LOGD(TAG, " Effect: 'None'");
|
||||||
}
|
}
|
||||||
this->parent_->stop_effect_();
|
this->parent_->stop_effect_();
|
||||||
}
|
}
|
||||||
|
|
||||||
this->parent_->start_transition_(v, *this->transition_length_, this->publish_);
|
this->parent_->start_transition_(v, this->transition_length_, this->get_publish_());
|
||||||
|
|
||||||
} else if (this->has_effect_()) {
|
} else if (this->has_effect_()) {
|
||||||
// EFFECT
|
// EFFECT
|
||||||
auto effect = this->effect_;
|
|
||||||
const char *effect_s;
|
const char *effect_s;
|
||||||
if (effect == 0u) {
|
if (this->effect_ == 0u) {
|
||||||
effect_s = "None";
|
effect_s = "None";
|
||||||
} else {
|
} else {
|
||||||
effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str();
|
effect_s = this->parent_->effects_[this->effect_ - 1]->get_name().c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->publish_) {
|
if (this->get_publish_()) {
|
||||||
ESP_LOGD(TAG, " Effect: '%s'", effect_s);
|
ESP_LOGD(TAG, " Effect: '%s'", effect_s);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->parent_->start_effect_(*this->effect_);
|
this->parent_->start_effect_(this->effect_);
|
||||||
|
|
||||||
// Also set light color values when starting an effect
|
// Also set light color values when starting an effect
|
||||||
// For example to turn off the light
|
// For example to turn off the light
|
||||||
this->parent_->set_immediately_(v, true);
|
this->parent_->set_immediately_(v, true);
|
||||||
} else {
|
} else {
|
||||||
// INSTANT CHANGE
|
// INSTANT CHANGE
|
||||||
this->parent_->set_immediately_(v, this->publish_);
|
this->parent_->set_immediately_(v, this->get_publish_());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this->has_transition_()) {
|
if (!this->has_transition_()) {
|
||||||
this->parent_->target_state_reached_callback_.call();
|
this->parent_->target_state_reached_callback_.call();
|
||||||
}
|
}
|
||||||
if (this->publish_) {
|
if (this->get_publish_()) {
|
||||||
this->parent_->publish_state();
|
this->parent_->publish_state();
|
||||||
}
|
}
|
||||||
if (this->save_) {
|
if (this->get_save_()) {
|
||||||
this->parent_->save_remote_values_();
|
this->parent_->save_remote_values_();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -135,82 +152,80 @@ LightColorValues LightCall::validate_() {
|
|||||||
auto traits = this->parent_->get_traits();
|
auto traits = this->parent_->get_traits();
|
||||||
|
|
||||||
// Color mode check
|
// Color mode check
|
||||||
if (this->color_mode_.has_value() && !traits.supports_color_mode(this->color_mode_.value())) {
|
if (this->has_color_mode() && !traits.supports_color_mode(this->color_mode_)) {
|
||||||
ESP_LOGW(TAG, "'%s' does not support color mode %s", name,
|
ESP_LOGW(TAG, "'%s' does not support color mode %s", name, LOG_STR_ARG(color_mode_to_human(this->color_mode_)));
|
||||||
LOG_STR_ARG(color_mode_to_human(this->color_mode_.value())));
|
this->set_flag_(FLAG_HAS_COLOR_MODE, false);
|
||||||
this->color_mode_.reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure there is always a color mode set
|
// Ensure there is always a color mode set
|
||||||
if (!this->color_mode_.has_value()) {
|
if (!this->has_color_mode()) {
|
||||||
this->color_mode_ = this->compute_color_mode_();
|
this->color_mode_ = this->compute_color_mode_();
|
||||||
|
this->set_flag_(FLAG_HAS_COLOR_MODE, true);
|
||||||
}
|
}
|
||||||
auto color_mode = *this->color_mode_;
|
auto color_mode = this->color_mode_;
|
||||||
|
|
||||||
// Transform calls that use non-native parameters for the current mode.
|
// Transform calls that use non-native parameters for the current mode.
|
||||||
this->transform_parameters_();
|
this->transform_parameters_();
|
||||||
|
|
||||||
// Brightness exists check
|
// Brightness exists check
|
||||||
if (this->brightness_.has_value() && *this->brightness_ > 0.0f && !(color_mode & ColorCapability::BRIGHTNESS)) {
|
if (this->has_brightness() && this->brightness_ > 0.0f && !(color_mode & ColorCapability::BRIGHTNESS)) {
|
||||||
ESP_LOGW(TAG, "'%s': setting brightness not supported", name);
|
ESP_LOGW(TAG, "'%s': setting brightness not supported", name);
|
||||||
this->brightness_.reset();
|
this->set_flag_(FLAG_HAS_BRIGHTNESS, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transition length possible check
|
// Transition length possible check
|
||||||
if (this->transition_length_.has_value() && *this->transition_length_ != 0 &&
|
if (this->has_transition_() && this->transition_length_ != 0 && !(color_mode & ColorCapability::BRIGHTNESS)) {
|
||||||
!(color_mode & ColorCapability::BRIGHTNESS)) {
|
|
||||||
ESP_LOGW(TAG, "'%s': transitions not supported", name);
|
ESP_LOGW(TAG, "'%s': transitions not supported", name);
|
||||||
this->transition_length_.reset();
|
this->set_flag_(FLAG_HAS_TRANSITION, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Color brightness exists check
|
// Color brightness exists check
|
||||||
if (this->color_brightness_.has_value() && *this->color_brightness_ > 0.0f && !(color_mode & ColorCapability::RGB)) {
|
if (this->has_color_brightness() && this->color_brightness_ > 0.0f && !(color_mode & ColorCapability::RGB)) {
|
||||||
ESP_LOGW(TAG, "'%s': color mode does not support setting RGB brightness", name);
|
ESP_LOGW(TAG, "'%s': color mode does not support setting RGB brightness", name);
|
||||||
this->color_brightness_.reset();
|
this->set_flag_(FLAG_HAS_COLOR_BRIGHTNESS, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// RGB exists check
|
// RGB exists check
|
||||||
if ((this->red_.has_value() && *this->red_ > 0.0f) || (this->green_.has_value() && *this->green_ > 0.0f) ||
|
if ((this->has_red() && this->red_ > 0.0f) || (this->has_green() && this->green_ > 0.0f) ||
|
||||||
(this->blue_.has_value() && *this->blue_ > 0.0f)) {
|
(this->has_blue() && this->blue_ > 0.0f)) {
|
||||||
if (!(color_mode & ColorCapability::RGB)) {
|
if (!(color_mode & ColorCapability::RGB)) {
|
||||||
ESP_LOGW(TAG, "'%s': color mode does not support setting RGB color", name);
|
ESP_LOGW(TAG, "'%s': color mode does not support setting RGB color", name);
|
||||||
this->red_.reset();
|
this->set_flag_(FLAG_HAS_RED, false);
|
||||||
this->green_.reset();
|
this->set_flag_(FLAG_HAS_GREEN, false);
|
||||||
this->blue_.reset();
|
this->set_flag_(FLAG_HAS_BLUE, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// White value exists check
|
// White value exists check
|
||||||
if (this->white_.has_value() && *this->white_ > 0.0f &&
|
if (this->has_white() && this->white_ > 0.0f &&
|
||||||
!(color_mode & ColorCapability::WHITE || color_mode & ColorCapability::COLD_WARM_WHITE)) {
|
!(color_mode & ColorCapability::WHITE || color_mode & ColorCapability::COLD_WARM_WHITE)) {
|
||||||
ESP_LOGW(TAG, "'%s': color mode does not support setting white value", name);
|
ESP_LOGW(TAG, "'%s': color mode does not support setting white value", name);
|
||||||
this->white_.reset();
|
this->set_flag_(FLAG_HAS_WHITE, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Color temperature exists check
|
// Color temperature exists check
|
||||||
if (this->color_temperature_.has_value() &&
|
if (this->has_color_temperature() &&
|
||||||
!(color_mode & ColorCapability::COLOR_TEMPERATURE || color_mode & ColorCapability::COLD_WARM_WHITE)) {
|
!(color_mode & ColorCapability::COLOR_TEMPERATURE || color_mode & ColorCapability::COLD_WARM_WHITE)) {
|
||||||
ESP_LOGW(TAG, "'%s': color mode does not support setting color temperature", name);
|
ESP_LOGW(TAG, "'%s': color mode does not support setting color temperature", name);
|
||||||
this->color_temperature_.reset();
|
this->set_flag_(FLAG_HAS_COLOR_TEMPERATURE, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cold/warm white value exists check
|
// Cold/warm white value exists check
|
||||||
if ((this->cold_white_.has_value() && *this->cold_white_ > 0.0f) ||
|
if ((this->has_cold_white() && this->cold_white_ > 0.0f) || (this->has_warm_white() && this->warm_white_ > 0.0f)) {
|
||||||
(this->warm_white_.has_value() && *this->warm_white_ > 0.0f)) {
|
|
||||||
if (!(color_mode & ColorCapability::COLD_WARM_WHITE)) {
|
if (!(color_mode & ColorCapability::COLD_WARM_WHITE)) {
|
||||||
ESP_LOGW(TAG, "'%s': color mode does not support setting cold/warm white value", name);
|
ESP_LOGW(TAG, "'%s': color mode does not support setting cold/warm white value", name);
|
||||||
this->cold_white_.reset();
|
this->set_flag_(FLAG_HAS_COLD_WHITE, false);
|
||||||
this->warm_white_.reset();
|
this->set_flag_(FLAG_HAS_WARM_WHITE, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define VALIDATE_RANGE_(name_, upper_name, min, max) \
|
#define VALIDATE_RANGE_(name_, upper_name, min, max) \
|
||||||
if (name_##_.has_value()) { \
|
if (this->has_##name_()) { \
|
||||||
auto val = *name_##_; \
|
auto val = this->name_##_; \
|
||||||
if (val < (min) || val > (max)) { \
|
if (val < (min) || val > (max)) { \
|
||||||
ESP_LOGW(TAG, "'%s': %s value %.2f is out of range [%.1f - %.1f]", name, LOG_STR_LITERAL(upper_name), val, \
|
ESP_LOGW(TAG, "'%s': %s value %.2f is out of range [%.1f - %.1f]", name, LOG_STR_LITERAL(upper_name), val, \
|
||||||
(min), (max)); \
|
(min), (max)); \
|
||||||
name_##_ = clamp(val, (min), (max)); \
|
this->name_##_ = clamp(val, (min), (max)); \
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
#define VALIDATE_RANGE(name, upper_name) VALIDATE_RANGE_(name, upper_name, 0.0f, 1.0f)
|
#define VALIDATE_RANGE(name, upper_name) VALIDATE_RANGE_(name, upper_name, 0.0f, 1.0f)
|
||||||
@ -227,110 +242,116 @@ LightColorValues LightCall::validate_() {
|
|||||||
VALIDATE_RANGE_(color_temperature, "Color temperature", traits.get_min_mireds(), traits.get_max_mireds())
|
VALIDATE_RANGE_(color_temperature, "Color temperature", traits.get_min_mireds(), traits.get_max_mireds())
|
||||||
|
|
||||||
// Flag whether an explicit turn off was requested, in which case we'll also stop the effect.
|
// Flag whether an explicit turn off was requested, in which case we'll also stop the effect.
|
||||||
bool explicit_turn_off_request = this->state_.has_value() && !*this->state_;
|
bool explicit_turn_off_request = this->has_state() && !this->state_;
|
||||||
|
|
||||||
// Turn off when brightness is set to zero, and reset brightness (so that it has nonzero brightness when turned on).
|
// Turn off when brightness is set to zero, and reset brightness (so that it has nonzero brightness when turned on).
|
||||||
if (this->brightness_.has_value() && *this->brightness_ == 0.0f) {
|
if (this->has_brightness() && this->brightness_ == 0.0f) {
|
||||||
this->state_ = optional<float>(false);
|
this->state_ = false;
|
||||||
this->brightness_ = optional<float>(1.0f);
|
this->set_flag_(FLAG_HAS_STATE, true);
|
||||||
|
this->brightness_ = 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set color brightness to 100% if currently zero and a color is set.
|
// Set color brightness to 100% if currently zero and a color is set.
|
||||||
if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
|
if (this->has_red() || this->has_green() || this->has_blue()) {
|
||||||
if (!this->color_brightness_.has_value() && this->parent_->remote_values.get_color_brightness() == 0.0f)
|
if (!this->has_color_brightness() && this->parent_->remote_values.get_color_brightness() == 0.0f) {
|
||||||
this->color_brightness_ = optional<float>(1.0f);
|
this->color_brightness_ = 1.0f;
|
||||||
|
this->set_flag_(FLAG_HAS_COLOR_BRIGHTNESS, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create color values for the light with this call applied.
|
// Create color values for the light with this call applied.
|
||||||
auto v = this->parent_->remote_values;
|
auto v = this->parent_->remote_values;
|
||||||
if (this->color_mode_.has_value())
|
if (this->has_color_mode())
|
||||||
v.set_color_mode(*this->color_mode_);
|
v.set_color_mode(this->color_mode_);
|
||||||
if (this->state_.has_value())
|
if (this->has_state())
|
||||||
v.set_state(*this->state_);
|
v.set_state(this->state_);
|
||||||
if (this->brightness_.has_value())
|
if (this->has_brightness())
|
||||||
v.set_brightness(*this->brightness_);
|
v.set_brightness(this->brightness_);
|
||||||
if (this->color_brightness_.has_value())
|
if (this->has_color_brightness())
|
||||||
v.set_color_brightness(*this->color_brightness_);
|
v.set_color_brightness(this->color_brightness_);
|
||||||
if (this->red_.has_value())
|
if (this->has_red())
|
||||||
v.set_red(*this->red_);
|
v.set_red(this->red_);
|
||||||
if (this->green_.has_value())
|
if (this->has_green())
|
||||||
v.set_green(*this->green_);
|
v.set_green(this->green_);
|
||||||
if (this->blue_.has_value())
|
if (this->has_blue())
|
||||||
v.set_blue(*this->blue_);
|
v.set_blue(this->blue_);
|
||||||
if (this->white_.has_value())
|
if (this->has_white())
|
||||||
v.set_white(*this->white_);
|
v.set_white(this->white_);
|
||||||
if (this->color_temperature_.has_value())
|
if (this->has_color_temperature())
|
||||||
v.set_color_temperature(*this->color_temperature_);
|
v.set_color_temperature(this->color_temperature_);
|
||||||
if (this->cold_white_.has_value())
|
if (this->has_cold_white())
|
||||||
v.set_cold_white(*this->cold_white_);
|
v.set_cold_white(this->cold_white_);
|
||||||
if (this->warm_white_.has_value())
|
if (this->has_warm_white())
|
||||||
v.set_warm_white(*this->warm_white_);
|
v.set_warm_white(this->warm_white_);
|
||||||
|
|
||||||
v.normalize_color();
|
v.normalize_color();
|
||||||
|
|
||||||
// Flash length check
|
// Flash length check
|
||||||
if (this->has_flash_() && *this->flash_length_ == 0) {
|
if (this->has_flash_() && this->flash_length_ == 0) {
|
||||||
ESP_LOGW(TAG, "'%s': flash length must be greater than zero", name);
|
ESP_LOGW(TAG, "'%s': flash length must be greater than zero", name);
|
||||||
this->flash_length_.reset();
|
this->set_flag_(FLAG_HAS_FLASH, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate transition length/flash length/effect not used at the same time
|
// validate transition length/flash length/effect not used at the same time
|
||||||
bool supports_transition = color_mode & ColorCapability::BRIGHTNESS;
|
bool supports_transition = color_mode & ColorCapability::BRIGHTNESS;
|
||||||
|
|
||||||
// If effect is already active, remove effect start
|
// If effect is already active, remove effect start
|
||||||
if (this->has_effect_() && *this->effect_ == this->parent_->active_effect_index_) {
|
if (this->has_effect_() && this->effect_ == this->parent_->active_effect_index_) {
|
||||||
this->effect_.reset();
|
this->set_flag_(FLAG_HAS_EFFECT, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate effect index
|
// validate effect index
|
||||||
if (this->has_effect_() && *this->effect_ > this->parent_->effects_.size()) {
|
if (this->has_effect_() && this->effect_ > this->parent_->effects_.size()) {
|
||||||
ESP_LOGW(TAG, "'%s': invalid effect index %" PRIu32, name, *this->effect_);
|
ESP_LOGW(TAG, "'%s': invalid effect index %" PRIu32, name, this->effect_);
|
||||||
this->effect_.reset();
|
this->set_flag_(FLAG_HAS_EFFECT, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) {
|
if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) {
|
||||||
ESP_LOGW(TAG, "'%s': effect cannot be used with transition/flash", name);
|
ESP_LOGW(TAG, "'%s': effect cannot be used with transition/flash", name);
|
||||||
this->transition_length_.reset();
|
this->set_flag_(FLAG_HAS_TRANSITION, false);
|
||||||
this->flash_length_.reset();
|
this->set_flag_(FLAG_HAS_FLASH, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->has_flash_() && this->has_transition_()) {
|
if (this->has_flash_() && this->has_transition_()) {
|
||||||
ESP_LOGW(TAG, "'%s': flash cannot be used with transition", name);
|
ESP_LOGW(TAG, "'%s': flash cannot be used with transition", name);
|
||||||
this->transition_length_.reset();
|
this->set_flag_(FLAG_HAS_TRANSITION, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this->has_transition_() && !this->has_flash_() && (!this->has_effect_() || *this->effect_ == 0) &&
|
if (!this->has_transition_() && !this->has_flash_() && (!this->has_effect_() || this->effect_ == 0) &&
|
||||||
supports_transition) {
|
supports_transition) {
|
||||||
// nothing specified and light supports transitions, set default transition length
|
// nothing specified and light supports transitions, set default transition length
|
||||||
this->transition_length_ = this->parent_->default_transition_length_;
|
this->transition_length_ = this->parent_->default_transition_length_;
|
||||||
|
this->set_flag_(FLAG_HAS_TRANSITION, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->transition_length_.value_or(0) == 0) {
|
if (this->has_transition_() && this->transition_length_ == 0) {
|
||||||
// 0 transition is interpreted as no transition (instant change)
|
// 0 transition is interpreted as no transition (instant change)
|
||||||
this->transition_length_.reset();
|
this->set_flag_(FLAG_HAS_TRANSITION, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->has_transition_() && !supports_transition) {
|
if (this->has_transition_() && !supports_transition) {
|
||||||
ESP_LOGW(TAG, "'%s': transitions not supported", name);
|
ESP_LOGW(TAG, "'%s': transitions not supported", name);
|
||||||
this->transition_length_.reset();
|
this->set_flag_(FLAG_HAS_TRANSITION, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not a flash and turning the light off, then disable the light
|
// If not a flash and turning the light off, then disable the light
|
||||||
// Do not use light color values directly, so that effects can set 0% brightness
|
// Do not use light color values directly, so that effects can set 0% brightness
|
||||||
// Reason: When user turns off the light in frontend, the effect should also stop
|
// Reason: When user turns off the light in frontend, the effect should also stop
|
||||||
if (!this->has_flash_() && !this->state_.value_or(v.is_on())) {
|
bool target_state = this->has_state() ? this->state_ : v.is_on();
|
||||||
|
if (!this->has_flash_() && !target_state) {
|
||||||
if (this->has_effect_()) {
|
if (this->has_effect_()) {
|
||||||
ESP_LOGW(TAG, "'%s': cannot start effect when turning off", name);
|
ESP_LOGW(TAG, "'%s': cannot start effect when turning off", name);
|
||||||
this->effect_.reset();
|
this->set_flag_(FLAG_HAS_EFFECT, false);
|
||||||
} else if (this->parent_->active_effect_index_ != 0 && explicit_turn_off_request) {
|
} else if (this->parent_->active_effect_index_ != 0 && explicit_turn_off_request) {
|
||||||
// Auto turn off effect
|
// Auto turn off effect
|
||||||
this->effect_ = 0;
|
this->effect_ = 0;
|
||||||
|
this->set_flag_(FLAG_HAS_EFFECT, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable saving for flashes
|
// Disable saving for flashes
|
||||||
if (this->has_flash_())
|
if (this->has_flash_())
|
||||||
this->save_ = false;
|
this->set_flag_(FLAG_SAVE, false);
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
@ -343,24 +364,27 @@ void LightCall::transform_parameters_() {
|
|||||||
// - RGBWW lights with color_interlock=true, which also sets "brightness" and
|
// - RGBWW lights with color_interlock=true, which also sets "brightness" and
|
||||||
// "color_temperature" (without color_interlock, CW/WW are set directly)
|
// "color_temperature" (without color_interlock, CW/WW are set directly)
|
||||||
// - Legacy Home Assistant (pre-colormode), which sets "white" and "color_temperature"
|
// - Legacy Home Assistant (pre-colormode), which sets "white" and "color_temperature"
|
||||||
if (((this->white_.has_value() && *this->white_ > 0.0f) || this->color_temperature_.has_value()) && //
|
if (((this->has_white() && this->white_ > 0.0f) || this->has_color_temperature()) && //
|
||||||
(*this->color_mode_ & ColorCapability::COLD_WARM_WHITE) && //
|
(this->color_mode_ & ColorCapability::COLD_WARM_WHITE) && //
|
||||||
!(*this->color_mode_ & ColorCapability::WHITE) && //
|
!(this->color_mode_ & ColorCapability::WHITE) && //
|
||||||
!(*this->color_mode_ & ColorCapability::COLOR_TEMPERATURE) && //
|
!(this->color_mode_ & ColorCapability::COLOR_TEMPERATURE) && //
|
||||||
traits.get_min_mireds() > 0.0f && traits.get_max_mireds() > 0.0f) {
|
traits.get_min_mireds() > 0.0f && traits.get_max_mireds() > 0.0f) {
|
||||||
ESP_LOGD(TAG, "'%s': setting cold/warm white channels using white/color temperature values",
|
ESP_LOGD(TAG, "'%s': setting cold/warm white channels using white/color temperature values",
|
||||||
this->parent_->get_name().c_str());
|
this->parent_->get_name().c_str());
|
||||||
if (this->color_temperature_.has_value()) {
|
if (this->has_color_temperature()) {
|
||||||
const float color_temp = clamp(*this->color_temperature_, traits.get_min_mireds(), traits.get_max_mireds());
|
const float color_temp = clamp(this->color_temperature_, traits.get_min_mireds(), traits.get_max_mireds());
|
||||||
const float ww_fraction =
|
const float ww_fraction =
|
||||||
(color_temp - traits.get_min_mireds()) / (traits.get_max_mireds() - traits.get_min_mireds());
|
(color_temp - traits.get_min_mireds()) / (traits.get_max_mireds() - traits.get_min_mireds());
|
||||||
const float cw_fraction = 1.0f - ww_fraction;
|
const float cw_fraction = 1.0f - ww_fraction;
|
||||||
const float max_cw_ww = std::max(ww_fraction, cw_fraction);
|
const float max_cw_ww = std::max(ww_fraction, cw_fraction);
|
||||||
this->cold_white_ = gamma_uncorrect(cw_fraction / max_cw_ww, this->parent_->get_gamma_correct());
|
this->cold_white_ = gamma_uncorrect(cw_fraction / max_cw_ww, this->parent_->get_gamma_correct());
|
||||||
this->warm_white_ = gamma_uncorrect(ww_fraction / max_cw_ww, this->parent_->get_gamma_correct());
|
this->warm_white_ = gamma_uncorrect(ww_fraction / max_cw_ww, this->parent_->get_gamma_correct());
|
||||||
|
this->set_flag_(FLAG_HAS_COLD_WHITE, true);
|
||||||
|
this->set_flag_(FLAG_HAS_WARM_WHITE, true);
|
||||||
}
|
}
|
||||||
if (this->white_.has_value()) {
|
if (this->has_white()) {
|
||||||
this->brightness_ = *this->white_;
|
this->brightness_ = this->white_;
|
||||||
|
this->set_flag_(FLAG_HAS_BRIGHTNESS, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -378,7 +402,7 @@ ColorMode LightCall::compute_color_mode_() {
|
|||||||
|
|
||||||
// Don't change if the light is being turned off.
|
// Don't change if the light is being turned off.
|
||||||
ColorMode current_mode = this->parent_->remote_values.get_color_mode();
|
ColorMode current_mode = this->parent_->remote_values.get_color_mode();
|
||||||
if (this->state_.has_value() && !*this->state_)
|
if (this->has_state() && !this->state_)
|
||||||
return current_mode;
|
return current_mode;
|
||||||
|
|
||||||
// If no color mode is specified, we try to guess the color mode. This is needed for backward compatibility to
|
// If no color mode is specified, we try to guess the color mode. This is needed for backward compatibility to
|
||||||
@ -411,12 +435,12 @@ ColorMode LightCall::compute_color_mode_() {
|
|||||||
return color_mode;
|
return color_mode;
|
||||||
}
|
}
|
||||||
std::set<ColorMode> LightCall::get_suitable_color_modes_() {
|
std::set<ColorMode> LightCall::get_suitable_color_modes_() {
|
||||||
bool has_white = this->white_.has_value() && *this->white_ > 0.0f;
|
bool has_white = this->has_white() && this->white_ > 0.0f;
|
||||||
bool has_ct = this->color_temperature_.has_value();
|
bool has_ct = this->has_color_temperature();
|
||||||
bool has_cwww = (this->cold_white_.has_value() && *this->cold_white_ > 0.0f) ||
|
bool has_cwww =
|
||||||
(this->warm_white_.has_value() && *this->warm_white_ > 0.0f);
|
(this->has_cold_white() && this->cold_white_ > 0.0f) || (this->has_warm_white() && this->warm_white_ > 0.0f);
|
||||||
bool has_rgb = (this->color_brightness_.has_value() && *this->color_brightness_ > 0.0f) ||
|
bool has_rgb = (this->has_color_brightness() && this->color_brightness_ > 0.0f) ||
|
||||||
(this->red_.has_value() || this->green_.has_value() || this->blue_.has_value());
|
(this->has_red() || this->has_green() || this->has_blue());
|
||||||
|
|
||||||
#define KEY(white, ct, cwww, rgb) ((white) << 0 | (ct) << 1 | (cwww) << 2 | (rgb) << 3)
|
#define KEY(white, ct, cwww, rgb) ((white) << 0 | (ct) << 1 | (cwww) << 2 | (rgb) << 3)
|
||||||
#define ENTRY(white, ct, cwww, rgb, ...) \
|
#define ENTRY(white, ct, cwww, rgb, ...) \
|
||||||
@ -491,7 +515,7 @@ LightCall &LightCall::from_light_color_values(const LightColorValues &values) {
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
ColorMode LightCall::get_active_color_mode_() {
|
ColorMode LightCall::get_active_color_mode_() {
|
||||||
return this->color_mode_.value_or(this->parent_->remote_values.get_color_mode());
|
return this->has_color_mode() ? this->color_mode_ : this->parent_->remote_values.get_color_mode();
|
||||||
}
|
}
|
||||||
LightCall &LightCall::set_transition_length_if_supported(uint32_t transition_length) {
|
LightCall &LightCall::set_transition_length_if_supported(uint32_t transition_length) {
|
||||||
if (this->get_active_color_mode_() & ColorCapability::BRIGHTNESS)
|
if (this->get_active_color_mode_() & ColorCapability::BRIGHTNESS)
|
||||||
@ -505,7 +529,7 @@ LightCall &LightCall::set_brightness_if_supported(float brightness) {
|
|||||||
}
|
}
|
||||||
LightCall &LightCall::set_color_mode_if_supported(ColorMode color_mode) {
|
LightCall &LightCall::set_color_mode_if_supported(ColorMode color_mode) {
|
||||||
if (this->parent_->get_traits().supports_color_mode(color_mode))
|
if (this->parent_->get_traits().supports_color_mode(color_mode))
|
||||||
this->color_mode_ = color_mode;
|
this->set_color_mode(color_mode);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
LightCall &LightCall::set_color_brightness_if_supported(float brightness) {
|
LightCall &LightCall::set_color_brightness_if_supported(float brightness) {
|
||||||
@ -549,110 +573,19 @@ LightCall &LightCall::set_warm_white_if_supported(float warm_white) {
|
|||||||
this->set_warm_white(warm_white);
|
this->set_warm_white(warm_white);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
LightCall &LightCall::set_state(optional<bool> state) {
|
IMPLEMENT_LIGHT_CALL_SETTER(state, bool, FLAG_HAS_STATE)
|
||||||
this->state_ = state;
|
IMPLEMENT_LIGHT_CALL_SETTER(transition_length, uint32_t, FLAG_HAS_TRANSITION)
|
||||||
return *this;
|
IMPLEMENT_LIGHT_CALL_SETTER(flash_length, uint32_t, FLAG_HAS_FLASH)
|
||||||
}
|
IMPLEMENT_LIGHT_CALL_SETTER(brightness, float, FLAG_HAS_BRIGHTNESS)
|
||||||
LightCall &LightCall::set_state(bool state) {
|
IMPLEMENT_LIGHT_CALL_SETTER(color_mode, ColorMode, FLAG_HAS_COLOR_MODE)
|
||||||
this->state_ = state;
|
IMPLEMENT_LIGHT_CALL_SETTER(color_brightness, float, FLAG_HAS_COLOR_BRIGHTNESS)
|
||||||
return *this;
|
IMPLEMENT_LIGHT_CALL_SETTER(red, float, FLAG_HAS_RED)
|
||||||
}
|
IMPLEMENT_LIGHT_CALL_SETTER(green, float, FLAG_HAS_GREEN)
|
||||||
LightCall &LightCall::set_transition_length(optional<uint32_t> transition_length) {
|
IMPLEMENT_LIGHT_CALL_SETTER(blue, float, FLAG_HAS_BLUE)
|
||||||
this->transition_length_ = transition_length;
|
IMPLEMENT_LIGHT_CALL_SETTER(white, float, FLAG_HAS_WHITE)
|
||||||
return *this;
|
IMPLEMENT_LIGHT_CALL_SETTER(color_temperature, float, FLAG_HAS_COLOR_TEMPERATURE)
|
||||||
}
|
IMPLEMENT_LIGHT_CALL_SETTER(cold_white, float, FLAG_HAS_COLD_WHITE)
|
||||||
LightCall &LightCall::set_transition_length(uint32_t transition_length) {
|
IMPLEMENT_LIGHT_CALL_SETTER(warm_white, float, FLAG_HAS_WARM_WHITE)
|
||||||
this->transition_length_ = transition_length;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_flash_length(optional<uint32_t> flash_length) {
|
|
||||||
this->flash_length_ = flash_length;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_flash_length(uint32_t flash_length) {
|
|
||||||
this->flash_length_ = flash_length;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_brightness(optional<float> brightness) {
|
|
||||||
this->brightness_ = brightness;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_brightness(float brightness) {
|
|
||||||
this->brightness_ = brightness;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_color_mode(optional<ColorMode> color_mode) {
|
|
||||||
this->color_mode_ = color_mode;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_color_mode(ColorMode color_mode) {
|
|
||||||
this->color_mode_ = color_mode;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_color_brightness(optional<float> brightness) {
|
|
||||||
this->color_brightness_ = brightness;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_color_brightness(float brightness) {
|
|
||||||
this->color_brightness_ = brightness;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_red(optional<float> red) {
|
|
||||||
this->red_ = red;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_red(float red) {
|
|
||||||
this->red_ = red;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_green(optional<float> green) {
|
|
||||||
this->green_ = green;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_green(float green) {
|
|
||||||
this->green_ = green;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_blue(optional<float> blue) {
|
|
||||||
this->blue_ = blue;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_blue(float blue) {
|
|
||||||
this->blue_ = blue;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_white(optional<float> white) {
|
|
||||||
this->white_ = white;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_white(float white) {
|
|
||||||
this->white_ = white;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_color_temperature(optional<float> color_temperature) {
|
|
||||||
this->color_temperature_ = color_temperature;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_color_temperature(float color_temperature) {
|
|
||||||
this->color_temperature_ = color_temperature;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_cold_white(optional<float> cold_white) {
|
|
||||||
this->cold_white_ = cold_white;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_cold_white(float cold_white) {
|
|
||||||
this->cold_white_ = cold_white;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_warm_white(optional<float> warm_white) {
|
|
||||||
this->warm_white_ = warm_white;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_warm_white(float warm_white) {
|
|
||||||
this->warm_white_ = warm_white;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_effect(optional<std::string> effect) {
|
LightCall &LightCall::set_effect(optional<std::string> effect) {
|
||||||
if (effect.has_value())
|
if (effect.has_value())
|
||||||
this->set_effect(*effect);
|
this->set_effect(*effect);
|
||||||
@ -660,18 +593,22 @@ LightCall &LightCall::set_effect(optional<std::string> effect) {
|
|||||||
}
|
}
|
||||||
LightCall &LightCall::set_effect(uint32_t effect_number) {
|
LightCall &LightCall::set_effect(uint32_t effect_number) {
|
||||||
this->effect_ = effect_number;
|
this->effect_ = effect_number;
|
||||||
|
this->set_flag_(FLAG_HAS_EFFECT, true);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
LightCall &LightCall::set_effect(optional<uint32_t> effect_number) {
|
LightCall &LightCall::set_effect(optional<uint32_t> effect_number) {
|
||||||
this->effect_ = effect_number;
|
if (effect_number.has_value()) {
|
||||||
|
this->effect_ = effect_number.value();
|
||||||
|
}
|
||||||
|
this->set_flag_(FLAG_HAS_EFFECT, effect_number.has_value());
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
LightCall &LightCall::set_publish(bool publish) {
|
LightCall &LightCall::set_publish(bool publish) {
|
||||||
this->publish_ = publish;
|
this->set_flag_(FLAG_PUBLISH, publish);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
LightCall &LightCall::set_save(bool save) {
|
LightCall &LightCall::set_save(bool save) {
|
||||||
this->save_ = save;
|
this->set_flag_(FLAG_SAVE, save);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
LightCall &LightCall::set_rgb(float red, float green, float blue) {
|
LightCall &LightCall::set_rgb(float red, float green, float blue) {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/core/optional.h"
|
|
||||||
#include "light_color_values.h"
|
#include "light_color_values.h"
|
||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
@ -10,6 +9,11 @@ namespace light {
|
|||||||
class LightState;
|
class LightState;
|
||||||
|
|
||||||
/** This class represents a requested change in a light state.
|
/** This class represents a requested change in a light state.
|
||||||
|
*
|
||||||
|
* Light state changes are tracked using a bitfield flags_ to minimize memory usage.
|
||||||
|
* Each possible light property has a flag indicating whether it has been set.
|
||||||
|
* This design keeps LightCall at ~56 bytes to minimize heap fragmentation on
|
||||||
|
* ESP8266 and other memory-constrained devices.
|
||||||
*/
|
*/
|
||||||
class LightCall {
|
class LightCall {
|
||||||
public:
|
public:
|
||||||
@ -131,6 +135,19 @@ class LightCall {
|
|||||||
/// Set whether this light call should trigger a save state to recover them at startup..
|
/// Set whether this light call should trigger a save state to recover them at startup..
|
||||||
LightCall &set_save(bool save);
|
LightCall &set_save(bool save);
|
||||||
|
|
||||||
|
// Getter methods to check if values are set
|
||||||
|
bool has_state() const { return (flags_ & FLAG_HAS_STATE) != 0; }
|
||||||
|
bool has_brightness() const { return (flags_ & FLAG_HAS_BRIGHTNESS) != 0; }
|
||||||
|
bool has_color_brightness() const { return (flags_ & FLAG_HAS_COLOR_BRIGHTNESS) != 0; }
|
||||||
|
bool has_red() const { return (flags_ & FLAG_HAS_RED) != 0; }
|
||||||
|
bool has_green() const { return (flags_ & FLAG_HAS_GREEN) != 0; }
|
||||||
|
bool has_blue() const { return (flags_ & FLAG_HAS_BLUE) != 0; }
|
||||||
|
bool has_white() const { return (flags_ & FLAG_HAS_WHITE) != 0; }
|
||||||
|
bool has_color_temperature() const { return (flags_ & FLAG_HAS_COLOR_TEMPERATURE) != 0; }
|
||||||
|
bool has_cold_white() const { return (flags_ & FLAG_HAS_COLD_WHITE) != 0; }
|
||||||
|
bool has_warm_white() const { return (flags_ & FLAG_HAS_WARM_WHITE) != 0; }
|
||||||
|
bool has_color_mode() const { return (flags_ & FLAG_HAS_COLOR_MODE) != 0; }
|
||||||
|
|
||||||
/** Set the RGB color of the light by RGB values.
|
/** Set the RGB color of the light by RGB values.
|
||||||
*
|
*
|
||||||
* Please note that this only changes the color of the light, not the brightness.
|
* Please note that this only changes the color of the light, not the brightness.
|
||||||
@ -170,27 +187,62 @@ class LightCall {
|
|||||||
/// Some color modes also can be set using non-native parameters, transform those calls.
|
/// Some color modes also can be set using non-native parameters, transform those calls.
|
||||||
void transform_parameters_();
|
void transform_parameters_();
|
||||||
|
|
||||||
bool has_transition_() { return this->transition_length_.has_value(); }
|
// Bitfield flags - each flag indicates whether a corresponding value has been set.
|
||||||
bool has_flash_() { return this->flash_length_.has_value(); }
|
enum FieldFlags : uint16_t {
|
||||||
bool has_effect_() { return this->effect_.has_value(); }
|
FLAG_HAS_STATE = 1 << 0,
|
||||||
|
FLAG_HAS_TRANSITION = 1 << 1,
|
||||||
|
FLAG_HAS_FLASH = 1 << 2,
|
||||||
|
FLAG_HAS_EFFECT = 1 << 3,
|
||||||
|
FLAG_HAS_BRIGHTNESS = 1 << 4,
|
||||||
|
FLAG_HAS_COLOR_BRIGHTNESS = 1 << 5,
|
||||||
|
FLAG_HAS_RED = 1 << 6,
|
||||||
|
FLAG_HAS_GREEN = 1 << 7,
|
||||||
|
FLAG_HAS_BLUE = 1 << 8,
|
||||||
|
FLAG_HAS_WHITE = 1 << 9,
|
||||||
|
FLAG_HAS_COLOR_TEMPERATURE = 1 << 10,
|
||||||
|
FLAG_HAS_COLD_WHITE = 1 << 11,
|
||||||
|
FLAG_HAS_WARM_WHITE = 1 << 12,
|
||||||
|
FLAG_HAS_COLOR_MODE = 1 << 13,
|
||||||
|
FLAG_PUBLISH = 1 << 14,
|
||||||
|
FLAG_SAVE = 1 << 15,
|
||||||
|
};
|
||||||
|
|
||||||
|
bool has_transition_() { return (this->flags_ & FLAG_HAS_TRANSITION) != 0; }
|
||||||
|
bool has_flash_() { return (this->flags_ & FLAG_HAS_FLASH) != 0; }
|
||||||
|
bool has_effect_() { return (this->flags_ & FLAG_HAS_EFFECT) != 0; }
|
||||||
|
bool get_publish_() { return (this->flags_ & FLAG_PUBLISH) != 0; }
|
||||||
|
bool get_save_() { return (this->flags_ & FLAG_SAVE) != 0; }
|
||||||
|
|
||||||
|
// Helper to set flag
|
||||||
|
void set_flag_(FieldFlags flag, bool value) {
|
||||||
|
if (value) {
|
||||||
|
this->flags_ |= flag;
|
||||||
|
} else {
|
||||||
|
this->flags_ &= ~flag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LightState *parent_;
|
LightState *parent_;
|
||||||
optional<bool> state_;
|
|
||||||
optional<uint32_t> transition_length_;
|
// Light state values - use flags_ to check if a value has been set.
|
||||||
optional<uint32_t> flash_length_;
|
// Group 4-byte aligned members first
|
||||||
optional<ColorMode> color_mode_;
|
uint32_t transition_length_;
|
||||||
optional<float> brightness_;
|
uint32_t flash_length_;
|
||||||
optional<float> color_brightness_;
|
uint32_t effect_;
|
||||||
optional<float> red_;
|
float brightness_;
|
||||||
optional<float> green_;
|
float color_brightness_;
|
||||||
optional<float> blue_;
|
float red_;
|
||||||
optional<float> white_;
|
float green_;
|
||||||
optional<float> color_temperature_;
|
float blue_;
|
||||||
optional<float> cold_white_;
|
float white_;
|
||||||
optional<float> warm_white_;
|
float color_temperature_;
|
||||||
optional<uint32_t> effect_;
|
float cold_white_;
|
||||||
bool publish_{true};
|
float warm_white_;
|
||||||
bool save_{true};
|
|
||||||
|
// Smaller members at the end for better packing
|
||||||
|
uint16_t flags_{FLAG_PUBLISH | FLAG_SAVE}; // Tracks which values are set
|
||||||
|
ColorMode color_mode_;
|
||||||
|
bool state_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace light
|
} // namespace light
|
||||||
|
@ -46,8 +46,7 @@ class LightColorValues {
|
|||||||
public:
|
public:
|
||||||
/// Construct the LightColorValues with all attributes enabled, but state set to off.
|
/// Construct the LightColorValues with all attributes enabled, but state set to off.
|
||||||
LightColorValues()
|
LightColorValues()
|
||||||
: color_mode_(ColorMode::UNKNOWN),
|
: state_(0.0f),
|
||||||
state_(0.0f),
|
|
||||||
brightness_(1.0f),
|
brightness_(1.0f),
|
||||||
color_brightness_(1.0f),
|
color_brightness_(1.0f),
|
||||||
red_(1.0f),
|
red_(1.0f),
|
||||||
@ -56,7 +55,8 @@ class LightColorValues {
|
|||||||
white_(1.0f),
|
white_(1.0f),
|
||||||
color_temperature_{0.0f},
|
color_temperature_{0.0f},
|
||||||
cold_white_{1.0f},
|
cold_white_{1.0f},
|
||||||
warm_white_{1.0f} {}
|
warm_white_{1.0f},
|
||||||
|
color_mode_(ColorMode::UNKNOWN) {}
|
||||||
|
|
||||||
LightColorValues(ColorMode color_mode, float state, float brightness, float color_brightness, float red, float green,
|
LightColorValues(ColorMode color_mode, float state, float brightness, float color_brightness, float red, float green,
|
||||||
float blue, float white, float color_temperature, float cold_white, float warm_white) {
|
float blue, float white, float color_temperature, float cold_white, float warm_white) {
|
||||||
@ -292,7 +292,6 @@ class LightColorValues {
|
|||||||
void set_warm_white(float warm_white) { this->warm_white_ = clamp(warm_white, 0.0f, 1.0f); }
|
void set_warm_white(float warm_white) { this->warm_white_ = clamp(warm_white, 0.0f, 1.0f); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
ColorMode color_mode_;
|
|
||||||
float state_; ///< ON / OFF, float for transition
|
float state_; ///< ON / OFF, float for transition
|
||||||
float brightness_;
|
float brightness_;
|
||||||
float color_brightness_;
|
float color_brightness_;
|
||||||
@ -303,6 +302,7 @@ class LightColorValues {
|
|||||||
float color_temperature_; ///< Color Temperature in Mired
|
float color_temperature_; ///< Color Temperature in Mired
|
||||||
float cold_white_;
|
float cold_white_;
|
||||||
float warm_white_;
|
float warm_white_;
|
||||||
|
ColorMode color_mode_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace light
|
} // namespace light
|
||||||
|
@ -31,9 +31,7 @@ enum LightRestoreMode : uint8_t {
|
|||||||
struct LightStateRTCState {
|
struct LightStateRTCState {
|
||||||
LightStateRTCState(ColorMode color_mode, bool state, float brightness, float color_brightness, float red, float green,
|
LightStateRTCState(ColorMode color_mode, bool state, float brightness, float color_brightness, float red, float green,
|
||||||
float blue, float white, float color_temp, float cold_white, float warm_white)
|
float blue, float white, float color_temp, float cold_white, float warm_white)
|
||||||
: color_mode(color_mode),
|
: brightness(brightness),
|
||||||
state(state),
|
|
||||||
brightness(brightness),
|
|
||||||
color_brightness(color_brightness),
|
color_brightness(color_brightness),
|
||||||
red(red),
|
red(red),
|
||||||
green(green),
|
green(green),
|
||||||
@ -41,10 +39,12 @@ struct LightStateRTCState {
|
|||||||
white(white),
|
white(white),
|
||||||
color_temp(color_temp),
|
color_temp(color_temp),
|
||||||
cold_white(cold_white),
|
cold_white(cold_white),
|
||||||
warm_white(warm_white) {}
|
warm_white(warm_white),
|
||||||
|
effect(0),
|
||||||
|
color_mode(color_mode),
|
||||||
|
state(state) {}
|
||||||
LightStateRTCState() = default;
|
LightStateRTCState() = default;
|
||||||
ColorMode color_mode{ColorMode::UNKNOWN};
|
// Group 4-byte aligned members first
|
||||||
bool state{false};
|
|
||||||
float brightness{1.0f};
|
float brightness{1.0f};
|
||||||
float color_brightness{1.0f};
|
float color_brightness{1.0f};
|
||||||
float red{1.0f};
|
float red{1.0f};
|
||||||
@ -55,6 +55,9 @@ struct LightStateRTCState {
|
|||||||
float cold_white{1.0f};
|
float cold_white{1.0f};
|
||||||
float warm_white{1.0f};
|
float warm_white{1.0f};
|
||||||
uint32_t effect{0};
|
uint32_t effect{0};
|
||||||
|
// Group smaller members at the end
|
||||||
|
ColorMode color_mode{ColorMode::UNKNOWN};
|
||||||
|
bool state{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
/** This class represents the communication layer between the front-end MQTT layer and the
|
/** This class represents the communication layer between the front-end MQTT layer and the
|
||||||
@ -216,6 +219,8 @@ class LightState : public EntityBase, public Component {
|
|||||||
std::unique_ptr<LightTransformer> transformer_{nullptr};
|
std::unique_ptr<LightTransformer> transformer_{nullptr};
|
||||||
/// List of effects for this light.
|
/// List of effects for this light.
|
||||||
std::vector<LightEffect *> effects_;
|
std::vector<LightEffect *> effects_;
|
||||||
|
/// Object used to store the persisted values of the light.
|
||||||
|
ESPPreferenceObject rtc_;
|
||||||
/// Value for storing the index of the currently active effect. 0 if no effect is active
|
/// Value for storing the index of the currently active effect. 0 if no effect is active
|
||||||
uint32_t active_effect_index_{};
|
uint32_t active_effect_index_{};
|
||||||
/// Default transition length for all transitions in ms.
|
/// Default transition length for all transitions in ms.
|
||||||
@ -224,15 +229,11 @@ class LightState : public EntityBase, public Component {
|
|||||||
uint32_t flash_transition_length_{};
|
uint32_t flash_transition_length_{};
|
||||||
/// Gamma correction factor for the light.
|
/// Gamma correction factor for the light.
|
||||||
float gamma_correct_{};
|
float gamma_correct_{};
|
||||||
|
|
||||||
/// Whether the light value should be written in the next cycle.
|
/// Whether the light value should be written in the next cycle.
|
||||||
bool next_write_{true};
|
bool next_write_{true};
|
||||||
// for effects, true if a transformer (transition) is active.
|
// for effects, true if a transformer (transition) is active.
|
||||||
bool is_transformer_active_ = false;
|
bool is_transformer_active_ = false;
|
||||||
|
|
||||||
/// Object used to store the persisted values of the light.
|
|
||||||
ESPPreferenceObject rtc_;
|
|
||||||
|
|
||||||
/** Callback to call when new values for the frontend are available.
|
/** Callback to call when new values for the frontend are available.
|
||||||
*
|
*
|
||||||
* "Remote values" are light color values that are reported to the frontend and have a lower
|
* "Remote values" are light color values that are reported to the frontend and have a lower
|
||||||
|
@ -59,9 +59,9 @@ class LightTransitionTransformer : public LightTransformer {
|
|||||||
// transition from 0 to 1 on x = [0, 1]
|
// transition from 0 to 1 on x = [0, 1]
|
||||||
static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); }
|
static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); }
|
||||||
|
|
||||||
bool changing_color_mode_{false};
|
|
||||||
LightColorValues end_values_{};
|
LightColorValues end_values_{};
|
||||||
LightColorValues intermediate_values_{};
|
LightColorValues intermediate_values_{};
|
||||||
|
bool changing_color_mode_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
class LightFlashTransformer : public LightTransformer {
|
class LightFlashTransformer : public LightTransformer {
|
||||||
@ -117,8 +117,8 @@ class LightFlashTransformer : public LightTransformer {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
LightState &state_;
|
LightState &state_;
|
||||||
uint32_t transition_length_;
|
|
||||||
std::unique_ptr<LightTransformer> transformer_{nullptr};
|
std::unique_ptr<LightTransformer> transformer_{nullptr};
|
||||||
|
uint32_t transition_length_;
|
||||||
bool begun_lightstate_restore_;
|
bool begun_lightstate_restore_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ from esphome.components.libretiny.const import (
|
|||||||
COMPONENT_LN882X,
|
COMPONENT_LN882X,
|
||||||
COMPONENT_RTL87XX,
|
COMPONENT_RTL87XX,
|
||||||
)
|
)
|
||||||
|
from esphome.config_helpers import filter_source_files_from_platform
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ARGS,
|
CONF_ARGS,
|
||||||
@ -42,6 +43,7 @@ from esphome.const import (
|
|||||||
PLATFORM_LN882X,
|
PLATFORM_LN882X,
|
||||||
PLATFORM_RP2040,
|
PLATFORM_RP2040,
|
||||||
PLATFORM_RTL87XX,
|
PLATFORM_RTL87XX,
|
||||||
|
PlatformFramework,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, Lambda, coroutine_with_priority
|
from esphome.core import CORE, Lambda, coroutine_with_priority
|
||||||
|
|
||||||
@ -444,3 +446,25 @@ async def logger_set_level_to_code(config, action_id, template_arg, args):
|
|||||||
|
|
||||||
lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void)
|
lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void)
|
||||||
return cg.new_Pvariable(action_id, template_arg, lambda_)
|
return cg.new_Pvariable(action_id, template_arg, lambda_)
|
||||||
|
|
||||||
|
|
||||||
|
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||||
|
{
|
||||||
|
"logger_esp32.cpp": {
|
||||||
|
PlatformFramework.ESP32_ARDUINO,
|
||||||
|
PlatformFramework.ESP32_IDF,
|
||||||
|
},
|
||||||
|
"logger_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
|
||||||
|
"logger_host.cpp": {PlatformFramework.HOST_NATIVE},
|
||||||
|
"logger_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
|
||||||
|
"logger_libretiny.cpp": {
|
||||||
|
PlatformFramework.BK72XX_ARDUINO,
|
||||||
|
PlatformFramework.RTL87XX_ARDUINO,
|
||||||
|
PlatformFramework.LN882X_ARDUINO,
|
||||||
|
},
|
||||||
|
"task_log_buffer.cpp": {
|
||||||
|
PlatformFramework.ESP32_ARDUINO,
|
||||||
|
PlatformFramework.ESP32_IDF,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -90,6 +90,25 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
|
|||||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||||
// Implementation for ESP8266 with flash string support.
|
// Implementation for ESP8266 with flash string support.
|
||||||
// Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266.
|
// Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266.
|
||||||
|
//
|
||||||
|
// This function handles format strings stored in flash memory (PROGMEM) to save RAM.
|
||||||
|
// The buffer is used in a special way to avoid allocating extra memory:
|
||||||
|
//
|
||||||
|
// Memory layout during execution:
|
||||||
|
// Step 1: Copy format string from flash to buffer
|
||||||
|
// tx_buffer_: [format_string][null][.....................]
|
||||||
|
// tx_buffer_at_: ------------------^
|
||||||
|
// msg_start: saved here -----------^
|
||||||
|
//
|
||||||
|
// Step 2: format_log_to_buffer_with_terminator_ reads format string from beginning
|
||||||
|
// and writes formatted output starting at msg_start position
|
||||||
|
// tx_buffer_: [format_string][null][formatted_message][null]
|
||||||
|
// tx_buffer_at_: -------------------------------------^
|
||||||
|
//
|
||||||
|
// Step 3: Output the formatted message (starting at msg_start)
|
||||||
|
// write_msg_ and callbacks receive: this->tx_buffer_ + msg_start
|
||||||
|
// which points to: [formatted_message][null]
|
||||||
|
//
|
||||||
void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format,
|
void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format,
|
||||||
va_list args) { // NOLINT
|
va_list args) { // NOLINT
|
||||||
if (level > this->level_for(tag) || global_recursion_guard_)
|
if (level > this->level_for(tag) || global_recursion_guard_)
|
||||||
@ -121,7 +140,9 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
|
|||||||
if (this->baud_rate_ > 0) {
|
if (this->baud_rate_ > 0) {
|
||||||
this->write_msg_(this->tx_buffer_ + msg_start);
|
this->write_msg_(this->tx_buffer_ + msg_start);
|
||||||
}
|
}
|
||||||
this->log_callback_.call(level, tag, this->tx_buffer_ + msg_start);
|
size_t msg_length =
|
||||||
|
this->tx_buffer_at_ - msg_start; // Don't subtract 1 - tx_buffer_at_ is already at the null terminator position
|
||||||
|
this->log_callback_.call(level, tag, this->tx_buffer_ + msg_start, msg_length);
|
||||||
|
|
||||||
global_recursion_guard_ = false;
|
global_recursion_guard_ = false;
|
||||||
}
|
}
|
||||||
@ -185,7 +206,8 @@ void Logger::loop() {
|
|||||||
this->tx_buffer_size_);
|
this->tx_buffer_size_);
|
||||||
this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_);
|
this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_);
|
||||||
this->tx_buffer_[this->tx_buffer_at_] = '\0';
|
this->tx_buffer_[this->tx_buffer_at_] = '\0';
|
||||||
this->log_callback_.call(message->level, message->tag, this->tx_buffer_);
|
size_t msg_len = this->tx_buffer_at_; // We already know the length from tx_buffer_at_
|
||||||
|
this->log_callback_.call(message->level, message->tag, this->tx_buffer_, msg_len);
|
||||||
// At this point all the data we need from message has been transferred to the tx_buffer
|
// At this point all the data we need from message has been transferred to the tx_buffer
|
||||||
// so we can release the message to allow other tasks to use it as soon as possible.
|
// so we can release the message to allow other tasks to use it as soon as possible.
|
||||||
this->log_buffer_->release_message_main_loop(received_token);
|
this->log_buffer_->release_message_main_loop(received_token);
|
||||||
@ -214,7 +236,7 @@ void Logger::set_log_level(const std::string &tag, uint8_t log_level) { this->lo
|
|||||||
UARTSelection Logger::get_uart() const { return this->uart_; }
|
UARTSelection Logger::get_uart() const { return this->uart_; }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void Logger::add_on_log_callback(std::function<void(uint8_t, const char *, const char *)> &&callback) {
|
void Logger::add_on_log_callback(std::function<void(uint8_t, const char *, const char *, size_t)> &&callback) {
|
||||||
this->log_callback_.add(std::move(callback));
|
this->log_callback_.add(std::move(callback));
|
||||||
}
|
}
|
||||||
float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
|
float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
|
||||||
|
@ -143,7 +143,7 @@ class Logger : public Component {
|
|||||||
inline uint8_t level_for(const char *tag);
|
inline uint8_t level_for(const char *tag);
|
||||||
|
|
||||||
/// Register a callback that will be called for every log message sent
|
/// Register a callback that will be called for every log message sent
|
||||||
void add_on_log_callback(std::function<void(uint8_t, const char *, const char *)> &&callback);
|
void add_on_log_callback(std::function<void(uint8_t, const char *, const char *, size_t)> &&callback);
|
||||||
|
|
||||||
// add a listener for log level changes
|
// add a listener for log level changes
|
||||||
void add_listener(std::function<void(uint8_t)> &&callback) { this->level_callback_.add(std::move(callback)); }
|
void add_listener(std::function<void(uint8_t)> &&callback) { this->level_callback_.add(std::move(callback)); }
|
||||||
@ -192,7 +192,7 @@ class Logger : public Component {
|
|||||||
if (this->baud_rate_ > 0) {
|
if (this->baud_rate_ > 0) {
|
||||||
this->write_msg_(this->tx_buffer_); // If logging is enabled, write to console
|
this->write_msg_(this->tx_buffer_); // If logging is enabled, write to console
|
||||||
}
|
}
|
||||||
this->log_callback_.call(level, tag, this->tx_buffer_);
|
this->log_callback_.call(level, tag, this->tx_buffer_, this->tx_buffer_at_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the body of the log message to the buffer
|
// Write the body of the log message to the buffer
|
||||||
@ -246,7 +246,7 @@ class Logger : public Component {
|
|||||||
|
|
||||||
// Large objects (internally aligned)
|
// Large objects (internally aligned)
|
||||||
std::map<std::string, uint8_t> log_levels_{};
|
std::map<std::string, uint8_t> log_levels_{};
|
||||||
CallbackManager<void(uint8_t, const char *, const char *)> log_callback_{};
|
CallbackManager<void(uint8_t, const char *, const char *, size_t)> log_callback_{};
|
||||||
CallbackManager<void(uint8_t)> level_callback_{};
|
CallbackManager<void(uint8_t)> level_callback_{};
|
||||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||||
std::unique_ptr<logger::TaskLogBuffer> log_buffer_; // Will be initialized with init_log_buffer
|
std::unique_ptr<logger::TaskLogBuffer> log_buffer_; // Will be initialized with init_log_buffer
|
||||||
@ -355,7 +355,7 @@ class Logger : public Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline void HOT write_footer_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
|
inline void HOT write_footer_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
|
||||||
static const uint16_t RESET_COLOR_LEN = strlen(ESPHOME_LOG_RESET_COLOR);
|
static constexpr uint16_t RESET_COLOR_LEN = sizeof(ESPHOME_LOG_RESET_COLOR) - 1;
|
||||||
this->write_body_to_buffer_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN, buffer, buffer_at, buffer_size);
|
this->write_body_to_buffer_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN, buffer, buffer_at, buffer_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -385,7 +385,7 @@ class LoggerMessageTrigger : public Trigger<uint8_t, const char *, const char *>
|
|||||||
public:
|
public:
|
||||||
explicit LoggerMessageTrigger(Logger *parent, uint8_t level) {
|
explicit LoggerMessageTrigger(Logger *parent, uint8_t level) {
|
||||||
this->level_ = level;
|
this->level_ = level;
|
||||||
parent->add_on_log_callback([this](uint8_t level, const char *tag, const char *message) {
|
parent->add_on_log_callback([this](uint8_t level, const char *tag, const char *message, size_t message_len) {
|
||||||
if (level <= this->level_) {
|
if (level <= this->level_) {
|
||||||
this->trigger(level, tag, message);
|
this->trigger(level, tag, message);
|
||||||
}
|
}
|
||||||
|
@ -184,7 +184,9 @@ void HOT Logger::write_msg_(const char *msg) {
|
|||||||
) {
|
) {
|
||||||
puts(msg);
|
puts(msg);
|
||||||
} else {
|
} else {
|
||||||
uart_write_bytes(this->uart_num_, msg, strlen(msg));
|
// Use tx_buffer_at_ if msg points to tx_buffer_, otherwise fall back to strlen
|
||||||
|
size_t len = (msg == this->tx_buffer_) ? this->tx_buffer_at_ : strlen(msg);
|
||||||
|
uart_write_bytes(this->uart_num_, msg, len);
|
||||||
uart_write_bytes(this->uart_num_, "\n", 1);
|
uart_write_bytes(this->uart_num_, "\n", 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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))
|
@ -1,5 +1,6 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components.esp32 import add_idf_component
|
from esphome.components.esp32 import add_idf_component
|
||||||
|
from esphome.config_helpers import filter_source_files_from_platform
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_DISABLED,
|
CONF_DISABLED,
|
||||||
@ -8,6 +9,7 @@ from esphome.const import (
|
|||||||
CONF_PROTOCOL,
|
CONF_PROTOCOL,
|
||||||
CONF_SERVICE,
|
CONF_SERVICE,
|
||||||
CONF_SERVICES,
|
CONF_SERVICES,
|
||||||
|
PlatformFramework,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
|
|
||||||
@ -108,3 +110,21 @@ async def to_code(config):
|
|||||||
)
|
)
|
||||||
|
|
||||||
cg.add(var.add_extra_service(exp))
|
cg.add(var.add_extra_service(exp))
|
||||||
|
|
||||||
|
|
||||||
|
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||||
|
{
|
||||||
|
"mdns_esp32.cpp": {
|
||||||
|
PlatformFramework.ESP32_ARDUINO,
|
||||||
|
PlatformFramework.ESP32_IDF,
|
||||||
|
},
|
||||||
|
"mdns_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
|
||||||
|
"mdns_host.cpp": {PlatformFramework.HOST_NATIVE},
|
||||||
|
"mdns_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
|
||||||
|
"mdns_libretiny.cpp": {
|
||||||
|
PlatformFramework.BK72XX_ARDUINO,
|
||||||
|
PlatformFramework.RTL87XX_ARDUINO,
|
||||||
|
PlatformFramework.LN882X_ARDUINO,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -5,6 +5,7 @@ from esphome.automation import Condition
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import logger
|
from esphome.components import logger
|
||||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||||
|
from esphome.config_helpers import filter_source_files_from_platform
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_AVAILABILITY,
|
CONF_AVAILABILITY,
|
||||||
@ -54,6 +55,7 @@ from esphome.const import (
|
|||||||
PLATFORM_BK72XX,
|
PLATFORM_BK72XX,
|
||||||
PLATFORM_ESP32,
|
PLATFORM_ESP32,
|
||||||
PLATFORM_ESP8266,
|
PLATFORM_ESP8266,
|
||||||
|
PlatformFramework,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
|
|
||||||
@ -596,3 +598,13 @@ async def mqtt_enable_to_code(config, action_id, template_arg, args):
|
|||||||
async def mqtt_disable_to_code(config, action_id, template_arg, args):
|
async def mqtt_disable_to_code(config, action_id, template_arg, args):
|
||||||
paren = await cg.get_variable(config[CONF_ID])
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
|
||||||
|
|
||||||
|
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||||
|
{
|
||||||
|
"mqtt_backend_esp32.cpp": {
|
||||||
|
PlatformFramework.ESP32_ARDUINO,
|
||||||
|
PlatformFramework.ESP32_IDF,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -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");
|
||||||
|
@ -57,14 +57,15 @@ void MQTTClientComponent::setup() {
|
|||||||
});
|
});
|
||||||
#ifdef USE_LOGGER
|
#ifdef USE_LOGGER
|
||||||
if (this->is_log_message_enabled() && logger::global_logger != nullptr) {
|
if (this->is_log_message_enabled() && logger::global_logger != nullptr) {
|
||||||
logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) {
|
logger::global_logger->add_on_log_callback(
|
||||||
if (level <= this->log_level_ && this->is_connected()) {
|
[this](int level, const char *tag, const char *message, size_t message_len) {
|
||||||
this->publish({.topic = this->log_message_.topic,
|
if (level <= this->log_level_ && this->is_connected()) {
|
||||||
.payload = message,
|
this->publish({.topic = this->log_message_.topic,
|
||||||
.qos = this->log_message_.qos,
|
.payload = std::string(message, message_len),
|
||||||
.retain = this->log_message_.retain});
|
.qos = this->log_message_.qos,
|
||||||
}
|
.retain = this->log_message_.retain});
|
||||||
});
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import uart
|
from esphome.components import uart
|
||||||
|
from esphome.config_helpers import filter_source_files_from_platform
|
||||||
|
from esphome.const import PlatformFramework
|
||||||
|
|
||||||
nextion_ns = cg.esphome_ns.namespace("nextion")
|
nextion_ns = cg.esphome_ns.namespace("nextion")
|
||||||
Nextion = nextion_ns.class_("Nextion", cg.PollingComponent, uart.UARTDevice)
|
Nextion = nextion_ns.class_("Nextion", cg.PollingComponent, uart.UARTDevice)
|
||||||
@ -8,3 +10,17 @@ nextion_ref = Nextion.operator("ref")
|
|||||||
CONF_NEXTION_ID = "nextion_id"
|
CONF_NEXTION_ID = "nextion_id"
|
||||||
CONF_PUBLISH_STATE = "publish_state"
|
CONF_PUBLISH_STATE = "publish_state"
|
||||||
CONF_SEND_TO_NEXTION = "send_to_nextion"
|
CONF_SEND_TO_NEXTION = "send_to_nextion"
|
||||||
|
|
||||||
|
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||||
|
{
|
||||||
|
"nextion_upload_arduino.cpp": {
|
||||||
|
PlatformFramework.ESP32_ARDUINO,
|
||||||
|
PlatformFramework.ESP8266_ARDUINO,
|
||||||
|
PlatformFramework.RP2040_ARDUINO,
|
||||||
|
PlatformFramework.BK72XX_ARDUINO,
|
||||||
|
PlatformFramework.RTL87XX_ARDUINO,
|
||||||
|
PlatformFramework.LN882X_ARDUINO,
|
||||||
|
},
|
||||||
|
"nextion_upload_idf.cpp": {PlatformFramework.ESP32_IDF},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -44,7 +44,7 @@ void NextionBinarySensor::set_state(bool state, bool publish, bool send_to_nexti
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (send_to_nextion) {
|
if (send_to_nextion) {
|
||||||
if (this->nextion_->is_sleeping() || !this->visible_) {
|
if (this->nextion_->is_sleeping() || !this->component_flags_.visible) {
|
||||||
this->needs_to_send_update_ = true;
|
this->needs_to_send_update_ = true;
|
||||||
} else {
|
} else {
|
||||||
this->needs_to_send_update_ = false;
|
this->needs_to_send_update_ = false;
|
||||||
|
@ -8,8 +8,8 @@ void NextionComponent::set_background_color(Color bco) {
|
|||||||
return; // This is a variable. no need to set color
|
return; // This is a variable. no need to set color
|
||||||
}
|
}
|
||||||
this->bco_ = bco;
|
this->bco_ = bco;
|
||||||
this->bco_needs_update_ = true;
|
this->component_flags_.bco_needs_update = true;
|
||||||
this->bco_is_set_ = true;
|
this->component_flags_.bco_is_set = true;
|
||||||
this->update_component_settings();
|
this->update_component_settings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,8 +19,8 @@ void NextionComponent::set_background_pressed_color(Color bco2) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this->bco2_ = bco2;
|
this->bco2_ = bco2;
|
||||||
this->bco2_needs_update_ = true;
|
this->component_flags_.bco2_needs_update = true;
|
||||||
this->bco2_is_set_ = true;
|
this->component_flags_.bco2_is_set = true;
|
||||||
this->update_component_settings();
|
this->update_component_settings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,8 +29,8 @@ void NextionComponent::set_foreground_color(Color pco) {
|
|||||||
return; // This is a variable. no need to set color
|
return; // This is a variable. no need to set color
|
||||||
}
|
}
|
||||||
this->pco_ = pco;
|
this->pco_ = pco;
|
||||||
this->pco_needs_update_ = true;
|
this->component_flags_.pco_needs_update = true;
|
||||||
this->pco_is_set_ = true;
|
this->component_flags_.pco_is_set = true;
|
||||||
this->update_component_settings();
|
this->update_component_settings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,8 +39,8 @@ void NextionComponent::set_foreground_pressed_color(Color pco2) {
|
|||||||
return; // This is a variable. no need to set color
|
return; // This is a variable. no need to set color
|
||||||
}
|
}
|
||||||
this->pco2_ = pco2;
|
this->pco2_ = pco2;
|
||||||
this->pco2_needs_update_ = true;
|
this->component_flags_.pco2_needs_update = true;
|
||||||
this->pco2_is_set_ = true;
|
this->component_flags_.pco2_is_set = true;
|
||||||
this->update_component_settings();
|
this->update_component_settings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,8 +49,8 @@ void NextionComponent::set_font_id(uint8_t font_id) {
|
|||||||
return; // This is a variable. no need to set color
|
return; // This is a variable. no need to set color
|
||||||
}
|
}
|
||||||
this->font_id_ = font_id;
|
this->font_id_ = font_id;
|
||||||
this->font_id_needs_update_ = true;
|
this->component_flags_.font_id_needs_update = true;
|
||||||
this->font_id_is_set_ = true;
|
this->component_flags_.font_id_is_set = true;
|
||||||
this->update_component_settings();
|
this->update_component_settings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,20 +58,20 @@ void NextionComponent::set_visible(bool visible) {
|
|||||||
if (this->variable_name_ == this->variable_name_to_send_) {
|
if (this->variable_name_ == this->variable_name_to_send_) {
|
||||||
return; // This is a variable. no need to set color
|
return; // This is a variable. no need to set color
|
||||||
}
|
}
|
||||||
this->visible_ = visible;
|
this->component_flags_.visible = visible;
|
||||||
this->visible_needs_update_ = true;
|
this->component_flags_.visible_needs_update = true;
|
||||||
this->visible_is_set_ = true;
|
this->component_flags_.visible_is_set = true;
|
||||||
this->update_component_settings();
|
this->update_component_settings();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NextionComponent::update_component_settings(bool force_update) {
|
void NextionComponent::update_component_settings(bool force_update) {
|
||||||
if (this->nextion_->is_sleeping() || !this->nextion_->is_setup() || !this->visible_is_set_ ||
|
if (this->nextion_->is_sleeping() || !this->nextion_->is_setup() || !this->component_flags_.visible_is_set ||
|
||||||
(!this->visible_needs_update_ && !this->visible_)) {
|
(!this->component_flags_.visible_needs_update && !this->component_flags_.visible)) {
|
||||||
this->needs_to_send_update_ = true;
|
this->needs_to_send_update_ = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->visible_needs_update_ || (force_update && this->visible_is_set_)) {
|
if (this->component_flags_.visible_needs_update || (force_update && this->component_flags_.visible_is_set)) {
|
||||||
std::string name_to_send = this->variable_name_;
|
std::string name_to_send = this->variable_name_;
|
||||||
|
|
||||||
size_t pos = name_to_send.find_last_of('.');
|
size_t pos = name_to_send.find_last_of('.');
|
||||||
@ -79,9 +79,9 @@ void NextionComponent::update_component_settings(bool force_update) {
|
|||||||
name_to_send = name_to_send.substr(pos + 1);
|
name_to_send = name_to_send.substr(pos + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->visible_needs_update_ = false;
|
this->component_flags_.visible_needs_update = false;
|
||||||
|
|
||||||
if (this->visible_) {
|
if (this->component_flags_.visible) {
|
||||||
this->nextion_->show_component(name_to_send.c_str());
|
this->nextion_->show_component(name_to_send.c_str());
|
||||||
this->send_state_to_nextion();
|
this->send_state_to_nextion();
|
||||||
} else {
|
} else {
|
||||||
@ -90,26 +90,26 @@ void NextionComponent::update_component_settings(bool force_update) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->bco_needs_update_ || (force_update && this->bco2_is_set_)) {
|
if (this->component_flags_.bco_needs_update || (force_update && this->component_flags_.bco2_is_set)) {
|
||||||
this->nextion_->set_component_background_color(this->variable_name_.c_str(), this->bco_);
|
this->nextion_->set_component_background_color(this->variable_name_.c_str(), this->bco_);
|
||||||
this->bco_needs_update_ = false;
|
this->component_flags_.bco_needs_update = false;
|
||||||
}
|
}
|
||||||
if (this->bco2_needs_update_ || (force_update && this->bco2_is_set_)) {
|
if (this->component_flags_.bco2_needs_update || (force_update && this->component_flags_.bco2_is_set)) {
|
||||||
this->nextion_->set_component_pressed_background_color(this->variable_name_.c_str(), this->bco2_);
|
this->nextion_->set_component_pressed_background_color(this->variable_name_.c_str(), this->bco2_);
|
||||||
this->bco2_needs_update_ = false;
|
this->component_flags_.bco2_needs_update = false;
|
||||||
}
|
}
|
||||||
if (this->pco_needs_update_ || (force_update && this->pco_is_set_)) {
|
if (this->component_flags_.pco_needs_update || (force_update && this->component_flags_.pco_is_set)) {
|
||||||
this->nextion_->set_component_foreground_color(this->variable_name_.c_str(), this->pco_);
|
this->nextion_->set_component_foreground_color(this->variable_name_.c_str(), this->pco_);
|
||||||
this->pco_needs_update_ = false;
|
this->component_flags_.pco_needs_update = false;
|
||||||
}
|
}
|
||||||
if (this->pco2_needs_update_ || (force_update && this->pco2_is_set_)) {
|
if (this->component_flags_.pco2_needs_update || (force_update && this->component_flags_.pco2_is_set)) {
|
||||||
this->nextion_->set_component_pressed_foreground_color(this->variable_name_.c_str(), this->pco2_);
|
this->nextion_->set_component_pressed_foreground_color(this->variable_name_.c_str(), this->pco2_);
|
||||||
this->pco2_needs_update_ = false;
|
this->component_flags_.pco2_needs_update = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->font_id_needs_update_ || (force_update && this->font_id_is_set_)) {
|
if (this->component_flags_.font_id_needs_update || (force_update && this->component_flags_.font_id_is_set)) {
|
||||||
this->nextion_->set_component_font(this->variable_name_.c_str(), this->font_id_);
|
this->nextion_->set_component_font(this->variable_name_.c_str(), this->font_id_);
|
||||||
this->font_id_needs_update_ = false;
|
this->component_flags_.font_id_needs_update = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // namespace nextion
|
} // namespace nextion
|
||||||
|
@ -21,29 +21,64 @@ class NextionComponent : public NextionComponentBase {
|
|||||||
void set_visible(bool visible);
|
void set_visible(bool visible);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
/**
|
||||||
|
* @brief Constructor initializes component state with visible=true (default state)
|
||||||
|
*/
|
||||||
|
NextionComponent() {
|
||||||
|
component_flags_ = {}; // Zero-initialize all state
|
||||||
|
component_flags_.visible = 1; // Set default visibility to true
|
||||||
|
}
|
||||||
|
|
||||||
NextionBase *nextion_;
|
NextionBase *nextion_;
|
||||||
|
|
||||||
bool bco_needs_update_ = false;
|
// Color and styling properties
|
||||||
bool bco_is_set_ = false;
|
Color bco_; // Background color
|
||||||
Color bco_;
|
Color bco2_; // Pressed background color
|
||||||
bool bco2_needs_update_ = false;
|
Color pco_; // Foreground color
|
||||||
bool bco2_is_set_ = false;
|
Color pco2_; // Pressed foreground color
|
||||||
Color bco2_;
|
|
||||||
bool pco_needs_update_ = false;
|
|
||||||
bool pco_is_set_ = false;
|
|
||||||
Color pco_;
|
|
||||||
bool pco2_needs_update_ = false;
|
|
||||||
bool pco2_is_set_ = false;
|
|
||||||
Color pco2_;
|
|
||||||
uint8_t font_id_ = 0;
|
uint8_t font_id_ = 0;
|
||||||
bool font_id_needs_update_ = false;
|
|
||||||
bool font_id_is_set_ = false;
|
|
||||||
|
|
||||||
bool visible_ = true;
|
/**
|
||||||
bool visible_needs_update_ = false;
|
* @brief Component state management using compact bitfield structure
|
||||||
bool visible_is_set_ = false;
|
*
|
||||||
|
* Stores all component state flags and properties in a single 16-bit bitfield
|
||||||
|
* for efficient memory usage and improved cache locality.
|
||||||
|
*
|
||||||
|
* Each component property maintains two state flags:
|
||||||
|
* - needs_update: Indicates the property requires synchronization with the display
|
||||||
|
* - is_set: Tracks whether the property has been explicitly configured
|
||||||
|
*
|
||||||
|
* The visible field stores both the update flags and the actual visibility state.
|
||||||
|
*/
|
||||||
|
struct ComponentState {
|
||||||
|
// Background color flags
|
||||||
|
uint16_t bco_needs_update : 1;
|
||||||
|
uint16_t bco_is_set : 1;
|
||||||
|
|
||||||
// void send_state_to_nextion() = 0;
|
// Pressed background color flags
|
||||||
|
uint16_t bco2_needs_update : 1;
|
||||||
|
uint16_t bco2_is_set : 1;
|
||||||
|
|
||||||
|
// Foreground color flags
|
||||||
|
uint16_t pco_needs_update : 1;
|
||||||
|
uint16_t pco_is_set : 1;
|
||||||
|
|
||||||
|
// Pressed foreground color flags
|
||||||
|
uint16_t pco2_needs_update : 1;
|
||||||
|
uint16_t pco2_is_set : 1;
|
||||||
|
|
||||||
|
// Font ID flags
|
||||||
|
uint16_t font_id_needs_update : 1;
|
||||||
|
uint16_t font_id_is_set : 1;
|
||||||
|
|
||||||
|
// Visibility flags
|
||||||
|
uint16_t visible_needs_update : 1;
|
||||||
|
uint16_t visible_is_set : 1;
|
||||||
|
uint16_t visible : 1; // Actual visibility state
|
||||||
|
|
||||||
|
// Reserved bits for future expansion
|
||||||
|
uint16_t reserved : 3;
|
||||||
|
} component_flags_;
|
||||||
};
|
};
|
||||||
} // namespace nextion
|
} // namespace nextion
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
@ -53,7 +53,7 @@ void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) {
|
|||||||
|
|
||||||
if (this->wave_chan_id_ == UINT8_MAX) {
|
if (this->wave_chan_id_ == UINT8_MAX) {
|
||||||
if (send_to_nextion) {
|
if (send_to_nextion) {
|
||||||
if (this->nextion_->is_sleeping() || !this->visible_) {
|
if (this->nextion_->is_sleeping() || !this->component_flags_.visible) {
|
||||||
this->needs_to_send_update_ = true;
|
this->needs_to_send_update_ = true;
|
||||||
} else {
|
} else {
|
||||||
this->needs_to_send_update_ = false;
|
this->needs_to_send_update_ = false;
|
||||||
|
@ -28,7 +28,7 @@ void NextionSwitch::set_state(bool state, bool publish, bool send_to_nextion) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (send_to_nextion) {
|
if (send_to_nextion) {
|
||||||
if (this->nextion_->is_sleeping() || !this->visible_) {
|
if (this->nextion_->is_sleeping() || !this->component_flags_.visible) {
|
||||||
this->needs_to_send_update_ = true;
|
this->needs_to_send_update_ = true;
|
||||||
} else {
|
} else {
|
||||||
this->needs_to_send_update_ = false;
|
this->needs_to_send_update_ = false;
|
||||||
|
@ -26,7 +26,7 @@ void NextionTextSensor::set_state(const std::string &state, bool publish, bool s
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (send_to_nextion) {
|
if (send_to_nextion) {
|
||||||
if (this->nextion_->is_sleeping() || !this->visible_) {
|
if (this->nextion_->is_sleeping() || !this->component_flags_.visible) {
|
||||||
this->needs_to_send_update_ = true;
|
this->needs_to_send_update_ = true;
|
||||||
} else {
|
} else {
|
||||||
this->nextion_->add_no_result_to_queue_with_set(this, state);
|
this->nextion_->add_no_result_to_queue_with_set(this, state);
|
||||||
|
@ -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"]
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from esphome import automation
|
from esphome import automation
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
from esphome.config_helpers import filter_source_files_from_platform
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ESPHOME,
|
CONF_ESPHOME,
|
||||||
@ -7,6 +8,7 @@ from esphome.const import (
|
|||||||
CONF_OTA,
|
CONF_OTA,
|
||||||
CONF_PLATFORM,
|
CONF_PLATFORM,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
|
PlatformFramework,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
|
|
||||||
@ -120,3 +122,18 @@ async def ota_to_code(var, config):
|
|||||||
use_state_callback = True
|
use_state_callback = True
|
||||||
if use_state_callback:
|
if use_state_callback:
|
||||||
cg.add_define("USE_OTA_STATE_CALLBACK")
|
cg.add_define("USE_OTA_STATE_CALLBACK")
|
||||||
|
|
||||||
|
|
||||||
|
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||||
|
{
|
||||||
|
"ota_backend_arduino_esp32.cpp": {PlatformFramework.ESP32_ARDUINO},
|
||||||
|
"ota_backend_esp_idf.cpp": {PlatformFramework.ESP32_IDF},
|
||||||
|
"ota_backend_arduino_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
|
||||||
|
"ota_backend_arduino_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
|
||||||
|
"ota_backend_arduino_libretiny.cpp": {
|
||||||
|
PlatformFramework.BK72XX_ARDUINO,
|
||||||
|
PlatformFramework.RTL87XX_ARDUINO,
|
||||||
|
PlatformFramework.LN882X_ARDUINO,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -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_;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from esphome import pins
|
from esphome import pins
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import esp32, esp32_rmt, remote_base
|
from esphome.components import esp32, esp32_rmt, remote_base
|
||||||
|
from esphome.config_helpers import filter_source_files_from_platform
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_BUFFER_SIZE,
|
CONF_BUFFER_SIZE,
|
||||||
@ -15,6 +16,7 @@ from esphome.const import (
|
|||||||
CONF_TYPE,
|
CONF_TYPE,
|
||||||
CONF_USE_DMA,
|
CONF_USE_DMA,
|
||||||
CONF_VALUE,
|
CONF_VALUE,
|
||||||
|
PlatformFramework,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, TimePeriod
|
from esphome.core import CORE, TimePeriod
|
||||||
|
|
||||||
@ -170,3 +172,19 @@ async def to_code(config):
|
|||||||
cg.add(var.set_buffer_size(config[CONF_BUFFER_SIZE]))
|
cg.add(var.set_buffer_size(config[CONF_BUFFER_SIZE]))
|
||||||
cg.add(var.set_filter_us(config[CONF_FILTER]))
|
cg.add(var.set_filter_us(config[CONF_FILTER]))
|
||||||
cg.add(var.set_idle_us(config[CONF_IDLE]))
|
cg.add(var.set_idle_us(config[CONF_IDLE]))
|
||||||
|
|
||||||
|
|
||||||
|
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||||
|
{
|
||||||
|
"remote_receiver_esp32.cpp": {
|
||||||
|
PlatformFramework.ESP32_ARDUINO,
|
||||||
|
PlatformFramework.ESP32_IDF,
|
||||||
|
},
|
||||||
|
"remote_receiver_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
|
||||||
|
"remote_receiver_libretiny.cpp": {
|
||||||
|
PlatformFramework.BK72XX_ARDUINO,
|
||||||
|
PlatformFramework.RTL87XX_ARDUINO,
|
||||||
|
PlatformFramework.LN882X_ARDUINO,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from esphome import automation, pins
|
from esphome import automation, pins
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import esp32, esp32_rmt, remote_base
|
from esphome.components import esp32, esp32_rmt, remote_base
|
||||||
|
from esphome.config_helpers import filter_source_files_from_platform
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_CARRIER_DUTY_PERCENT,
|
CONF_CARRIER_DUTY_PERCENT,
|
||||||
@ -12,6 +13,7 @@ from esphome.const import (
|
|||||||
CONF_PIN,
|
CONF_PIN,
|
||||||
CONF_RMT_SYMBOLS,
|
CONF_RMT_SYMBOLS,
|
||||||
CONF_USE_DMA,
|
CONF_USE_DMA,
|
||||||
|
PlatformFramework,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
|
|
||||||
@ -95,3 +97,19 @@ async def to_code(config):
|
|||||||
await automation.build_automation(
|
await automation.build_automation(
|
||||||
var.get_complete_trigger(), [], on_complete_config
|
var.get_complete_trigger(), [], on_complete_config
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||||
|
{
|
||||||
|
"remote_transmitter_esp32.cpp": {
|
||||||
|
PlatformFramework.ESP32_ARDUINO,
|
||||||
|
PlatformFramework.ESP32_IDF,
|
||||||
|
},
|
||||||
|
"remote_transmitter_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
|
||||||
|
"remote_transmitter_libretiny.cpp": {
|
||||||
|
PlatformFramework.BK72XX_ARDUINO,
|
||||||
|
PlatformFramework.RTL87XX_ARDUINO,
|
||||||
|
PlatformFramework.LN882X_ARDUINO,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -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")
|
||||||
|
@ -118,7 +118,7 @@ optional<float> QuantileFilter::new_value(float value) {
|
|||||||
size_t queue_size = quantile_queue.size();
|
size_t queue_size = quantile_queue.size();
|
||||||
if (queue_size) {
|
if (queue_size) {
|
||||||
size_t position = ceilf(queue_size * this->quantile_) - 1;
|
size_t position = ceilf(queue_size * this->quantile_) - 1;
|
||||||
ESP_LOGVV(TAG, "QuantileFilter(%p)::position: %d/%d", this, position + 1, queue_size);
|
ESP_LOGVV(TAG, "QuantileFilter(%p)::position: %zu/%zu", this, position + 1, queue_size);
|
||||||
result = quantile_queue[position];
|
result = quantile_queue[position];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
|
from esphome.core import CORE
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
|
|
||||||
@ -40,3 +41,18 @@ async def to_code(config):
|
|||||||
elif impl == IMPLEMENTATION_BSD_SOCKETS:
|
elif impl == IMPLEMENTATION_BSD_SOCKETS:
|
||||||
cg.add_define("USE_SOCKET_IMPL_BSD_SOCKETS")
|
cg.add_define("USE_SOCKET_IMPL_BSD_SOCKETS")
|
||||||
cg.add_define("USE_SOCKET_SELECT_SUPPORT")
|
cg.add_define("USE_SOCKET_SELECT_SUPPORT")
|
||||||
|
|
||||||
|
|
||||||
|
def FILTER_SOURCE_FILES() -> list[str]:
|
||||||
|
"""Return list of socket implementation files that aren't selected by the user."""
|
||||||
|
impl = CORE.config["socket"][CONF_IMPLEMENTATION]
|
||||||
|
|
||||||
|
# Build list of files to exclude based on selected implementation
|
||||||
|
excluded = []
|
||||||
|
if impl != IMPLEMENTATION_LWIP_TCP:
|
||||||
|
excluded.append("lwip_raw_tcp_impl.cpp")
|
||||||
|
if impl != IMPLEMENTATION_BSD_SOCKETS:
|
||||||
|
excluded.append("bsd_sockets_impl.cpp")
|
||||||
|
if impl != IMPLEMENTATION_LWIP_SOCKETS:
|
||||||
|
excluded.append("lwip_sockets_impl.cpp")
|
||||||
|
return excluded
|
||||||
|
@ -13,6 +13,7 @@ from esphome.components.esp32.const import (
|
|||||||
VARIANT_ESP32S2,
|
VARIANT_ESP32S2,
|
||||||
VARIANT_ESP32S3,
|
VARIANT_ESP32S3,
|
||||||
)
|
)
|
||||||
|
from esphome.config_helpers import filter_source_files_from_platform
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_CLK_PIN,
|
CONF_CLK_PIN,
|
||||||
@ -31,6 +32,7 @@ from esphome.const import (
|
|||||||
PLATFORM_ESP32,
|
PLATFORM_ESP32,
|
||||||
PLATFORM_ESP8266,
|
PLATFORM_ESP8266,
|
||||||
PLATFORM_RP2040,
|
PLATFORM_RP2040,
|
||||||
|
PlatformFramework,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
import esphome.final_validate as fv
|
import esphome.final_validate as fv
|
||||||
@ -423,3 +425,18 @@ def final_validate_device_schema(name: str, *, require_mosi: bool, require_miso:
|
|||||||
{cv.Required(CONF_SPI_ID): fv.id_declaration_match_schema(hub_schema)},
|
{cv.Required(CONF_SPI_ID): fv.id_declaration_match_schema(hub_schema)},
|
||||||
extra=cv.ALLOW_EXTRA,
|
extra=cv.ALLOW_EXTRA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||||
|
{
|
||||||
|
"spi_arduino.cpp": {
|
||||||
|
PlatformFramework.ESP32_ARDUINO,
|
||||||
|
PlatformFramework.ESP8266_ARDUINO,
|
||||||
|
PlatformFramework.RP2040_ARDUINO,
|
||||||
|
PlatformFramework.BK72XX_ARDUINO,
|
||||||
|
PlatformFramework.RTL87XX_ARDUINO,
|
||||||
|
PlatformFramework.LN882X_ARDUINO,
|
||||||
|
},
|
||||||
|
"spi_esp_idf.cpp": {PlatformFramework.ESP32_IDF},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
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