mirror of
https://github.com/esphome/esphome.git
synced 2026-02-08 16:51:52 +00:00
Compare commits
172 Commits
2025.4.1
...
add-heap-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcdf209ac3 | ||
|
|
4c1111a395 | ||
|
|
57425a765b | ||
|
|
abb09b7fee | ||
|
|
b69fd2762e | ||
|
|
a38f0067ae | ||
|
|
00e128bdd5 | ||
|
|
35238c1437 | ||
|
|
8e29437900 | ||
|
|
9e64e71cdf | ||
|
|
ef2621aa54 | ||
|
|
882273cb56 | ||
|
|
ad2b74d9b4 | ||
|
|
26669bd1b6 | ||
|
|
54ead9a6b4 | ||
|
|
d60e1f02c0 | ||
|
|
213648564c | ||
|
|
8bdbde9732 | ||
|
|
e988762576 | ||
|
|
75496849eb | ||
|
|
39b119e9cc | ||
|
|
4d43caf6c1 | ||
|
|
ce5e1a6294 | ||
|
|
88be14aaa3 | ||
|
|
1ac56b06c5 | ||
|
|
8bbc509b0b | ||
|
|
6f35d0ac88 | ||
|
|
3b8a5db97c | ||
|
|
b8d83d0765 | ||
|
|
e7a2b395fd | ||
|
|
ad99d7fb45 | ||
|
|
0b032e5c19 | ||
|
|
c7523ace78 | ||
|
|
2a6827e1d2 | ||
|
|
125aff79ec | ||
|
|
a31d8ec309 | ||
|
|
3ed03edfec | ||
|
|
4dc6cbe2d7 | ||
|
|
524cd4b4e3 | ||
|
|
84ebbf0762 | ||
|
|
670ad7192c | ||
|
|
bc6ee20270 | ||
|
|
e869a3aec3 | ||
|
|
8aff6d2fdd | ||
|
|
8d33c6de36 | ||
|
|
f4b5f32cb4 | ||
|
|
2eb9582d0f | ||
|
|
db97440b04 | ||
|
|
ced7ae1d7a | ||
|
|
d6699fa3c0 | ||
|
|
836e5ffa43 | ||
|
|
c7f597bc75 | ||
|
|
e215fafebe | ||
|
|
da9c755f67 | ||
|
|
087ff865a7 | ||
|
|
8cd62c0308 | ||
|
|
f5241ff777 | ||
|
|
1aa2b79311 | ||
|
|
2dca2d5f85 | ||
|
|
f03b42ced5 | ||
|
|
0f8a0af244 | ||
|
|
62646f5f32 | ||
|
|
71f81d2f18 | ||
|
|
4ec8414050 | ||
|
|
807925fd38 | ||
|
|
b597565165 | ||
|
|
9a9b91b180 | ||
|
|
9dcf295df8 | ||
|
|
e8a3de2642 | ||
|
|
d2b4dba51f | ||
|
|
bf527b0331 | ||
|
|
cdc77506de | ||
|
|
6de6a0c82c | ||
|
|
20062576a3 | ||
|
|
07ba9fdf8f | ||
|
|
caa255f5d1 | ||
|
|
c0be2c14f3 | ||
|
|
9f629dcaa2 | ||
|
|
0fe6c65ba3 | ||
|
|
c756bb3b3e | ||
|
|
ecb91b0101 | ||
|
|
5f9a509bdc | ||
|
|
dc6dd9fe0d | ||
|
|
b8ba26787e | ||
|
|
844569e96b | ||
|
|
43580739ac | ||
|
|
c9f7ab6948 | ||
|
|
b5bdfb3089 | ||
|
|
a31a5e74bd | ||
|
|
629481a526 | ||
|
|
3291a11824 | ||
|
|
d2ee2d3b23 | ||
|
|
253e3ec6f6 | ||
|
|
fdc4ec8a57 | ||
|
|
1da0dff8b1 | ||
|
|
38dae8489e | ||
|
|
22c0e1079e | ||
|
|
2d3f141140 | ||
|
|
e49252ca3d | ||
|
|
c9d1476ae0 | ||
|
|
ee646d7324 | ||
|
|
e557bca420 | ||
|
|
adcd6517db | ||
|
|
4c8f5275f9 | ||
|
|
526db0102c | ||
|
|
8a3fe9ce4c | ||
|
|
fb97ef33a8 | ||
|
|
805a6d85a5 | ||
|
|
8f9fbb15b8 | ||
|
|
3d24dea455 | ||
|
|
666d5374ea | ||
|
|
6792ff6d58 | ||
|
|
f29ccb9e75 | ||
|
|
911bd54765 | ||
|
|
89b1b12993 | ||
|
|
33d79e03d9 | ||
|
|
991f3d3a10 | ||
|
|
97823ddd16 | ||
|
|
6ff180152a | ||
|
|
dbb7cbed3e | ||
|
|
fbf00f0af4 | ||
|
|
82c6a40371 | ||
|
|
0242ac56df | ||
|
|
b82666002d | ||
|
|
e11883e431 | ||
|
|
ff5b9df607 | ||
|
|
e5b7e3039a | ||
|
|
31ed1eb6f0 | ||
|
|
0c3daab649 | ||
|
|
816371e3e9 | ||
|
|
3c7bb65a23 | ||
|
|
4a65fd76b3 | ||
|
|
2704db5eef | ||
|
|
f10bc73d31 | ||
|
|
55e099450c | ||
|
|
248dbd32a5 | ||
|
|
a7b676231a | ||
|
|
2fd5f9ac58 | ||
|
|
ca4838a5f4 | ||
|
|
1b72550236 | ||
|
|
e5d718d1b1 | ||
|
|
af9b568778 | ||
|
|
3677ef71d1 | ||
|
|
7e133171e0 | ||
|
|
bc56d319b5 | ||
|
|
c423a6fb61 | ||
|
|
4034bf4f04 | ||
|
|
477abc05ae | ||
|
|
ff2b93a3e4 | ||
|
|
a52d6388a9 | ||
|
|
6259ca9ded | ||
|
|
f6ef50505b | ||
|
|
3c242b7296 | ||
|
|
00dd5b8339 | ||
|
|
a007a8237a | ||
|
|
9b86cc37f0 | ||
|
|
2dfcf950fa | ||
|
|
5908b93e82 | ||
|
|
995db1f961 | ||
|
|
abcc656a6f | ||
|
|
4a9f323d92 | ||
|
|
34a4e70cc5 | ||
|
|
fb5d697c22 | ||
|
|
df4642208e | ||
|
|
264e234efc | ||
|
|
ca78dd44b5 | ||
|
|
7edf458898 | ||
|
|
d9873e24a7 | ||
|
|
645bd490ba | ||
|
|
27f6d00e7a | ||
|
|
f9d668eeca | ||
|
|
6b930595e2 |
@@ -114,4 +114,5 @@ config/
|
||||
examples/
|
||||
Dockerfile
|
||||
.git/
|
||||
tests/build/
|
||||
tests/
|
||||
.*
|
||||
|
||||
33
.github/actions/build-image/action.yaml
vendored
33
.github/actions/build-image/action.yaml
vendored
@@ -1,15 +1,11 @@
|
||||
name: Build Image
|
||||
inputs:
|
||||
platform:
|
||||
description: "Platform to build for"
|
||||
required: true
|
||||
example: "linux/amd64"
|
||||
target:
|
||||
description: "Target to build"
|
||||
required: true
|
||||
example: "docker"
|
||||
baseimg:
|
||||
description: "Base image type"
|
||||
build_type:
|
||||
description: "Build type"
|
||||
required: true
|
||||
example: "docker"
|
||||
suffix:
|
||||
@@ -19,6 +15,11 @@ inputs:
|
||||
description: "Version to build"
|
||||
required: true
|
||||
example: "2023.12.0"
|
||||
base_os:
|
||||
description: "Base OS to use"
|
||||
required: false
|
||||
default: "debian"
|
||||
example: "debian"
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
@@ -46,52 +47,52 @@ runs:
|
||||
|
||||
- name: Build and push to ghcr by digest
|
||||
id: build-ghcr
|
||||
uses: docker/build-push-action@v6.15.0
|
||||
uses: docker/build-push-action@v6.16.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
platforms: ${{ inputs.platform }}
|
||||
target: ${{ inputs.target }}
|
||||
cache-from: type=gha
|
||||
cache-to: ${{ steps.cache-to.outputs.value }}
|
||||
build-args: |
|
||||
BASEIMGTYPE=${{ inputs.baseimg }}
|
||||
BUILD_TYPE=${{ inputs.build_type }}
|
||||
BUILD_VERSION=${{ inputs.version }}
|
||||
BUILD_OS=${{ inputs.base_os }}
|
||||
outputs: |
|
||||
type=image,name=ghcr.io/${{ steps.tags.outputs.image_name }},push-by-digest=true,name-canonical=true,push=true
|
||||
|
||||
- name: Export ghcr digests
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p /tmp/digests/${{ inputs.target }}/ghcr
|
||||
mkdir -p /tmp/digests/${{ inputs.build_type }}/ghcr
|
||||
digest="${{ steps.build-ghcr.outputs.digest }}"
|
||||
touch "/tmp/digests/${{ inputs.target }}/ghcr/${digest#sha256:}"
|
||||
touch "/tmp/digests/${{ inputs.build_type }}/ghcr/${digest#sha256:}"
|
||||
|
||||
- name: Build and push to dockerhub by digest
|
||||
id: build-dockerhub
|
||||
uses: docker/build-push-action@v6.15.0
|
||||
uses: docker/build-push-action@v6.16.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
platforms: ${{ inputs.platform }}
|
||||
target: ${{ inputs.target }}
|
||||
cache-from: type=gha
|
||||
cache-to: ${{ steps.cache-to.outputs.value }}
|
||||
build-args: |
|
||||
BASEIMGTYPE=${{ inputs.baseimg }}
|
||||
BUILD_TYPE=${{ inputs.build_type }}
|
||||
BUILD_VERSION=${{ inputs.version }}
|
||||
BUILD_OS=${{ inputs.base_os }}
|
||||
outputs: |
|
||||
type=image,name=docker.io/${{ steps.tags.outputs.image_name }},push-by-digest=true,name-canonical=true,push=true
|
||||
|
||||
- name: Export dockerhub digests
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p /tmp/digests/${{ inputs.target }}/dockerhub
|
||||
mkdir -p /tmp/digests/${{ inputs.build_type }}/dockerhub
|
||||
digest="${{ steps.build-dockerhub.outputs.digest }}"
|
||||
touch "/tmp/digests/${{ inputs.target }}/dockerhub/${digest#sha256:}"
|
||||
touch "/tmp/digests/${{ inputs.build_type }}/dockerhub/${digest#sha256:}"
|
||||
|
||||
6
.github/actions/restore-python/action.yml
vendored
6
.github/actions/restore-python/action.yml
vendored
@@ -17,7 +17,7 @@ runs:
|
||||
steps:
|
||||
- name: Set up Python ${{ inputs.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.5.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
@@ -34,7 +34,7 @@ runs:
|
||||
python -m venv venv
|
||||
source venv/bin/activate
|
||||
python --version
|
||||
pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
|
||||
pip install -r requirements.txt -r requirements_test.txt
|
||||
pip install -e .
|
||||
- name: Create Python virtual environment
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true' && runner.os == 'Windows'
|
||||
@@ -43,5 +43,5 @@ runs:
|
||||
python -m venv venv
|
||||
./venv/Scripts/activate
|
||||
python --version
|
||||
pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
|
||||
pip install -r requirements.txt -r requirements_test.txt
|
||||
pip install -e .
|
||||
|
||||
1
.github/dependabot.yml
vendored
1
.github/dependabot.yml
vendored
@@ -17,7 +17,6 @@ updates:
|
||||
docker-actions:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "docker/setup-qemu-action"
|
||||
- "docker/login-action"
|
||||
- "docker/setup-buildx-action"
|
||||
- package-ecosystem: github-actions
|
||||
|
||||
2
.github/workflows/ci-api-proto.yml
vendored
2
.github/workflows/ci-api-proto.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.5.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
|
||||
9
.github/workflows/ci-docker.yml
vendored
9
.github/workflows/ci-docker.yml
vendored
@@ -37,12 +37,15 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: ["ubuntu-latest", "ubuntu-24.04-arm"]
|
||||
build_type: ["ha-addon", "docker", "lint"]
|
||||
os: ["ubuntu-24.04", "ubuntu-24.04-arm"]
|
||||
build_type:
|
||||
- "ha-addon"
|
||||
- "docker"
|
||||
# - "lint"
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.5.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: "3.9"
|
||||
- name: Set up Docker Buildx
|
||||
|
||||
9
.github/workflows/ci.yml
vendored
9
.github/workflows/ci.yml
vendored
@@ -39,10 +39,10 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Generate cache-key
|
||||
id: cache-key
|
||||
run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
|
||||
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.5.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore Python virtual environment
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
python -m venv venv
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
|
||||
pip install -r requirements.txt -r requirements_test.txt
|
||||
pip install -e .
|
||||
|
||||
ruff:
|
||||
@@ -165,6 +165,7 @@ jobs:
|
||||
. venv/bin/activate
|
||||
script/ci-custom.py
|
||||
script/build_codeowners.py --check
|
||||
script/build_language_schema.py --check
|
||||
|
||||
pytest:
|
||||
name: Run pytest
|
||||
@@ -220,7 +221,7 @@ jobs:
|
||||
. venv/bin/activate
|
||||
pytest -vv --cov-report=xml --tb=native tests
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
uses: codecov/codecov-action@v5.4.2
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
|
||||
70
.github/workflows/release.yml
vendored
70
.github/workflows/release.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.5.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Set up python environment
|
||||
@@ -68,31 +68,31 @@ jobs:
|
||||
uses: pypa/gh-action-pypi-publish@v1.12.4
|
||||
|
||||
deploy-docker:
|
||||
name: Build ESPHome ${{ matrix.platform }}
|
||||
name: Build ESPHome ${{ matrix.platform.arch }}
|
||||
if: github.repository == 'esphome/esphome'
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
needs: [init]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
- arch: amd64
|
||||
os: "ubuntu-24.04"
|
||||
- arch: arm64
|
||||
os: "ubuntu-24.04-arm"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.5.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: "3.9"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.10.0
|
||||
- name: Set up QEMU
|
||||
if: matrix.platform != 'linux/amd64'
|
||||
uses: docker/setup-qemu-action@v3.6.0
|
||||
|
||||
- name: Log in to docker hub
|
||||
uses: docker/login-action@v3.4.0
|
||||
@@ -109,45 +109,36 @@ jobs:
|
||||
- name: Build docker
|
||||
uses: ./.github/actions/build-image
|
||||
with:
|
||||
platform: ${{ matrix.platform }}
|
||||
target: docker
|
||||
baseimg: docker
|
||||
target: final
|
||||
build_type: docker
|
||||
suffix: ""
|
||||
version: ${{ needs.init.outputs.tag }}
|
||||
|
||||
- name: Build ha-addon
|
||||
uses: ./.github/actions/build-image
|
||||
with:
|
||||
platform: ${{ matrix.platform }}
|
||||
target: hassio
|
||||
baseimg: hassio
|
||||
target: final
|
||||
build_type: ha-addon
|
||||
suffix: "hassio"
|
||||
version: ${{ needs.init.outputs.tag }}
|
||||
|
||||
- name: Build lint
|
||||
uses: ./.github/actions/build-image
|
||||
with:
|
||||
platform: ${{ matrix.platform }}
|
||||
target: lint
|
||||
baseimg: docker
|
||||
suffix: lint
|
||||
version: ${{ needs.init.outputs.tag }}
|
||||
|
||||
- name: Sanitize platform name
|
||||
id: sanitize
|
||||
run: |
|
||||
echo "${{ matrix.platform }}" | sed 's|/|-|g' > /tmp/platform
|
||||
echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT
|
||||
# - name: Build lint
|
||||
# uses: ./.github/actions/build-image
|
||||
# with:
|
||||
# target: lint
|
||||
# build_type: lint
|
||||
# suffix: lint
|
||||
# version: ${{ needs.init.outputs.tag }}
|
||||
|
||||
- name: Upload digests
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: digests-${{ steps.sanitize.outputs.name }}
|
||||
name: digests-${{ matrix.platform.arch }}
|
||||
path: /tmp/digests
|
||||
retention-days: 1
|
||||
|
||||
deploy-manifest:
|
||||
name: Publish ESPHome ${{ matrix.image.title }} to ${{ matrix.registry }}
|
||||
name: Publish ESPHome ${{ matrix.image.build_type }} to ${{ matrix.registry }}
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- init
|
||||
@@ -160,15 +151,12 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image:
|
||||
- title: "ha-addon"
|
||||
target: "hassio"
|
||||
suffix: "hassio"
|
||||
- title: "docker"
|
||||
target: "docker"
|
||||
- build_type: "docker"
|
||||
suffix: ""
|
||||
- title: "lint"
|
||||
target: "lint"
|
||||
suffix: "lint"
|
||||
- build_type: "ha-addon"
|
||||
suffix: "hassio"
|
||||
# - build_type: "lint"
|
||||
# suffix: "lint"
|
||||
registry:
|
||||
- ghcr
|
||||
- dockerhub
|
||||
@@ -176,7 +164,7 @@ jobs:
|
||||
- uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4.2.1
|
||||
uses: actions/download-artifact@v4.3.0
|
||||
with:
|
||||
pattern: digests-*
|
||||
path: /tmp/digests
|
||||
@@ -212,7 +200,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Create manifest list and push
|
||||
working-directory: /tmp/digests/${{ matrix.image.target }}/${{ matrix.registry }}
|
||||
working-directory: /tmp/digests/${{ matrix.image.build_type }}/${{ matrix.registry }}
|
||||
run: |
|
||||
docker buildx imagetools create $(jq -Rcnr 'inputs | . / "," | map("-t " + .) | join(" ")' <<< "${{ steps.tags.outputs.tags}}") \
|
||||
$(printf '${{ steps.tags.outputs.image }}@sha256:%s ' *)
|
||||
|
||||
2
.github/workflows/sync-device-classes.yml
vendored
2
.github/workflows/sync-device-classes.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
path: lib/home-assistant
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5.5.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: 3.12
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ repos:
|
||||
- id: pyupgrade
|
||||
args: [--py39-plus]
|
||||
- repo: https://github.com/adrienverge/yamllint.git
|
||||
rev: v1.35.1
|
||||
rev: v1.37.1
|
||||
hooks:
|
||||
- id: yamllint
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
|
||||
@@ -98,6 +98,7 @@ esphome/components/climate/* @esphome/core
|
||||
esphome/components/climate_ir/* @glmnet
|
||||
esphome/components/color_temperature/* @jesserockz
|
||||
esphome/components/combination/* @Cat-Ion @kahrendt
|
||||
esphome/components/const/* @esphome/core
|
||||
esphome/components/coolix/* @glmnet
|
||||
esphome/components/copy/* @OttoWinter
|
||||
esphome/components/cover/* @esphome/core
|
||||
@@ -250,6 +251,7 @@ esphome/components/ltr501/* @latonita
|
||||
esphome/components/ltr_als_ps/* @latonita
|
||||
esphome/components/lvgl/* @clydebarrow
|
||||
esphome/components/m5stack_8angle/* @rnauber
|
||||
esphome/components/mapping/* @clydebarrow
|
||||
esphome/components/matrix_keypad/* @ssieb
|
||||
esphome/components/max17043/* @blacknell
|
||||
esphome/components/max31865/* @DAVe3283
|
||||
@@ -276,7 +278,7 @@ esphome/components/mdns/* @esphome/core
|
||||
esphome/components/media_player/* @jesserockz
|
||||
esphome/components/micro_wake_word/* @jesserockz @kahrendt
|
||||
esphome/components/micronova/* @jorre05
|
||||
esphome/components/microphone/* @jesserockz
|
||||
esphome/components/microphone/* @jesserockz @kahrendt
|
||||
esphome/components/mics_4514/* @jesserockz
|
||||
esphome/components/midea/* @dudanov
|
||||
esphome/components/midea_ir/* @dudanov
|
||||
@@ -317,6 +319,7 @@ esphome/components/online_image/* @clydebarrow @guillempages
|
||||
esphome/components/opentherm/* @olegtarasov
|
||||
esphome/components/ota/* @esphome/core
|
||||
esphome/components/output/* @esphome/core
|
||||
esphome/components/packet_transport/* @clydebarrow
|
||||
esphome/components/pca6416a/* @Mat931
|
||||
esphome/components/pca9554/* @clydebarrow @hwstar
|
||||
esphome/components/pcf85063/* @brogon
|
||||
@@ -324,7 +327,9 @@ esphome/components/pcf8563/* @KoenBreeman
|
||||
esphome/components/pid/* @OttoWinter
|
||||
esphome/components/pipsolar/* @andreashergert1984
|
||||
esphome/components/pm1006/* @habbie
|
||||
esphome/components/pm2005/* @andrewjswan
|
||||
esphome/components/pmsa003i/* @sjtrny
|
||||
esphome/components/pmsx003/* @ximex
|
||||
esphome/components/pmwcs3/* @SeByDocKy
|
||||
esphome/components/pn532/* @OttoWinter @jesserockz
|
||||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
|
||||
@@ -424,6 +429,7 @@ esphome/components/sun/* @OttoWinter
|
||||
esphome/components/sun_gtil2/* @Mat931
|
||||
esphome/components/switch/* @esphome/core
|
||||
esphome/components/switch/binary_sensor/* @ssieb
|
||||
esphome/components/syslog/* @clydebarrow
|
||||
esphome/components/t6615/* @tylermenezes
|
||||
esphome/components/tc74/* @sethgirvan
|
||||
esphome/components/tca9548a/* @andreashergert1984
|
||||
@@ -463,6 +469,7 @@ esphome/components/tuya/switch/* @jesserockz
|
||||
esphome/components/tuya/text_sensor/* @dentra
|
||||
esphome/components/uart/* @esphome/core
|
||||
esphome/components/uart/button/* @ssieb
|
||||
esphome/components/uart/packet_transport/* @clydebarrow
|
||||
esphome/components/udp/* @clydebarrow
|
||||
esphome/components/ufire_ec/* @pvizeli
|
||||
esphome/components/ufire_ise/* @pvizeli
|
||||
|
||||
@@ -1,131 +1,54 @@
|
||||
# Build these with the build.py script
|
||||
# Example:
|
||||
# python3 docker/build.py --tag dev --arch amd64 --build-type docker build
|
||||
ARG BUILD_VERSION=dev
|
||||
ARG BUILD_OS=alpine
|
||||
ARG BUILD_BASE_VERSION=2025.04.0
|
||||
ARG BUILD_TYPE=docker
|
||||
|
||||
# One of "docker", "hassio"
|
||||
ARG BASEIMGTYPE=docker
|
||||
FROM ghcr.io/esphome/docker-base:${BUILD_OS}-${BUILD_BASE_VERSION} AS base-source-docker
|
||||
FROM ghcr.io/esphome/docker-base:${BUILD_OS}-ha-addon-${BUILD_BASE_VERSION} AS base-source-ha-addon
|
||||
|
||||
ARG BUILD_TYPE
|
||||
FROM base-source-${BUILD_TYPE} AS base
|
||||
|
||||
# https://github.com/hassio-addons/addon-debian-base/releases
|
||||
FROM ghcr.io/hassio-addons/debian-base:7.2.0 AS base-hassio
|
||||
# https://hub.docker.com/_/debian?tab=tags&page=1&name=bookworm
|
||||
FROM debian:12.2-slim AS base-docker
|
||||
RUN git config --system --add safe.directory "*"
|
||||
|
||||
FROM base-${BASEIMGTYPE} AS base
|
||||
RUN pip install uv==0.6.14
|
||||
|
||||
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
|
||||
|
||||
# Note that --break-system-packages is used below because
|
||||
# https://peps.python.org/pep-0668/ added a safety check that prevents
|
||||
# installing packages with the same name as a system package. This is
|
||||
# not a problem for us because we are not concerned about overwriting
|
||||
# system packages because we are running in an isolated container.
|
||||
COPY requirements.txt /
|
||||
|
||||
RUN \
|
||||
apt-get update \
|
||||
# Use pinned versions so that we get updates with build caching
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
python3-pip=23.0.1+dfsg-1 \
|
||||
python3-setuptools=66.1.1-1+deb12u1 \
|
||||
python3-venv=3.11.2-1+b1 \
|
||||
python3-wheel=0.38.4-2 \
|
||||
iputils-ping=3:20221126-1+deb12u1 \
|
||||
git=1:2.39.5-0+deb12u2 \
|
||||
curl=7.88.1-10+deb12u12 \
|
||||
openssh-client=1:9.2p1-2+deb12u5 \
|
||||
python3-cffi=1.15.1-5 \
|
||||
libcairo2=1.16.0-7 \
|
||||
libmagic1=1:5.44-3 \
|
||||
patch=2.7.6-7 \
|
||||
&& rm -rf \
|
||||
/tmp/* \
|
||||
/var/{cache,log}/* \
|
||||
/var/lib/apt/lists/*
|
||||
|
||||
ENV \
|
||||
# Fix click python3 lang warning https://click.palletsprojects.com/en/7.x/python3/
|
||||
LANG=C.UTF-8 LC_ALL=C.UTF-8 \
|
||||
# Store globally installed pio libs in /piolibs
|
||||
PLATFORMIO_GLOBALLIB_DIR=/piolibs
|
||||
uv pip install --no-cache-dir \
|
||||
-r /requirements.txt
|
||||
|
||||
RUN \
|
||||
pip3 install \
|
||||
--break-system-packages --no-cache-dir \
|
||||
# Keep platformio version in sync with requirements.txt
|
||||
platformio==6.1.18 \
|
||||
# Change some platformio settings
|
||||
&& platformio settings set enable_telemetry No \
|
||||
platformio settings set enable_telemetry No \
|
||||
&& platformio settings set check_platformio_interval 1000000 \
|
||||
&& mkdir -p /piolibs
|
||||
|
||||
|
||||
# First install requirements to leverage caching when requirements don't change
|
||||
# tmpfs is for https://github.com/rust-lang/cargo/issues/8719
|
||||
|
||||
COPY requirements.txt requirements_optional.txt /
|
||||
RUN --mount=type=tmpfs,target=/root/.cargo <<END-OF-RUN
|
||||
# Fail on any non-zero status
|
||||
set -e
|
||||
|
||||
# install build tools in case wheels are not available
|
||||
BUILD_DEPS="
|
||||
build-essential=12.9
|
||||
python3-dev=3.11.2-1+b1
|
||||
zlib1g-dev=1:1.2.13.dfsg-1
|
||||
libjpeg-dev=1:2.1.5-2
|
||||
libfreetype-dev=2.12.1+dfsg-5+deb12u4
|
||||
libssl-dev=3.0.15-1~deb12u1
|
||||
libffi-dev=3.4.4-1
|
||||
cargo=0.66.0+ds1-1
|
||||
pkg-config=1.8.1-1
|
||||
"
|
||||
LIB_DEPS="
|
||||
libtiff6=4.5.0-6+deb12u1
|
||||
libopenjp2-7=2.5.0-2+deb12u1
|
||||
"
|
||||
if [ "$TARGETARCH$TARGETVARIANT" = "arm64" ]
|
||||
then
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends $BUILD_DEPS $LIB_DEPS
|
||||
fi
|
||||
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse CARGO_HOME=/root/.cargo
|
||||
pip3 install --break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt
|
||||
|
||||
if [ "$TARGETARCH$TARGETVARIANT" = "arm64" ]
|
||||
then
|
||||
apt-get remove -y --purge --auto-remove $BUILD_DEPS
|
||||
rm -rf /tmp/* /var/{cache,log}/* /var/lib/apt/lists/*
|
||||
fi
|
||||
END-OF-RUN
|
||||
|
||||
|
||||
COPY script/platformio_install_deps.py platformio.ini /
|
||||
RUN /platformio_install_deps.py /platformio.ini --libraries
|
||||
|
||||
# Avoid unsafe git error when container user and file config volume permissions don't match
|
||||
RUN git config --system --add safe.directory '*'
|
||||
ARG BUILD_VERSION
|
||||
|
||||
LABEL \
|
||||
org.opencontainers.image.authors="The ESPHome Authors" \
|
||||
org.opencontainers.image.title="ESPHome" \
|
||||
org.opencontainers.image.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \
|
||||
org.opencontainers.image.url="https://esphome.io/" \
|
||||
org.opencontainers.image.documentation="https://esphome.io/" \
|
||||
org.opencontainers.image.source="https://github.com/esphome/esphome" \
|
||||
org.opencontainers.image.licenses="ESPHome" \
|
||||
org.opencontainers.image.version=${BUILD_VERSION}
|
||||
|
||||
|
||||
# ======================= docker-type image =======================
|
||||
FROM base AS docker
|
||||
|
||||
# Copy esphome and install
|
||||
COPY . /esphome
|
||||
RUN pip3 install --break-system-packages --no-cache-dir -e /esphome
|
||||
|
||||
# Settings for dashboard
|
||||
ENV USERNAME="" PASSWORD=""
|
||||
FROM base AS base-docker
|
||||
|
||||
# Expose the dashboard to Docker
|
||||
EXPOSE 6052
|
||||
|
||||
# Run healthcheck (heartbeat)
|
||||
HEALTHCHECK --interval=30s --timeout=30s \
|
||||
CMD curl --fail http://localhost:6052/version -A "HealthCheck" || exit 1
|
||||
CMD curl --fail http://localhost:6052/version -A "HealthCheck" || exit 1
|
||||
|
||||
COPY docker/docker_entrypoint.sh /entrypoint.sh
|
||||
|
||||
@@ -139,43 +62,13 @@ ENTRYPOINT ["/entrypoint.sh"]
|
||||
CMD ["dashboard", "/config"]
|
||||
|
||||
|
||||
ARG BUILD_VERSION=dev
|
||||
|
||||
# Labels
|
||||
LABEL \
|
||||
org.opencontainers.image.authors="The ESPHome Authors" \
|
||||
org.opencontainers.image.title="ESPHome" \
|
||||
org.opencontainers.image.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \
|
||||
org.opencontainers.image.url="https://esphome.io/" \
|
||||
org.opencontainers.image.documentation="https://esphome.io/" \
|
||||
org.opencontainers.image.source="https://github.com/esphome/esphome" \
|
||||
org.opencontainers.image.licenses="ESPHome" \
|
||||
org.opencontainers.image.version=${BUILD_VERSION}
|
||||
|
||||
|
||||
# ======================= hassio-type image =======================
|
||||
FROM base AS hassio
|
||||
|
||||
RUN \
|
||||
apt-get update \
|
||||
# Use pinned versions so that we get updates with build caching
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
nginx-light=1.22.1-9+deb12u1 \
|
||||
&& rm -rf \
|
||||
/tmp/* \
|
||||
/var/{cache,log}/* \
|
||||
/var/lib/apt/lists/*
|
||||
|
||||
ARG BUILD_VERSION=dev
|
||||
# ======================= ha-addon-type image =======================
|
||||
FROM base AS base-ha-addon
|
||||
|
||||
# Copy root filesystem
|
||||
COPY docker/ha-addon-rootfs/ /
|
||||
|
||||
# Copy esphome and install
|
||||
COPY . /esphome
|
||||
RUN pip3 install --break-system-packages --no-cache-dir -e /esphome
|
||||
|
||||
# Labels
|
||||
ARG BUILD_VERSION
|
||||
LABEL \
|
||||
io.hass.name="ESPHome" \
|
||||
io.hass.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \
|
||||
@@ -183,35 +76,9 @@ LABEL \
|
||||
io.hass.version="${BUILD_VERSION}"
|
||||
# io.hass.arch is inherited from addon-debian-base
|
||||
|
||||
ARG BUILD_TYPE
|
||||
FROM base-${BUILD_TYPE} AS final
|
||||
|
||||
|
||||
|
||||
# ======================= lint-type image =======================
|
||||
FROM base AS lint
|
||||
|
||||
ENV \
|
||||
PLATFORMIO_CORE_DIR=/esphome/.temp/platformio
|
||||
|
||||
RUN \
|
||||
curl -L https://apt.llvm.org/llvm-snapshot.gpg.key -o /etc/apt/trusted.gpg.d/apt.llvm.org.asc \
|
||||
&& echo "deb http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-18 main" > /etc/apt/sources.list.d/llvm.sources.list \
|
||||
&& apt-get update \
|
||||
# Use pinned versions so that we get updates with build caching
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
clang-format-13=1:13.0.1-11+b2 \
|
||||
patch=2.7.6-7 \
|
||||
software-properties-common=0.99.30-4.1~deb12u1 \
|
||||
nano=7.2-1+deb12u1 \
|
||||
build-essential=12.9 \
|
||||
python3-dev=3.11.2-1+b1 \
|
||||
clang-tidy-18=1:18.1.8~++20240731024826+3b5b5c1ec4a3-1~exp1~20240731144843.145 \
|
||||
&& rm -rf \
|
||||
/tmp/* \
|
||||
/var/{cache,log}/* \
|
||||
/var/lib/apt/lists/*
|
||||
|
||||
COPY requirements_test.txt /
|
||||
RUN pip3 install --break-system-packages --no-cache-dir -r /requirements_test.txt
|
||||
|
||||
VOLUME ["/esphome"]
|
||||
WORKDIR /esphome
|
||||
# Copy esphome and install
|
||||
COPY . /esphome
|
||||
RUN uv pip install --no-cache-dir -e /esphome
|
||||
|
||||
@@ -54,7 +54,7 @@ manifest_parser = subparsers.add_parser(
|
||||
class DockerParams:
|
||||
build_to: str
|
||||
manifest_to: str
|
||||
baseimgtype: str
|
||||
build_type: str
|
||||
platform: str
|
||||
target: str
|
||||
|
||||
@@ -66,24 +66,19 @@ class DockerParams:
|
||||
TYPE_LINT: "esphome/esphome-lint",
|
||||
}[build_type]
|
||||
build_to = f"{prefix}-{arch}"
|
||||
baseimgtype = {
|
||||
TYPE_DOCKER: "docker",
|
||||
TYPE_HA_ADDON: "hassio",
|
||||
TYPE_LINT: "docker",
|
||||
}[build_type]
|
||||
platform = {
|
||||
ARCH_AMD64: "linux/amd64",
|
||||
ARCH_AARCH64: "linux/arm64",
|
||||
}[arch]
|
||||
target = {
|
||||
TYPE_DOCKER: "docker",
|
||||
TYPE_HA_ADDON: "hassio",
|
||||
TYPE_DOCKER: "final",
|
||||
TYPE_HA_ADDON: "final",
|
||||
TYPE_LINT: "lint",
|
||||
}[build_type]
|
||||
return cls(
|
||||
build_to=build_to,
|
||||
manifest_to=prefix,
|
||||
baseimgtype=baseimgtype,
|
||||
build_type=build_type,
|
||||
platform=platform,
|
||||
target=target,
|
||||
)
|
||||
@@ -145,7 +140,7 @@ def main():
|
||||
"buildx",
|
||||
"build",
|
||||
"--build-arg",
|
||||
f"BASEIMGTYPE={params.baseimgtype}",
|
||||
f"BUILD_TYPE={params.build_type}",
|
||||
"--build-arg",
|
||||
f"BUILD_VERSION={args.tag}",
|
||||
"--cache-from",
|
||||
|
||||
@@ -114,13 +114,14 @@ void IRAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
|
||||
// fully off, disable output immediately
|
||||
this->gate_pin.digital_write(false);
|
||||
} else {
|
||||
auto min_us = this->cycle_time_us * this->min_power / 1000;
|
||||
if (this->method == DIM_METHOD_TRAILING) {
|
||||
this->enable_time_us = 1; // cannot be 0
|
||||
this->disable_time_us = std::max((uint32_t) 10, this->value * this->cycle_time_us / 65535);
|
||||
// calculate time until disable in µs with integer arithmetic and take into account min_power
|
||||
this->disable_time_us = std::max((uint32_t) 10, this->value * (this->cycle_time_us - min_us) / 65535 + min_us);
|
||||
} else {
|
||||
// calculate time until enable in µs: (1.0-value)*cycle_time, but with integer arithmetic
|
||||
// also take into account min_power
|
||||
auto min_us = this->cycle_time_us * this->min_power / 1000;
|
||||
this->enable_time_us = std::max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535);
|
||||
|
||||
if (this->method == DIM_METHOD_LEADING_PULSE) {
|
||||
|
||||
@@ -47,9 +47,10 @@ SAMPLING_MODES = {
|
||||
adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
|
||||
adc2_channel_t = cg.global_ns.enum("adc2_channel_t")
|
||||
|
||||
# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
|
||||
# pin to adc1 channel mapping
|
||||
# https://github.com/espressif/esp-idf/blob/v4.4.8/components/driver/include/driver/adc.h
|
||||
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32/include/soc/adc_channel.h
|
||||
VARIANT_ESP32: {
|
||||
36: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
37: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
@@ -60,6 +61,41 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
|
||||
34: adc1_channel_t.ADC1_CHANNEL_6,
|
||||
35: adc1_channel_t.ADC1_CHANNEL_7,
|
||||
},
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c2/include/soc/adc_channel.h
|
||||
VARIANT_ESP32C2: {
|
||||
0: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
1: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
3: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
4: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
},
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c3/include/soc/adc_channel.h
|
||||
VARIANT_ESP32C3: {
|
||||
0: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
1: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
3: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
4: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
},
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c6/include/soc/adc_channel.h
|
||||
VARIANT_ESP32C6: {
|
||||
0: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
1: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
3: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
4: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
5: adc1_channel_t.ADC1_CHANNEL_5,
|
||||
6: adc1_channel_t.ADC1_CHANNEL_6,
|
||||
},
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32h2/include/soc/adc_channel.h
|
||||
VARIANT_ESP32H2: {
|
||||
1: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
3: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
4: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
5: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
},
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/adc_channel.h
|
||||
VARIANT_ESP32S2: {
|
||||
1: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
@@ -72,6 +108,7 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
|
||||
9: adc1_channel_t.ADC1_CHANNEL_8,
|
||||
10: adc1_channel_t.ADC1_CHANNEL_9,
|
||||
},
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s3/include/soc/adc_channel.h
|
||||
VARIANT_ESP32S3: {
|
||||
1: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
@@ -84,40 +121,12 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
|
||||
9: adc1_channel_t.ADC1_CHANNEL_8,
|
||||
10: adc1_channel_t.ADC1_CHANNEL_9,
|
||||
},
|
||||
VARIANT_ESP32C3: {
|
||||
0: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
1: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
3: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
4: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
},
|
||||
VARIANT_ESP32C2: {
|
||||
0: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
1: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
3: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
4: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
},
|
||||
VARIANT_ESP32C6: {
|
||||
0: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
1: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
3: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
4: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
5: adc1_channel_t.ADC1_CHANNEL_5,
|
||||
6: adc1_channel_t.ADC1_CHANNEL_6,
|
||||
},
|
||||
VARIANT_ESP32H2: {
|
||||
1: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
3: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
4: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
5: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
},
|
||||
}
|
||||
|
||||
# pin to adc2 channel mapping
|
||||
# https://github.com/espressif/esp-idf/blob/v4.4.8/components/driver/include/driver/adc.h
|
||||
ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
|
||||
# TODO: add other variants
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32/include/soc/adc_channel.h
|
||||
VARIANT_ESP32: {
|
||||
4: adc2_channel_t.ADC2_CHANNEL_0,
|
||||
0: adc2_channel_t.ADC2_CHANNEL_1,
|
||||
@@ -130,6 +139,19 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
|
||||
25: adc2_channel_t.ADC2_CHANNEL_8,
|
||||
26: adc2_channel_t.ADC2_CHANNEL_9,
|
||||
},
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c2/include/soc/adc_channel.h
|
||||
VARIANT_ESP32C2: {
|
||||
5: adc2_channel_t.ADC2_CHANNEL_0,
|
||||
},
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c3/include/soc/adc_channel.h
|
||||
VARIANT_ESP32C3: {
|
||||
5: adc2_channel_t.ADC2_CHANNEL_0,
|
||||
},
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c6/include/soc/adc_channel.h
|
||||
VARIANT_ESP32C6: {}, # no ADC2
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32h2/include/soc/adc_channel.h
|
||||
VARIANT_ESP32H2: {}, # no ADC2
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/adc_channel.h
|
||||
VARIANT_ESP32S2: {
|
||||
11: adc2_channel_t.ADC2_CHANNEL_0,
|
||||
12: adc2_channel_t.ADC2_CHANNEL_1,
|
||||
@@ -142,6 +164,7 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
|
||||
19: adc2_channel_t.ADC2_CHANNEL_8,
|
||||
20: adc2_channel_t.ADC2_CHANNEL_9,
|
||||
},
|
||||
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s3/include/soc/adc_channel.h
|
||||
VARIANT_ESP32S3: {
|
||||
11: adc2_channel_t.ADC2_CHANNEL_0,
|
||||
12: adc2_channel_t.ADC2_CHANNEL_1,
|
||||
@@ -154,12 +177,6 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
|
||||
19: adc2_channel_t.ADC2_CHANNEL_8,
|
||||
20: adc2_channel_t.ADC2_CHANNEL_9,
|
||||
},
|
||||
VARIANT_ESP32C3: {
|
||||
5: adc2_channel_t.ADC2_CHANNEL_0,
|
||||
},
|
||||
VARIANT_ESP32C2: {},
|
||||
VARIANT_ESP32C6: {},
|
||||
VARIANT_ESP32H2: {},
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@ void AnalogThresholdBinarySensor::setup() {
|
||||
if (std::isnan(sensor_value)) {
|
||||
this->publish_initial_state(false);
|
||||
} else {
|
||||
this->publish_initial_state(sensor_value >= (this->lower_threshold_ + this->upper_threshold_) / 2.0f);
|
||||
this->publish_initial_state(sensor_value >=
|
||||
(this->lower_threshold_.value() + this->upper_threshold_.value()) / 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +25,8 @@ void AnalogThresholdBinarySensor::set_sensor(sensor::Sensor *analog_sensor) {
|
||||
this->sensor_->add_on_state_callback([this](float sensor_value) {
|
||||
// if there is an invalid sensor reading, ignore the change and keep the current state
|
||||
if (!std::isnan(sensor_value)) {
|
||||
this->publish_state(sensor_value >= (this->state ? this->lower_threshold_ : this->upper_threshold_));
|
||||
this->publish_state(sensor_value >=
|
||||
(this->state ? this->lower_threshold_.value() : this->upper_threshold_.value()));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -32,8 +34,8 @@ void AnalogThresholdBinarySensor::set_sensor(sensor::Sensor *analog_sensor) {
|
||||
void AnalogThresholdBinarySensor::dump_config() {
|
||||
LOG_BINARY_SENSOR("", "Analog Threshold Binary Sensor", this);
|
||||
LOG_SENSOR(" ", "Sensor", this->sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Upper threshold: %.11f", this->upper_threshold_);
|
||||
ESP_LOGCONFIG(TAG, " Lower threshold: %.11f", this->lower_threshold_);
|
||||
ESP_LOGCONFIG(TAG, " Upper threshold: %.11f", this->upper_threshold_.value());
|
||||
ESP_LOGCONFIG(TAG, " Lower threshold: %.11f", this->lower_threshold_.value());
|
||||
}
|
||||
|
||||
} // namespace analog_threshold
|
||||
|
||||
@@ -15,14 +15,13 @@ class AnalogThresholdBinarySensor : public Component, public binary_sensor::Bina
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
void set_sensor(sensor::Sensor *analog_sensor);
|
||||
void set_upper_threshold(float threshold) { this->upper_threshold_ = threshold; }
|
||||
void set_lower_threshold(float threshold) { this->lower_threshold_ = threshold; }
|
||||
template<typename T> void set_upper_threshold(T upper_threshold) { this->upper_threshold_ = upper_threshold; }
|
||||
template<typename T> void set_lower_threshold(T lower_threshold) { this->lower_threshold_ = lower_threshold; }
|
||||
|
||||
protected:
|
||||
sensor::Sensor *sensor_{nullptr};
|
||||
|
||||
float upper_threshold_;
|
||||
float lower_threshold_;
|
||||
TemplatableValue<float> upper_threshold_{};
|
||||
TemplatableValue<float> lower_threshold_{};
|
||||
};
|
||||
|
||||
} // namespace analog_threshold
|
||||
|
||||
@@ -18,11 +18,11 @@ CONFIG_SCHEMA = (
|
||||
{
|
||||
cv.Required(CONF_SENSOR_ID): cv.use_id(sensor.Sensor),
|
||||
cv.Required(CONF_THRESHOLD): cv.Any(
|
||||
cv.float_,
|
||||
cv.templatable(cv.float_),
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_UPPER): cv.float_,
|
||||
cv.Required(CONF_LOWER): cv.float_,
|
||||
cv.Required(CONF_UPPER): cv.templatable(cv.float_),
|
||||
cv.Required(CONF_LOWER): cv.templatable(cv.float_),
|
||||
}
|
||||
),
|
||||
),
|
||||
@@ -39,9 +39,11 @@ async def to_code(config):
|
||||
sens = await cg.get_variable(config[CONF_SENSOR_ID])
|
||||
cg.add(var.set_sensor(sens))
|
||||
|
||||
if isinstance(config[CONF_THRESHOLD], float):
|
||||
cg.add(var.set_upper_threshold(config[CONF_THRESHOLD]))
|
||||
cg.add(var.set_lower_threshold(config[CONF_THRESHOLD]))
|
||||
if isinstance(config[CONF_THRESHOLD], dict):
|
||||
lower = await cg.templatable(config[CONF_THRESHOLD][CONF_LOWER], [], float)
|
||||
upper = await cg.templatable(config[CONF_THRESHOLD][CONF_UPPER], [], float)
|
||||
else:
|
||||
cg.add(var.set_upper_threshold(config[CONF_THRESHOLD][CONF_UPPER]))
|
||||
cg.add(var.set_lower_threshold(config[CONF_THRESHOLD][CONF_LOWER]))
|
||||
lower = await cg.templatable(config[CONF_THRESHOLD], [], float)
|
||||
upper = lower
|
||||
cg.add(var.set_upper_threshold(upper))
|
||||
cg.add(var.set_lower_threshold(lower))
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import base64
|
||||
import logging
|
||||
|
||||
from esphome import automation
|
||||
from esphome.automation import Condition
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ACTION,
|
||||
@@ -23,12 +25,14 @@ from esphome.const import (
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_VARIABLES,
|
||||
)
|
||||
from esphome.core import coroutine_with_priority
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
|
||||
DEPENDENCIES = ["network"]
|
||||
AUTO_LOAD = ["socket"]
|
||||
CODEOWNERS = ["@OttoWinter"]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
api_ns = cg.esphome_ns.namespace("api")
|
||||
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
|
||||
HomeAssistantServiceCallAction = api_ns.class_(
|
||||
@@ -49,6 +53,11 @@ SERVICE_ARG_NATIVE_TYPES = {
|
||||
"string[]": cg.std_vector.template(cg.std_string),
|
||||
}
|
||||
CONF_ENCRYPTION = "encryption"
|
||||
CONF_HEAP_TRACING = "heap_tracing"
|
||||
CONF_HEAP_TRACING_STANDALONE = "standalone" # vs SYSTEM
|
||||
CONF_HEAP_TRACING_RECORDS = "num_records"
|
||||
CONF_HEAP_TASK_TRACKING = "task_tracking"
|
||||
CONF_HEAP_TASK_MAX = "max_tasks"
|
||||
|
||||
|
||||
def validate_encryption_key(value):
|
||||
@@ -82,6 +91,35 @@ ACTIONS_SCHEMA = automation.validate_automation(
|
||||
),
|
||||
)
|
||||
|
||||
ENCRYPTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_KEY): validate_encryption_key,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _encryption_schema(config):
|
||||
if config is None:
|
||||
config = {}
|
||||
return ENCRYPTION_SCHEMA(config)
|
||||
|
||||
|
||||
HEAP_TRACING_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_HEAP_TRACING_STANDALONE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_HEAP_TRACING_RECORDS, default=100): cv.positive_int,
|
||||
cv.Optional(CONF_HEAP_TASK_TRACKING, default=True): cv.boolean,
|
||||
cv.Optional(CONF_HEAP_TASK_MAX, default=10): cv.positive_int,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _heap_tracing_schema(config):
|
||||
if config is None:
|
||||
config = {}
|
||||
return HEAP_TRACING_SCHEMA(config)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
@@ -95,11 +133,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
CONF_SERVICES, group_of_exclusion=CONF_ACTIONS
|
||||
): ACTIONS_SCHEMA,
|
||||
cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA,
|
||||
cv.Optional(CONF_ENCRYPTION): cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_KEY): validate_encryption_key,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ENCRYPTION): _encryption_schema,
|
||||
cv.Optional(CONF_HEAP_TRACING): _heap_tracing_schema,
|
||||
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
@@ -151,14 +186,90 @@ async def to_code(config):
|
||||
config[CONF_ON_CLIENT_DISCONNECTED],
|
||||
)
|
||||
|
||||
if encryption_config := config.get(CONF_ENCRYPTION):
|
||||
decoded = base64.b64decode(encryption_config[CONF_KEY])
|
||||
cg.add(var.set_noise_psk(list(decoded)))
|
||||
if (encryption_config := config.get(CONF_ENCRYPTION, None)) is not None:
|
||||
if key := encryption_config.get(CONF_KEY):
|
||||
decoded = base64.b64decode(key)
|
||||
cg.add(var.set_noise_psk(list(decoded)))
|
||||
else:
|
||||
# No key provided, but encryption desired
|
||||
# This will allow a plaintext client to provide a noise key,
|
||||
# send it to the device, and then switch to noise.
|
||||
# The key will be saved in flash and used for future connections
|
||||
# and plaintext disabled. Only a factory reset can remove it.
|
||||
cg.add_define("USE_API_PLAINTEXT")
|
||||
cg.add_define("USE_API_NOISE")
|
||||
cg.add_library("esphome/noise-c", "0.1.6")
|
||||
else:
|
||||
cg.add_define("USE_API_PLAINTEXT")
|
||||
|
||||
# Handle heap tracing configuration if ESP32 platform and using ESP-IDF
|
||||
if (heap_tracing_config := config.get(CONF_HEAP_TRACING, None)) is not None:
|
||||
if CORE.using_esp_idf:
|
||||
# Enable heap tracing in sdkconfig
|
||||
add_idf_sdkconfig_option("CONFIG_HEAP_TRACING", True)
|
||||
add_idf_sdkconfig_option("CONFIG_HEAP_TRACE_STACK_DEPTH", "30")
|
||||
add_idf_sdkconfig_option("CONFIG_ESP32_APPTRACE_ENABLE", True)
|
||||
|
||||
# Set tracing mode (standalone or system)
|
||||
if heap_tracing_config[CONF_HEAP_TRACING_STANDALONE]:
|
||||
add_idf_sdkconfig_option("CONFIG_HEAP_TRACING_STANDALONE", True)
|
||||
else:
|
||||
add_idf_sdkconfig_option("CONFIG_HEAP_TRACING_SYSTEM", True)
|
||||
|
||||
# Enable runtime stats gathering for task info
|
||||
if heap_tracing_config[CONF_HEAP_TASK_TRACKING]:
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS", True
|
||||
)
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS", True
|
||||
)
|
||||
add_idf_sdkconfig_option("CONFIG_FREERTOS_USE_TRACE_FACILITY", True)
|
||||
|
||||
# Generate code to implement heap tracing
|
||||
cg.add_global(cg.RawStatement('#include "esp_heap_trace.h"'))
|
||||
|
||||
# Define the trace record buffer
|
||||
num_records = heap_tracing_config[CONF_HEAP_TRACING_RECORDS]
|
||||
cg.add_global(
|
||||
cg.RawStatement(
|
||||
f"static heap_trace_record_t trace_record[{num_records}];"
|
||||
)
|
||||
)
|
||||
|
||||
# No additional setup needed for task tracking
|
||||
|
||||
# Add helper functions for heap tracing with extern "C" to make them globally accessible
|
||||
cg.add_global(
|
||||
cg.RawStatement(
|
||||
"""
|
||||
// Global heap tracing functions that can be called from any context
|
||||
extern "C" void start_heap_trace() {
|
||||
heap_trace_init_standalone(trace_record, """
|
||||
+ str(num_records)
|
||||
+ """);
|
||||
heap_trace_start(HEAP_TRACE_LEAKS);
|
||||
}
|
||||
|
||||
extern "C" void stop_and_dump_heap_trace() {
|
||||
heap_trace_stop();
|
||||
heap_trace_dump();
|
||||
}
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
# Add periodic heap trace dumping to the api_server.cpp file
|
||||
# This will be added in C++ code
|
||||
cg.add_define("USE_API_HEAP_TRACE")
|
||||
|
||||
else:
|
||||
# Not using ESP-IDF, so we can't use heap tracing
|
||||
_LOGGER.warning(
|
||||
"Heap tracing is only available when using ESP-IDF. "
|
||||
"Disabling heap tracing configuration."
|
||||
)
|
||||
|
||||
cg.add_define("USE_API")
|
||||
cg.add_global(api_ns.using)
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ service APIConnection {
|
||||
option (needs_authentication) = false;
|
||||
}
|
||||
rpc execute_service (ExecuteServiceRequest) returns (void) {}
|
||||
rpc noise_encryption_set_key (NoiseEncryptionSetKeyRequest) returns (NoiseEncryptionSetKeyResponse) {}
|
||||
|
||||
rpc cover_command (CoverCommandRequest) returns (void) {}
|
||||
rpc fan_command (FanCommandRequest) returns (void) {}
|
||||
@@ -60,6 +61,7 @@ service APIConnection {
|
||||
rpc bluetooth_gatt_notify(BluetoothGATTNotifyRequest) returns (void) {}
|
||||
rpc subscribe_bluetooth_connections_free(SubscribeBluetoothConnectionsFreeRequest) returns (BluetoothConnectionsFreeResponse) {}
|
||||
rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
|
||||
rpc bluetooth_scanner_set_mode(BluetoothScannerSetModeRequest) returns (void) {}
|
||||
|
||||
rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {}
|
||||
rpc voice_assistant_get_configuration(VoiceAssistantConfigurationRequest) returns (VoiceAssistantConfigurationResponse) {}
|
||||
@@ -230,6 +232,9 @@ message DeviceInfoResponse {
|
||||
|
||||
// The Bluetooth mac address of the device. For example "AC:BC:32:89:0E:AA"
|
||||
string bluetooth_mac_address = 18;
|
||||
|
||||
// Supports receiving and saving api encryption key
|
||||
bool api_encryption_supported = 19;
|
||||
}
|
||||
|
||||
message ListEntitiesRequest {
|
||||
@@ -654,6 +659,23 @@ message SubscribeLogsResponse {
|
||||
bool send_failed = 4;
|
||||
}
|
||||
|
||||
// ==================== NOISE ENCRYPTION ====================
|
||||
message NoiseEncryptionSetKeyRequest {
|
||||
option (id) = 124;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_API_NOISE";
|
||||
|
||||
bytes key = 1;
|
||||
}
|
||||
|
||||
message NoiseEncryptionSetKeyResponse {
|
||||
option (id) = 125;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_API_NOISE";
|
||||
|
||||
bool success = 1;
|
||||
}
|
||||
|
||||
// ==================== HOMEASSISTANT.SERVICE ====================
|
||||
message SubscribeHomeassistantServicesRequest {
|
||||
option (id) = 34;
|
||||
@@ -1451,6 +1473,37 @@ message BluetoothDeviceClearCacheResponse {
|
||||
int32 error = 3;
|
||||
}
|
||||
|
||||
enum BluetoothScannerState {
|
||||
BLUETOOTH_SCANNER_STATE_IDLE = 0;
|
||||
BLUETOOTH_SCANNER_STATE_STARTING = 1;
|
||||
BLUETOOTH_SCANNER_STATE_RUNNING = 2;
|
||||
BLUETOOTH_SCANNER_STATE_FAILED = 3;
|
||||
BLUETOOTH_SCANNER_STATE_STOPPING = 4;
|
||||
BLUETOOTH_SCANNER_STATE_STOPPED = 5;
|
||||
}
|
||||
|
||||
enum BluetoothScannerMode {
|
||||
BLUETOOTH_SCANNER_MODE_PASSIVE = 0;
|
||||
BLUETOOTH_SCANNER_MODE_ACTIVE = 1;
|
||||
}
|
||||
|
||||
message BluetoothScannerStateResponse {
|
||||
option(id) = 126;
|
||||
option(source) = SOURCE_SERVER;
|
||||
option(ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
BluetoothScannerState state = 1;
|
||||
BluetoothScannerMode mode = 2;
|
||||
}
|
||||
|
||||
message BluetoothScannerSetModeRequest {
|
||||
option(id) = 127;
|
||||
option(source) = SOURCE_CLIENT;
|
||||
option(ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
BluetoothScannerMode mode = 1;
|
||||
}
|
||||
|
||||
// ==================== PUSH TO TALK ====================
|
||||
enum VoiceAssistantSubscribeFlag {
|
||||
VOICE_ASSISTANT_SUBSCRIBE_NONE = 0;
|
||||
|
||||
@@ -62,7 +62,14 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
|
||||
: parent_(parent), deferred_message_queue_(this), initial_state_iterator_(this), list_entities_iterator_(this) {
|
||||
this->proto_write_buffer_.reserve(64);
|
||||
|
||||
#if defined(USE_API_PLAINTEXT)
|
||||
#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
|
||||
auto noise_ctx = parent->get_noise_ctx();
|
||||
if (noise_ctx->has_psk()) {
|
||||
this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), noise_ctx)};
|
||||
} else {
|
||||
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
|
||||
}
|
||||
#elif defined(USE_API_PLAINTEXT)
|
||||
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
|
||||
#elif defined(USE_API_NOISE)
|
||||
this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())};
|
||||
@@ -185,15 +192,34 @@ void APIConnection::loop() {
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
if (this->image_reader_.available() && this->helper_->can_write_without_blocking()) {
|
||||
uint32_t to_send = std::min((size_t) 1024, this->image_reader_.available());
|
||||
auto buffer = this->create_buffer();
|
||||
// Message will use 8 more bytes than the minimum size, and typical
|
||||
// MTU is 1500. Sometimes users will see as low as 1460 MTU.
|
||||
// If its IPv6 the header is 40 bytes, and if its IPv4
|
||||
// the header is 20 bytes. So we have 1460 - 40 = 1420 bytes
|
||||
// available for the payload. But we also need to add the size of
|
||||
// the protobuf overhead, which is 8 bytes.
|
||||
//
|
||||
// To be safe we pick 1390 bytes as the maximum size
|
||||
// to send in one go. This is the maximum size of a single packet
|
||||
// that can be sent over the network.
|
||||
// This is to avoid fragmentation of the packet.
|
||||
uint32_t to_send = std::min((size_t) 1390, this->image_reader_.available());
|
||||
bool done = this->image_reader_.available() == to_send;
|
||||
uint32_t msg_size = 0;
|
||||
ProtoSize::add_fixed_field<4>(msg_size, 1, true);
|
||||
// partial message size calculated manually since its a special case
|
||||
// 1 for the data field, varint for the data size, and the data itself
|
||||
msg_size += 1 + ProtoSize::varint(to_send) + to_send;
|
||||
ProtoSize::add_bool_field(msg_size, 1, done);
|
||||
|
||||
auto buffer = this->create_buffer(msg_size);
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, esp32_camera::global_esp32_camera->get_object_id_hash());
|
||||
// bytes data = 2;
|
||||
buffer.encode_bytes(2, this->image_reader_.peek_data_buffer(), to_send);
|
||||
// bool done = 3;
|
||||
bool done = this->image_reader_.available() == to_send;
|
||||
buffer.encode_bool(3, done);
|
||||
|
||||
bool success = this->send_buffer(buffer, 44);
|
||||
|
||||
if (success) {
|
||||
@@ -1468,6 +1494,11 @@ BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_
|
||||
resp.limit = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_limit();
|
||||
return resp;
|
||||
}
|
||||
|
||||
void APIConnection::bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) {
|
||||
bluetooth_proxy::global_bluetooth_proxy->bluetooth_scanner_set_mode(
|
||||
msg.mode == enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
@@ -1762,12 +1793,25 @@ bool APIConnection::try_send_log_message(int level, const char *tag, const char
|
||||
if (this->log_subscription_ < level)
|
||||
return false;
|
||||
|
||||
// Send raw so that we don't copy too much
|
||||
auto buffer = this->create_buffer();
|
||||
// LogLevel level = 1;
|
||||
buffer.encode_uint32(1, static_cast<uint32_t>(level));
|
||||
// string message = 3;
|
||||
buffer.encode_string(3, line, strlen(line));
|
||||
// Pre-calculate message size to avoid reallocations
|
||||
const size_t line_length = strlen(line);
|
||||
uint32_t msg_size = 0;
|
||||
|
||||
// Add size for level field (field ID 1, varint type)
|
||||
// 1 byte for field tag + size of the level varint
|
||||
msg_size += 1 + api::ProtoSize::varint(static_cast<uint32_t>(level));
|
||||
|
||||
// Add size for string field (field ID 3, string type)
|
||||
// 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;
|
||||
|
||||
// Create a pre-sized buffer
|
||||
auto buffer = this->create_buffer(msg_size);
|
||||
|
||||
// Encode the message (SubscribeLogsResponse)
|
||||
buffer.encode_uint32(1, static_cast<uint32_t>(level)); // LogLevel level = 1
|
||||
buffer.encode_string(3, line, line_length); // string message = 3
|
||||
|
||||
// SubscribeLogsResponse - 29
|
||||
return this->send_buffer(buffer, 29);
|
||||
}
|
||||
@@ -1848,6 +1892,9 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
resp.legacy_voice_assistant_version = voice_assistant::global_voice_assistant->get_legacy_version();
|
||||
resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags();
|
||||
#endif
|
||||
#ifdef USE_API_NOISE
|
||||
resp.api_encryption_supported = true;
|
||||
#endif
|
||||
return resp;
|
||||
}
|
||||
@@ -1869,6 +1916,26 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
||||
ESP_LOGV(TAG, "Could not find matching service!");
|
||||
}
|
||||
}
|
||||
#ifdef USE_API_NOISE
|
||||
NoiseEncryptionSetKeyResponse APIConnection::noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) {
|
||||
psk_t psk{};
|
||||
NoiseEncryptionSetKeyResponse resp;
|
||||
if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) {
|
||||
ESP_LOGW(TAG, "Invalid encryption key length");
|
||||
resp.success = false;
|
||||
return resp;
|
||||
}
|
||||
|
||||
if (!this->parent_->save_noise_psk(psk, true)) {
|
||||
ESP_LOGW(TAG, "Failed to save encryption key");
|
||||
resp.success = false;
|
||||
return resp;
|
||||
}
|
||||
|
||||
resp.success = true;
|
||||
return resp;
|
||||
}
|
||||
#endif
|
||||
void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) {
|
||||
state_subs_at_ = 0;
|
||||
}
|
||||
|
||||
@@ -221,6 +221,7 @@ class APIConnection : public APIServerConnection {
|
||||
void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) override;
|
||||
BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free(
|
||||
const SubscribeBluetoothConnectionsFreeRequest &msg) override;
|
||||
void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) override;
|
||||
|
||||
#endif
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
@@ -300,6 +301,9 @@ class APIConnection : public APIServerConnection {
|
||||
return {};
|
||||
}
|
||||
void execute_service(const ExecuteServiceRequest &msg) override;
|
||||
#ifdef USE_API_NOISE
|
||||
NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) override;
|
||||
#endif
|
||||
|
||||
bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; }
|
||||
bool is_connection_setup() override {
|
||||
@@ -308,9 +312,10 @@ class APIConnection : public APIServerConnection {
|
||||
void on_fatal_error() override;
|
||||
void on_unauthenticated_access() override;
|
||||
void on_no_setup_connection() override;
|
||||
ProtoWriteBuffer create_buffer() override {
|
||||
ProtoWriteBuffer create_buffer(uint32_t reserve_size) override {
|
||||
// FIXME: ensure no recursive writes can happen
|
||||
this->proto_write_buffer_.clear();
|
||||
this->proto_write_buffer_.reserve(reserve_size);
|
||||
return {&this->proto_write_buffer_};
|
||||
}
|
||||
bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) override;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "proto.h"
|
||||
#include "api_pb2_size.h"
|
||||
#include <cstring>
|
||||
|
||||
namespace esphome {
|
||||
@@ -574,6 +575,8 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
|
||||
|
||||
if (!tx_buf_.empty()) {
|
||||
// tx buf not empty, can't write now because then stream would be inconsistent
|
||||
// Reserve space upfront to avoid multiple reallocations
|
||||
tx_buf_.reserve(tx_buf_.size() + total_write_len);
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
|
||||
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
|
||||
@@ -584,6 +587,8 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
|
||||
ssize_t sent = socket_->writev(iov, iovcnt);
|
||||
if (is_would_block(sent)) {
|
||||
// operation would block, add buffer to tx_buf
|
||||
// Reserve space upfront to avoid multiple reallocations
|
||||
tx_buf_.reserve(tx_buf_.size() + total_write_len);
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
|
||||
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
|
||||
@@ -596,6 +601,10 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
} else if ((size_t) sent != total_write_len) {
|
||||
// partially sent, add end to tx_buf
|
||||
size_t remaining = total_write_len - sent;
|
||||
// Reserve space upfront to avoid multiple reallocations
|
||||
tx_buf_.reserve(tx_buf_.size() + remaining);
|
||||
|
||||
size_t to_consume = sent;
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
if (to_consume >= iov[i].iov_len) {
|
||||
@@ -933,6 +942,8 @@ APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *pay
|
||||
}
|
||||
|
||||
std::vector<uint8_t> header;
|
||||
header.reserve(1 + api::ProtoSize::varint(static_cast<uint32_t>(payload_len)) +
|
||||
api::ProtoSize::varint(static_cast<uint32_t>(type)));
|
||||
header.push_back(0x00);
|
||||
ProtoVarInt(payload_len).encode(header);
|
||||
ProtoVarInt(type).encode(header);
|
||||
@@ -994,6 +1005,8 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt
|
||||
|
||||
if (!tx_buf_.empty()) {
|
||||
// tx buf not empty, can't write now because then stream would be inconsistent
|
||||
// Reserve space upfront to avoid multiple reallocations
|
||||
tx_buf_.reserve(tx_buf_.size() + total_write_len);
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
|
||||
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
|
||||
@@ -1004,6 +1017,8 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt
|
||||
ssize_t sent = socket_->writev(iov, iovcnt);
|
||||
if (is_would_block(sent)) {
|
||||
// operation would block, add buffer to tx_buf
|
||||
// Reserve space upfront to avoid multiple reallocations
|
||||
tx_buf_.reserve(tx_buf_.size() + total_write_len);
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
|
||||
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
|
||||
@@ -1016,6 +1031,10 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
} else if ((size_t) sent != total_write_len) {
|
||||
// partially sent, add end to tx_buf
|
||||
size_t remaining = total_write_len - sent;
|
||||
// Reserve space upfront to avoid multiple reallocations
|
||||
tx_buf_.reserve(tx_buf_.size() + remaining);
|
||||
|
||||
size_t to_consume = sent;
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
if (to_consume >= iov[i].iov_len) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -11,11 +11,20 @@ using psk_t = std::array<uint8_t, 32>;
|
||||
|
||||
class APINoiseContext {
|
||||
public:
|
||||
void set_psk(psk_t psk) { psk_ = psk; }
|
||||
const psk_t &get_psk() const { return psk_; }
|
||||
void set_psk(psk_t psk) {
|
||||
this->psk_ = psk;
|
||||
bool has_psk = false;
|
||||
for (auto i : psk) {
|
||||
has_psk |= i;
|
||||
}
|
||||
this->has_psk_ = has_psk;
|
||||
}
|
||||
const psk_t &get_psk() const { return this->psk_; }
|
||||
bool has_psk() const { return this->has_psk_; }
|
||||
|
||||
protected:
|
||||
psk_t psk_;
|
||||
psk_t psk_{};
|
||||
bool has_psk_{false};
|
||||
};
|
||||
#endif // USE_API_NOISE
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
// This file was automatically generated with a tool.
|
||||
// See scripts/api_protobuf/api_protobuf.py
|
||||
// See script/api_protobuf/api_protobuf.py
|
||||
#include "api_pb2_service.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
@@ -179,6 +179,16 @@ bool APIServerConnectionBase::send_text_sensor_state_response(const TextSensorSt
|
||||
bool APIServerConnectionBase::send_subscribe_logs_response(const SubscribeLogsResponse &msg) {
|
||||
return this->send_message_<SubscribeLogsResponse>(msg, 29);
|
||||
}
|
||||
#ifdef USE_API_NOISE
|
||||
#endif
|
||||
#ifdef USE_API_NOISE
|
||||
bool APIServerConnectionBase::send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_noise_encryption_set_key_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<NoiseEncryptionSetKeyResponse>(msg, 125);
|
||||
}
|
||||
#endif
|
||||
bool APIServerConnectionBase::send_homeassistant_service_response(const HomeassistantServiceResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_homeassistant_service_response: %s", msg.dump().c_str());
|
||||
@@ -462,6 +472,16 @@ bool APIServerConnectionBase::send_bluetooth_device_clear_cache_response(const B
|
||||
return this->send_message_<BluetoothDeviceClearCacheResponse>(msg, 88);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool APIServerConnectionBase::send_bluetooth_scanner_state_response(const BluetoothScannerStateResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_bluetooth_scanner_state_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<BluetoothScannerStateResponse>(msg, 126);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
@@ -1191,6 +1211,28 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
ESP_LOGVV(TAG, "on_voice_assistant_set_configuration: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_voice_assistant_set_configuration(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 124: {
|
||||
#ifdef USE_API_NOISE
|
||||
NoiseEncryptionSetKeyRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_noise_encryption_set_key_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_noise_encryption_set_key_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 127: {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
BluetoothScannerSetModeRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_bluetooth_scanner_set_mode_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_bluetooth_scanner_set_mode_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -1311,6 +1353,22 @@ void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest
|
||||
}
|
||||
this->execute_service(msg);
|
||||
}
|
||||
#ifdef USE_API_NOISE
|
||||
void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
NoiseEncryptionSetKeyResponse ret = this->noise_encryption_set_key(msg);
|
||||
if (!this->send_noise_encryption_set_key_response(ret)) {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
@@ -1668,6 +1726,19 @@ void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request(
|
||||
this->unsubscribe_bluetooth_le_advertisements(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void APIServerConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->bluetooth_scanner_set_mode(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// This file was automatically generated with a tool.
|
||||
// See scripts/api_protobuf/api_protobuf.py
|
||||
// See script/api_protobuf/api_protobuf.py
|
||||
#pragma once
|
||||
|
||||
#include "api_pb2.h"
|
||||
@@ -83,6 +83,12 @@ class APIServerConnectionBase : public ProtoService {
|
||||
#endif
|
||||
virtual void on_subscribe_logs_request(const SubscribeLogsRequest &value){};
|
||||
bool send_subscribe_logs_response(const SubscribeLogsResponse &msg);
|
||||
#ifdef USE_API_NOISE
|
||||
virtual void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_API_NOISE
|
||||
bool send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyResponse &msg);
|
||||
#endif
|
||||
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
|
||||
bool send_homeassistant_service_response(const HomeassistantServiceResponse &msg);
|
||||
virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
|
||||
@@ -228,6 +234,12 @@ class APIServerConnectionBase : public ProtoService {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_scanner_state_response(const BluetoothScannerStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){};
|
||||
#endif
|
||||
@@ -349,6 +361,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
|
||||
virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0;
|
||||
virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
|
||||
#ifdef USE_API_NOISE
|
||||
virtual NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
virtual void cover_command(const CoverCommandRequest &msg) = 0;
|
||||
#endif
|
||||
@@ -431,6 +446,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0;
|
||||
#endif
|
||||
@@ -457,6 +475,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
|
||||
void on_get_time_request(const GetTimeRequest &msg) override;
|
||||
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
|
||||
#ifdef USE_API_NOISE
|
||||
void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
void on_cover_command_request(const CoverCommandRequest &msg) override;
|
||||
#endif
|
||||
@@ -539,6 +560,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
void on_unsubscribe_bluetooth_le_advertisements_request(
|
||||
const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override;
|
||||
#endif
|
||||
|
||||
361
esphome/components/api/api_pb2_size.h
Normal file
361
esphome/components/api/api_pb2_size.h
Normal file
@@ -0,0 +1,361 @@
|
||||
#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 templated version directly takes a message object, calculates its size internally,
|
||||
* and updates the total_size reference. This eliminates the need for a temporary variable
|
||||
* at the call site.
|
||||
*
|
||||
* @tparam MessageType The type of the nested message (inferred from parameter)
|
||||
* @param message The nested message object
|
||||
*/
|
||||
template<typename MessageType>
|
||||
static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const MessageType &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
|
||||
@@ -14,6 +14,96 @@
|
||||
#include "esphome/components/logger/logger.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HEAP_TRACE
|
||||
#include "esp_heap_trace.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
// Forward declare heap tracing functions that will be used in the API class
|
||||
extern "C" void start_heap_trace();
|
||||
extern "C" void stop_and_dump_heap_trace();
|
||||
|
||||
// Task heap information tracking
|
||||
extern "C" void dump_task_heap_info() {
|
||||
// Get basic heap statistics
|
||||
multi_heap_info_t info;
|
||||
heap_caps_get_info(&info, MALLOC_CAP_INTERNAL);
|
||||
|
||||
ESP_LOGI("HEAP", "=== Task Heap Information ===");
|
||||
ESP_LOGI("HEAP", "-------------------------------------");
|
||||
ESP_LOGI("HEAP", "Total free bytes: %u", info.total_free_bytes);
|
||||
ESP_LOGI("HEAP", "Total allocated bytes: %u", info.total_allocated_bytes);
|
||||
ESP_LOGI("HEAP", "Minimum free bytes: %u", info.minimum_free_bytes);
|
||||
ESP_LOGI("HEAP", "Largest free block: %u", info.largest_free_block);
|
||||
ESP_LOGI("HEAP", "Free blocks: %u", info.free_blocks);
|
||||
ESP_LOGI("HEAP", "Allocated blocks: %u", info.allocated_blocks);
|
||||
ESP_LOGI("HEAP", "Total blocks: %u", info.total_blocks);
|
||||
ESP_LOGI("HEAP", "-------------------------------------");
|
||||
|
||||
// Get information about running tasks with a much larger buffer to prevent overflow
|
||||
// The FreeRTOS functions don't provide a way to check buffer size requirements in advance
|
||||
static char buffer[2048];
|
||||
|
||||
// Zero out the buffer for safety
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
|
||||
// Get task list
|
||||
vTaskList(buffer);
|
||||
|
||||
// Check if buffer has valid content
|
||||
if (buffer[0] != '\0') {
|
||||
ESP_LOGI("HEAP", "Task Information:");
|
||||
ESP_LOGI("HEAP", "Name State Priority Stack Num");
|
||||
ESP_LOGI("HEAP", "-------------------------------------");
|
||||
|
||||
// Process the buffer line by line to add the log prefix to each line
|
||||
char *line = strtok(buffer, "\n\r");
|
||||
int count = 0;
|
||||
while (line != nullptr && strlen(line) > 0 && count < 20) {
|
||||
ESP_LOGI("HEAP", "%s", line);
|
||||
line = strtok(nullptr, "\n\r");
|
||||
count++;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE("HEAP", "Could not get task information");
|
||||
}
|
||||
|
||||
ESP_LOGI("HEAP", "-------------------------------------");
|
||||
|
||||
// Runtime statistics - use a separate section with a different buffer to avoid corruption
|
||||
static char stats_buffer[2048];
|
||||
memset(stats_buffer, 0, sizeof(stats_buffer));
|
||||
|
||||
// Get runtime stats
|
||||
vTaskGetRunTimeStats(stats_buffer);
|
||||
|
||||
// Check if buffer has valid content
|
||||
if (stats_buffer[0] != '\0') {
|
||||
ESP_LOGI("HEAP", "Task Runtime Statistics:");
|
||||
ESP_LOGI("HEAP", "Name Time Percentage");
|
||||
ESP_LOGI("HEAP", "-------------------------------------");
|
||||
|
||||
// Process the runtime stats buffer line by line safely
|
||||
char *line = strtok(stats_buffer, "\n\r");
|
||||
int count = 0;
|
||||
// Limit to 20 lines to prevent buffer overruns
|
||||
while (line != nullptr && count < 20) {
|
||||
// Skip empty lines
|
||||
if (strlen(line) > 0) {
|
||||
ESP_LOGI("HEAP", "%s", line);
|
||||
}
|
||||
line = strtok(nullptr, "\n\r");
|
||||
count++;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE("HEAP", "Could not get task runtime statistics");
|
||||
}
|
||||
|
||||
ESP_LOGI("HEAP", "-------------------------------------");
|
||||
}
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace esphome {
|
||||
@@ -22,22 +112,45 @@ namespace api {
|
||||
static const char *const TAG = "api";
|
||||
|
||||
// APIServer
|
||||
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
APIServer::APIServer() { global_api_server = this; }
|
||||
|
||||
void APIServer::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server...");
|
||||
this->setup_controller();
|
||||
socket_ = socket::socket_ip(SOCK_STREAM, 0);
|
||||
if (socket_ == nullptr) {
|
||||
ESP_LOGW(TAG, "Could not create socket.");
|
||||
|
||||
#ifdef USE_API_HEAP_TRACE
|
||||
ESP_LOGI(TAG, "Initializing heap tracing");
|
||||
start_heap_trace();
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
uint32_t hash = 88491486UL;
|
||||
|
||||
this->noise_pref_ = global_preferences->make_preference<SavedNoisePsk>(hash, true);
|
||||
|
||||
SavedNoisePsk noise_pref_saved{};
|
||||
if (this->noise_pref_.load(&noise_pref_saved)) {
|
||||
ESP_LOGD(TAG, "Loaded saved Noise PSK");
|
||||
|
||||
this->set_noise_psk(noise_pref_saved.psk);
|
||||
}
|
||||
#endif
|
||||
|
||||
this->socket_ = socket::socket_ip(SOCK_STREAM, 0);
|
||||
if (this->socket_ == nullptr) {
|
||||
ESP_LOGW(TAG, "Could not create socket");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
int enable = 1;
|
||||
int err = socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
|
||||
int err = this->socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
|
||||
if (err != 0) {
|
||||
ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err);
|
||||
// we can still continue
|
||||
}
|
||||
err = socket_->setblocking(false);
|
||||
err = this->socket_->setblocking(false);
|
||||
if (err != 0) {
|
||||
ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err);
|
||||
this->mark_failed();
|
||||
@@ -53,14 +166,14 @@ void APIServer::setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
err = socket_->bind((struct sockaddr *) &server, sl);
|
||||
err = this->socket_->bind((struct sockaddr *) &server, sl);
|
||||
if (err != 0) {
|
||||
ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
err = socket_->listen(4);
|
||||
err = this->socket_->listen(4);
|
||||
if (err != 0) {
|
||||
ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
|
||||
this->mark_failed();
|
||||
@@ -92,18 +205,19 @@ void APIServer::setup() {
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void APIServer::loop() {
|
||||
// Accept new clients
|
||||
while (true) {
|
||||
struct sockaddr_storage source_addr;
|
||||
socklen_t addr_len = sizeof(source_addr);
|
||||
auto sock = socket_->accept((struct sockaddr *) &source_addr, &addr_len);
|
||||
auto sock = this->socket_->accept((struct sockaddr *) &source_addr, &addr_len);
|
||||
if (!sock)
|
||||
break;
|
||||
ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str());
|
||||
|
||||
auto *conn = new APIConnection(std::move(sock), this);
|
||||
clients_.emplace_back(conn);
|
||||
this->clients_.emplace_back(conn);
|
||||
conn->start();
|
||||
}
|
||||
|
||||
@@ -135,17 +249,41 @@ void APIServer::loop() {
|
||||
this->status_clear_warning();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_API_HEAP_TRACE
|
||||
// Periodically dump heap trace information (every 30 seconds)
|
||||
static uint32_t last_heap_trace_dump = 0;
|
||||
const uint32_t now = millis();
|
||||
if (now - last_heap_trace_dump > 30000) { // 30 seconds
|
||||
ESP_LOGI(TAG, "Dumping heap trace information");
|
||||
stop_and_dump_heap_trace();
|
||||
|
||||
// Also dump task-specific heap information
|
||||
dump_task_heap_info();
|
||||
|
||||
// Start a new trace for the next period
|
||||
start_heap_trace();
|
||||
|
||||
last_heap_trace_dump = now;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void APIServer::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "API Server:");
|
||||
ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->port_);
|
||||
#ifdef USE_API_NOISE
|
||||
ESP_LOGCONFIG(TAG, " Using noise encryption: YES");
|
||||
ESP_LOGCONFIG(TAG, " Using noise encryption: %s", YESNO(this->noise_ctx_->has_psk()));
|
||||
if (!this->noise_ctx_->has_psk()) {
|
||||
ESP_LOGCONFIG(TAG, " Supports noise encryption: YES");
|
||||
}
|
||||
#else
|
||||
ESP_LOGCONFIG(TAG, " Using noise encryption: NO");
|
||||
#endif
|
||||
}
|
||||
|
||||
bool APIServer::uses_password() const { return !this->password_.empty(); }
|
||||
|
||||
bool APIServer::check_password(const std::string &password) const {
|
||||
// depend only on input password length
|
||||
const char *a = this->password_.c_str();
|
||||
@@ -174,7 +312,9 @@ bool APIServer::check_password(const std::string &password) const {
|
||||
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
void APIServer::handle_disconnect(APIConnection *conn) {}
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) {
|
||||
if (obj->is_internal())
|
||||
@@ -342,57 +482,6 @@ void APIServer::on_update(update::UpdateEntity *obj) {
|
||||
}
|
||||
#endif
|
||||
|
||||
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
||||
void APIServer::set_port(uint16_t port) { this->port_ = port; }
|
||||
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
void APIServer::set_password(const std::string &password) { this->password_ = password; }
|
||||
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
||||
for (auto &client : this->clients_) {
|
||||
client->send_homeassistant_service_call(call);
|
||||
}
|
||||
}
|
||||
|
||||
APIServer::APIServer() { global_api_server = this; }
|
||||
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(std::string)> f) {
|
||||
this->state_subs_.push_back(HomeAssistantStateSubscription{
|
||||
.entity_id = std::move(entity_id),
|
||||
.attribute = std::move(attribute),
|
||||
.callback = std::move(f),
|
||||
.once = false,
|
||||
});
|
||||
}
|
||||
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(std::string)> f) {
|
||||
this->state_subs_.push_back(HomeAssistantStateSubscription{
|
||||
.entity_id = std::move(entity_id),
|
||||
.attribute = std::move(attribute),
|
||||
.callback = std::move(f),
|
||||
.once = true,
|
||||
});
|
||||
};
|
||||
const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const {
|
||||
return this->state_subs_;
|
||||
}
|
||||
uint16_t APIServer::get_port() const { return this->port_; }
|
||||
void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void APIServer::request_time() {
|
||||
for (auto &client : this->clients_) {
|
||||
if (!client->remove_ && client->is_authenticated())
|
||||
client->send_time_request();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
bool APIServer::is_connected() const { return !this->clients_.empty(); }
|
||||
void APIServer::on_shutdown() {
|
||||
for (auto &c : this->clients_) {
|
||||
c->send_disconnect_request(DisconnectRequest());
|
||||
}
|
||||
delay(10);
|
||||
}
|
||||
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) {
|
||||
if (obj->is_internal())
|
||||
@@ -402,6 +491,106 @@ void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlP
|
||||
}
|
||||
#endif
|
||||
|
||||
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
void APIServer::set_port(uint16_t port) { this->port_ = port; }
|
||||
|
||||
void APIServer::set_password(const std::string &password) { this->password_ = password; }
|
||||
|
||||
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
||||
for (auto &client : this->clients_) {
|
||||
client->send_homeassistant_service_call(call);
|
||||
}
|
||||
}
|
||||
|
||||
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(std::string)> f) {
|
||||
this->state_subs_.push_back(HomeAssistantStateSubscription{
|
||||
.entity_id = std::move(entity_id),
|
||||
.attribute = std::move(attribute),
|
||||
.callback = std::move(f),
|
||||
.once = false,
|
||||
});
|
||||
}
|
||||
|
||||
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(std::string)> f) {
|
||||
this->state_subs_.push_back(HomeAssistantStateSubscription{
|
||||
.entity_id = std::move(entity_id),
|
||||
.attribute = std::move(attribute),
|
||||
.callback = std::move(f),
|
||||
.once = true,
|
||||
});
|
||||
};
|
||||
|
||||
const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const {
|
||||
return this->state_subs_;
|
||||
}
|
||||
|
||||
uint16_t APIServer::get_port() const { return this->port_; }
|
||||
|
||||
void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
|
||||
auto &old_psk = this->noise_ctx_->get_psk();
|
||||
if (std::equal(old_psk.begin(), old_psk.end(), psk.begin())) {
|
||||
ESP_LOGW(TAG, "New PSK matches old");
|
||||
return true;
|
||||
}
|
||||
|
||||
SavedNoisePsk new_saved_psk{psk};
|
||||
if (!this->noise_pref_.save(&new_saved_psk)) {
|
||||
ESP_LOGW(TAG, "Failed to save Noise PSK");
|
||||
return false;
|
||||
}
|
||||
// ensure it's written immediately
|
||||
if (!global_preferences->sync()) {
|
||||
ESP_LOGW(TAG, "Failed to sync preferences");
|
||||
return false;
|
||||
}
|
||||
ESP_LOGD(TAG, "Noise PSK saved");
|
||||
if (make_active) {
|
||||
this->set_timeout(100, [this, psk]() {
|
||||
ESP_LOGW(TAG, "Disconnecting all clients to reset connections");
|
||||
this->set_noise_psk(psk);
|
||||
for (auto &c : this->clients_) {
|
||||
c->send_disconnect_request(DisconnectRequest());
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void APIServer::request_time() {
|
||||
for (auto &client : this->clients_) {
|
||||
if (!client->remove_ && client->is_authenticated())
|
||||
client->send_time_request();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool APIServer::is_connected() const { return !this->clients_.empty(); }
|
||||
|
||||
void APIServer::on_shutdown() {
|
||||
for (auto &c : this->clients_) {
|
||||
c->send_disconnect_request(DisconnectRequest());
|
||||
}
|
||||
delay(10);
|
||||
|
||||
#ifdef USE_API_HEAP_TRACE
|
||||
// Make sure to stop tracing on shutdown to get final results
|
||||
ESP_LOGI(TAG, "Final heap trace dump on shutdown");
|
||||
stop_and_dump_heap_trace();
|
||||
|
||||
// Dump final task heap information
|
||||
ESP_LOGI(TAG, "Final task heap information dump on shutdown");
|
||||
dump_task_heap_info();
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
#endif
|
||||
|
||||
@@ -19,6 +19,12 @@
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
struct SavedNoisePsk {
|
||||
psk_t psk;
|
||||
} PACKED; // NOLINT
|
||||
#endif
|
||||
|
||||
class APIServer : public Component, public Controller {
|
||||
public:
|
||||
APIServer();
|
||||
@@ -35,6 +41,7 @@ class APIServer : public Component, public Controller {
|
||||
void set_reboot_timeout(uint32_t reboot_timeout);
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
bool save_noise_psk(psk_t psk, bool make_active = true);
|
||||
void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(psk); }
|
||||
std::shared_ptr<APINoiseContext> get_noise_ctx() { return noise_ctx_; }
|
||||
#endif // USE_API_NOISE
|
||||
@@ -142,6 +149,7 @@ class APIServer : public Component, public Controller {
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>();
|
||||
ESPPreferenceObject noise_pref_;
|
||||
#endif // USE_API_NOISE
|
||||
};
|
||||
|
||||
|
||||
@@ -149,6 +149,18 @@ class ProtoWriteBuffer {
|
||||
void write(uint8_t value) { this->buffer_->push_back(value); }
|
||||
void encode_varint_raw(ProtoVarInt value) { value.encode(*this->buffer_); }
|
||||
void encode_varint_raw(uint32_t value) { this->encode_varint_raw(ProtoVarInt(value)); }
|
||||
/**
|
||||
* Encode a field key (tag/wire type combination).
|
||||
*
|
||||
* @param field_id Field number (tag) in the protobuf message
|
||||
* @param type Wire type value:
|
||||
* - 0: Varint (int32, int64, uint32, uint64, sint32, sint64, bool, enum)
|
||||
* - 1: 64-bit (fixed64, sfixed64, double)
|
||||
* - 2: Length-delimited (string, bytes, embedded messages, packed repeated fields)
|
||||
* - 5: 32-bit (fixed32, sfixed32, float)
|
||||
*
|
||||
* Following https://protobuf.dev/programming-guides/encoding/#structure
|
||||
*/
|
||||
void encode_field_raw(uint32_t field_id, uint32_t type) {
|
||||
uint32_t val = (field_id << 3) | (type & 0b111);
|
||||
this->encode_varint_raw(val);
|
||||
@@ -157,7 +169,7 @@ class ProtoWriteBuffer {
|
||||
if (len == 0 && !force)
|
||||
return;
|
||||
|
||||
this->encode_field_raw(field_id, 2);
|
||||
this->encode_field_raw(field_id, 2); // type 2: Length-delimited string
|
||||
this->encode_varint_raw(len);
|
||||
auto *data = reinterpret_cast<const uint8_t *>(string);
|
||||
this->buffer_->insert(this->buffer_->end(), data, data + len);
|
||||
@@ -171,26 +183,26 @@ class ProtoWriteBuffer {
|
||||
void encode_uint32(uint32_t field_id, uint32_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 0);
|
||||
this->encode_field_raw(field_id, 0); // type 0: Varint - uint32
|
||||
this->encode_varint_raw(value);
|
||||
}
|
||||
void encode_uint64(uint32_t field_id, uint64_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 0);
|
||||
this->encode_field_raw(field_id, 0); // type 0: Varint - uint64
|
||||
this->encode_varint_raw(ProtoVarInt(value));
|
||||
}
|
||||
void encode_bool(uint32_t field_id, bool value, bool force = false) {
|
||||
if (!value && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 0);
|
||||
this->encode_field_raw(field_id, 0); // type 0: Varint - bool
|
||||
this->write(0x01);
|
||||
}
|
||||
void encode_fixed32(uint32_t field_id, uint32_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
|
||||
this->encode_field_raw(field_id, 5);
|
||||
this->encode_field_raw(field_id, 5); // type 5: 32-bit fixed32
|
||||
this->write((value >> 0) & 0xFF);
|
||||
this->write((value >> 8) & 0xFF);
|
||||
this->write((value >> 16) & 0xFF);
|
||||
@@ -200,7 +212,7 @@ class ProtoWriteBuffer {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
|
||||
this->encode_field_raw(field_id, 5);
|
||||
this->encode_field_raw(field_id, 1); // type 1: 64-bit fixed64
|
||||
this->write((value >> 0) & 0xFF);
|
||||
this->write((value >> 8) & 0xFF);
|
||||
this->write((value >> 16) & 0xFF);
|
||||
@@ -254,7 +266,7 @@ class ProtoWriteBuffer {
|
||||
this->encode_uint64(field_id, uvalue, force);
|
||||
}
|
||||
template<class C> void encode_message(uint32_t field_id, const C &value, bool force = false) {
|
||||
this->encode_field_raw(field_id, 2);
|
||||
this->encode_field_raw(field_id, 2); // type 2: Length-delimited message
|
||||
size_t begin = this->buffer_->size();
|
||||
|
||||
value.encode(*this);
|
||||
@@ -276,6 +288,7 @@ class ProtoMessage {
|
||||
virtual ~ProtoMessage() = default;
|
||||
virtual void encode(ProtoWriteBuffer buffer) const = 0;
|
||||
void decode(const uint8_t *buffer, size_t length);
|
||||
virtual void calculate_size(uint32_t &total_size) const = 0;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
std::string dump() const;
|
||||
virtual void dump_to(std::string &out) const = 0;
|
||||
@@ -298,13 +311,29 @@ class ProtoService {
|
||||
virtual void on_fatal_error() = 0;
|
||||
virtual void on_unauthenticated_access() = 0;
|
||||
virtual void on_no_setup_connection() = 0;
|
||||
virtual ProtoWriteBuffer create_buffer() = 0;
|
||||
/**
|
||||
* Create a buffer with a reserved size.
|
||||
* @param reserve_size The number of bytes to pre-allocate in the buffer. This is a hint
|
||||
* to optimize memory usage and avoid reallocations during encoding.
|
||||
* Implementations should aim to allocate at least this size.
|
||||
* @return A ProtoWriteBuffer object with the reserved size.
|
||||
*/
|
||||
virtual ProtoWriteBuffer create_buffer(uint32_t reserve_size) = 0;
|
||||
virtual bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) = 0;
|
||||
virtual bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0;
|
||||
|
||||
// Optimized method that pre-allocates buffer based on message size
|
||||
template<class C> bool send_message_(const C &msg, uint32_t message_type) {
|
||||
auto buffer = this->create_buffer();
|
||||
uint32_t msg_size = 0;
|
||||
msg.calculate_size(msg_size);
|
||||
|
||||
// Create a pre-sized buffer
|
||||
auto buffer = this->create_buffer(msg_size);
|
||||
|
||||
// Encode message into the buffer
|
||||
msg.encode(buffer);
|
||||
|
||||
// Send the buffer
|
||||
return this->send_buffer(buffer, message_type);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/as3935/as3935.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace as3935_i2c {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
namespace esphome {
|
||||
namespace as7341 {
|
||||
|
||||
static const uint8_t AS7341_CHIP_ID = 0X09;
|
||||
static const uint8_t AS7341_CHIP_ID = 0x09;
|
||||
|
||||
static const uint8_t AS7341_CONFIG = 0x70;
|
||||
static const uint8_t AS7341_LED = 0x74;
|
||||
|
||||
@@ -48,6 +48,12 @@ def set_stream_limits(
|
||||
min_sample_rate: int = _UNDEF,
|
||||
max_sample_rate: int = _UNDEF,
|
||||
):
|
||||
"""Sets the limits for the audio stream that audio component can handle
|
||||
|
||||
When the component sinks audio (e.g., a speaker), these indicate the limits to the audio it can receive.
|
||||
When the component sources audio (e.g., a microphone), these indicate the limits to the audio it can send.
|
||||
"""
|
||||
|
||||
def set_limits_in_config(config):
|
||||
if min_bits_per_sample is not _UNDEF:
|
||||
config[CONF_MIN_BITS_PER_SAMPLE] = min_bits_per_sample
|
||||
@@ -69,43 +75,87 @@ def final_validate_audio_schema(
|
||||
name: str,
|
||||
*,
|
||||
audio_device: str,
|
||||
bits_per_sample: int,
|
||||
channels: int,
|
||||
sample_rate: int,
|
||||
bits_per_sample: int = _UNDEF,
|
||||
channels: int = _UNDEF,
|
||||
sample_rate: int = _UNDEF,
|
||||
enabled_channels: list[int] = _UNDEF,
|
||||
audio_device_issue: bool = False,
|
||||
):
|
||||
"""Validates audio compatibility when passed between different components.
|
||||
|
||||
The component derived from ``AUDIO_COMPONENT_SCHEMA`` should call ``set_stream_limits`` in a validator to specify its compatible settings
|
||||
|
||||
- If audio_device_issue is True, then the error message indicates the user should adjust the AUDIO_COMPONENT_SCHEMA derived component's configuration to match the values passed to this function
|
||||
- If audio_device_issue is False, then the error message indicates the user should adjust the configuration of the component calling this function, as it falls out of the valid stream limits
|
||||
|
||||
Args:
|
||||
name (str): Friendly name of the component calling this function with an audio component to validate
|
||||
audio_device (str): The configuration parameter name that contains the ID of an AUDIO_COMPONENT_SCHEMA derived component to validate against
|
||||
bits_per_sample (int, optional): The desired bits per sample
|
||||
channels (int, optional): The desired number of channels
|
||||
sample_rate (int, optional): The desired sample rate
|
||||
enabled_channels (list[int], optional): The desired enabled channels
|
||||
audio_device_issue (bool, optional): Format the error message to indicate the problem is in the configuration for the ``audio_device`` component. Defaults to False.
|
||||
"""
|
||||
|
||||
def validate_audio_compatiblity(audio_config):
|
||||
audio_schema = {}
|
||||
|
||||
try:
|
||||
cv.int_range(
|
||||
min=audio_config.get(CONF_MIN_BITS_PER_SAMPLE),
|
||||
max=audio_config.get(CONF_MAX_BITS_PER_SAMPLE),
|
||||
)(bits_per_sample)
|
||||
except cv.Invalid as exc:
|
||||
raise cv.Invalid(
|
||||
f"Invalid configuration for the {name} component. The {CONF_BITS_PER_SAMPLE} {str(exc)}"
|
||||
) from exc
|
||||
if bits_per_sample is not _UNDEF:
|
||||
try:
|
||||
cv.int_range(
|
||||
min=audio_config.get(CONF_MIN_BITS_PER_SAMPLE),
|
||||
max=audio_config.get(CONF_MAX_BITS_PER_SAMPLE),
|
||||
)(bits_per_sample)
|
||||
except cv.Invalid as exc:
|
||||
if audio_device_issue:
|
||||
error_string = f"Invalid configuration for the specified {audio_device}. The {name} component requires {bits_per_sample} bits per sample."
|
||||
else:
|
||||
error_string = f"Invalid configuration for the {name} component. The {CONF_BITS_PER_SAMPLE} {str(exc)}"
|
||||
raise cv.Invalid(error_string) from exc
|
||||
|
||||
try:
|
||||
cv.int_range(
|
||||
min=audio_config.get(CONF_MIN_CHANNELS),
|
||||
max=audio_config.get(CONF_MAX_CHANNELS),
|
||||
)(channels)
|
||||
except cv.Invalid as exc:
|
||||
raise cv.Invalid(
|
||||
f"Invalid configuration for the {name} component. The {CONF_NUM_CHANNELS} {str(exc)}"
|
||||
) from exc
|
||||
if channels is not _UNDEF:
|
||||
try:
|
||||
cv.int_range(
|
||||
min=audio_config.get(CONF_MIN_CHANNELS),
|
||||
max=audio_config.get(CONF_MAX_CHANNELS),
|
||||
)(channels)
|
||||
except cv.Invalid as exc:
|
||||
if audio_device_issue:
|
||||
error_string = f"Invalid configuration for the specified {audio_device}. The {name} component requires {channels} channels."
|
||||
else:
|
||||
error_string = f"Invalid configuration for the {name} component. The {CONF_NUM_CHANNELS} {str(exc)}"
|
||||
raise cv.Invalid(error_string) from exc
|
||||
|
||||
try:
|
||||
cv.int_range(
|
||||
min=audio_config.get(CONF_MIN_SAMPLE_RATE),
|
||||
max=audio_config.get(CONF_MAX_SAMPLE_RATE),
|
||||
)(sample_rate)
|
||||
return cv.Schema(audio_schema, extra=cv.ALLOW_EXTRA)(audio_config)
|
||||
except cv.Invalid as exc:
|
||||
raise cv.Invalid(
|
||||
f"Invalid configuration for the {name} component. The {CONF_SAMPLE_RATE} {str(exc)}"
|
||||
) from exc
|
||||
if sample_rate is not _UNDEF:
|
||||
try:
|
||||
cv.int_range(
|
||||
min=audio_config.get(CONF_MIN_SAMPLE_RATE),
|
||||
max=audio_config.get(CONF_MAX_SAMPLE_RATE),
|
||||
)(sample_rate)
|
||||
except cv.Invalid as exc:
|
||||
if audio_device_issue:
|
||||
error_string = f"Invalid configuration for the specified {audio_device}. The {name} component requires a {sample_rate} sample rate."
|
||||
else:
|
||||
error_string = f"Invalid configuration for the {name} component. The {CONF_SAMPLE_RATE} {str(exc)}"
|
||||
raise cv.Invalid(error_string) from exc
|
||||
|
||||
if enabled_channels is not _UNDEF:
|
||||
for channel in enabled_channels:
|
||||
try:
|
||||
# Channels are 0-indexed
|
||||
cv.int_range(
|
||||
min=0,
|
||||
max=audio_config.get(CONF_MAX_CHANNELS) - 1,
|
||||
)(channel)
|
||||
except cv.Invalid as exc:
|
||||
if audio_device_issue:
|
||||
error_string = f"Invalid configuration for the specified {audio_device}. The {name} component requires channel {channel}."
|
||||
else:
|
||||
error_string = f"Invalid configuration for the {name} component. Enabled channel {channel} {str(exc)}"
|
||||
raise cv.Invalid(error_string) from exc
|
||||
|
||||
return cv.Schema(audio_schema, extra=cv.ALLOW_EXTRA)(audio_config)
|
||||
|
||||
return cv.Schema(
|
||||
{
|
||||
|
||||
@@ -135,5 +135,30 @@ const char *audio_file_type_to_string(AudioFileType file_type);
|
||||
void scale_audio_samples(const int16_t *audio_samples, int16_t *output_buffer, int16_t scale_factor,
|
||||
size_t samples_to_scale);
|
||||
|
||||
/// @brief Unpacks a quantized audio sample into a Q31 fixed point number.
|
||||
/// @param data Pointer to uint8_t array containing the audio sample
|
||||
/// @param bytes_per_sample The number of bytes per sample
|
||||
/// @return Q31 sample
|
||||
inline int32_t unpack_audio_sample_to_q31(const uint8_t *data, size_t bytes_per_sample) {
|
||||
int32_t sample = 0;
|
||||
if (bytes_per_sample == 1) {
|
||||
sample |= data[0] << 24;
|
||||
} else if (bytes_per_sample == 2) {
|
||||
sample |= data[0] << 16;
|
||||
sample |= data[1] << 24;
|
||||
} else if (bytes_per_sample == 3) {
|
||||
sample |= data[0] << 8;
|
||||
sample |= data[1] << 16;
|
||||
sample |= data[2] << 24;
|
||||
} else if (bytes_per_sample == 4) {
|
||||
sample |= data[0];
|
||||
sample |= data[1] << 8;
|
||||
sample |= data[2] << 16;
|
||||
sample |= data[3] << 24;
|
||||
}
|
||||
|
||||
return sample;
|
||||
}
|
||||
|
||||
} // namespace audio
|
||||
} // namespace esphome
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace esphome {
|
||||
namespace audio {
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "audio_transfer_buffer.h"
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/ring_buffer.h"
|
||||
|
||||
#ifdef USE_SPEAKER
|
||||
|
||||
@@ -15,21 +15,17 @@ void BinarySensor::publish_state(bool state) {
|
||||
if (!this->publish_dedup_.next(state))
|
||||
return;
|
||||
if (this->filter_list_ == nullptr) {
|
||||
this->send_state_internal(state, false);
|
||||
this->send_state_internal(state);
|
||||
} else {
|
||||
this->filter_list_->input(state, false);
|
||||
this->filter_list_->input(state);
|
||||
}
|
||||
}
|
||||
void BinarySensor::publish_initial_state(bool state) {
|
||||
if (!this->publish_dedup_.next(state))
|
||||
return;
|
||||
if (this->filter_list_ == nullptr) {
|
||||
this->send_state_internal(state, true);
|
||||
} else {
|
||||
this->filter_list_->input(state, true);
|
||||
}
|
||||
this->has_state_ = false;
|
||||
this->publish_state(state);
|
||||
}
|
||||
void BinarySensor::send_state_internal(bool state, bool is_initial) {
|
||||
void BinarySensor::send_state_internal(bool state) {
|
||||
bool is_initial = !this->has_state_;
|
||||
if (is_initial) {
|
||||
ESP_LOGD(TAG, "'%s': Sending initial state %s", this->get_name().c_str(), ONOFF(state));
|
||||
} else {
|
||||
|
||||
@@ -67,7 +67,7 @@ class BinarySensor : public EntityBase, public EntityBase_DeviceClass {
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
void send_state_internal(bool state, bool is_initial);
|
||||
void send_state_internal(bool state);
|
||||
|
||||
/// Return whether this binary sensor has outputted a state.
|
||||
virtual bool has_state() const;
|
||||
|
||||
@@ -9,37 +9,37 @@ namespace binary_sensor {
|
||||
|
||||
static const char *const TAG = "sensor.filter";
|
||||
|
||||
void Filter::output(bool value, bool is_initial) {
|
||||
void Filter::output(bool value) {
|
||||
if (!this->dedup_.next(value))
|
||||
return;
|
||||
|
||||
if (this->next_ == nullptr) {
|
||||
this->parent_->send_state_internal(value, is_initial);
|
||||
this->parent_->send_state_internal(value);
|
||||
} else {
|
||||
this->next_->input(value, is_initial);
|
||||
this->next_->input(value);
|
||||
}
|
||||
}
|
||||
void Filter::input(bool value, bool is_initial) {
|
||||
auto b = this->new_value(value, is_initial);
|
||||
void Filter::input(bool value) {
|
||||
auto b = this->new_value(value);
|
||||
if (b.has_value()) {
|
||||
this->output(*b, is_initial);
|
||||
this->output(*b);
|
||||
}
|
||||
}
|
||||
|
||||
optional<bool> DelayedOnOffFilter::new_value(bool value, bool is_initial) {
|
||||
optional<bool> DelayedOnOffFilter::new_value(bool value) {
|
||||
if (value) {
|
||||
this->set_timeout("ON_OFF", this->on_delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
|
||||
this->set_timeout("ON_OFF", this->on_delay_.value(), [this]() { this->output(true); });
|
||||
} else {
|
||||
this->set_timeout("ON_OFF", this->off_delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
|
||||
this->set_timeout("ON_OFF", this->off_delay_.value(), [this]() { this->output(false); });
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
|
||||
optional<bool> DelayedOnFilter::new_value(bool value) {
|
||||
if (value) {
|
||||
this->set_timeout("ON", this->delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
|
||||
this->set_timeout("ON", this->delay_.value(), [this]() { this->output(true); });
|
||||
return {};
|
||||
} else {
|
||||
this->cancel_timeout("ON");
|
||||
@@ -49,9 +49,9 @@ optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
|
||||
|
||||
float DelayedOnFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) {
|
||||
optional<bool> DelayedOffFilter::new_value(bool value) {
|
||||
if (!value) {
|
||||
this->set_timeout("OFF", this->delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
|
||||
this->set_timeout("OFF", this->delay_.value(), [this]() { this->output(false); });
|
||||
return {};
|
||||
} else {
|
||||
this->cancel_timeout("OFF");
|
||||
@@ -61,11 +61,11 @@ optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) {
|
||||
|
||||
float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
optional<bool> InvertFilter::new_value(bool value, bool is_initial) { return !value; }
|
||||
optional<bool> InvertFilter::new_value(bool value) { return !value; }
|
||||
|
||||
AutorepeatFilter::AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings) : timings_(std::move(timings)) {}
|
||||
|
||||
optional<bool> AutorepeatFilter::new_value(bool value, bool is_initial) {
|
||||
optional<bool> AutorepeatFilter::new_value(bool value) {
|
||||
if (value) {
|
||||
// Ignore if already running
|
||||
if (this->active_timing_ != 0)
|
||||
@@ -101,7 +101,7 @@ void AutorepeatFilter::next_timing_() {
|
||||
|
||||
void AutorepeatFilter::next_value_(bool val) {
|
||||
const AutorepeatFilterTiming &timing = this->timings_[this->active_timing_ - 2];
|
||||
this->output(val, false); // This is at least the second one so not initial
|
||||
this->output(val);
|
||||
this->set_timeout("ON_OFF", val ? timing.time_on : timing.time_off, [this, val]() { this->next_value_(!val); });
|
||||
}
|
||||
|
||||
@@ -109,18 +109,18 @@ float AutorepeatFilter::get_setup_priority() const { return setup_priority::HARD
|
||||
|
||||
LambdaFilter::LambdaFilter(std::function<optional<bool>(bool)> f) : f_(std::move(f)) {}
|
||||
|
||||
optional<bool> LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); }
|
||||
optional<bool> LambdaFilter::new_value(bool value) { return this->f_(value); }
|
||||
|
||||
optional<bool> SettleFilter::new_value(bool value, bool is_initial) {
|
||||
optional<bool> SettleFilter::new_value(bool value) {
|
||||
if (!this->steady_) {
|
||||
this->set_timeout("SETTLE", this->delay_.value(), [this, value, is_initial]() {
|
||||
this->set_timeout("SETTLE", this->delay_.value(), [this, value]() {
|
||||
this->steady_ = true;
|
||||
this->output(value, is_initial);
|
||||
this->output(value);
|
||||
});
|
||||
return {};
|
||||
} else {
|
||||
this->steady_ = false;
|
||||
this->output(value, is_initial);
|
||||
this->output(value);
|
||||
this->set_timeout("SETTLE", this->delay_.value(), [this]() { this->steady_ = true; });
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -14,11 +14,11 @@ class BinarySensor;
|
||||
|
||||
class Filter {
|
||||
public:
|
||||
virtual optional<bool> new_value(bool value, bool is_initial) = 0;
|
||||
virtual optional<bool> new_value(bool value) = 0;
|
||||
|
||||
void input(bool value, bool is_initial);
|
||||
void input(bool value);
|
||||
|
||||
void output(bool value, bool is_initial);
|
||||
void output(bool value);
|
||||
|
||||
protected:
|
||||
friend BinarySensor;
|
||||
@@ -30,7 +30,7 @@ class Filter {
|
||||
|
||||
class DelayedOnOffFilter : public Filter, public Component {
|
||||
public:
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
@@ -44,7 +44,7 @@ class DelayedOnOffFilter : public Filter, public Component {
|
||||
|
||||
class DelayedOnFilter : public Filter, public Component {
|
||||
public:
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
@@ -56,7 +56,7 @@ class DelayedOnFilter : public Filter, public Component {
|
||||
|
||||
class DelayedOffFilter : public Filter, public Component {
|
||||
public:
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
@@ -68,7 +68,7 @@ class DelayedOffFilter : public Filter, public Component {
|
||||
|
||||
class InvertFilter : public Filter {
|
||||
public:
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
};
|
||||
|
||||
struct AutorepeatFilterTiming {
|
||||
@@ -86,7 +86,7 @@ class AutorepeatFilter : public Filter, public Component {
|
||||
public:
|
||||
explicit AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings);
|
||||
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
@@ -102,7 +102,7 @@ class LambdaFilter : public Filter {
|
||||
public:
|
||||
explicit LambdaFilter(std::function<optional<bool>(bool)> f);
|
||||
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
protected:
|
||||
std::function<optional<bool>(bool)> f_;
|
||||
@@ -110,7 +110,7 @@ class LambdaFilter : public Filter {
|
||||
|
||||
class SettleFilter : public Filter, public Component {
|
||||
public:
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ static const uint8_t BL0906_WRITE_COMMAND = 0xCA;
|
||||
static const uint8_t BL0906_V_RMS = 0x16;
|
||||
|
||||
// Total power
|
||||
static const uint8_t BL0906_WATT_SUM = 0X2C;
|
||||
static const uint8_t BL0906_WATT_SUM = 0x2C;
|
||||
|
||||
// Current1~6
|
||||
static const uint8_t BL0906_I_1_RMS = 0x0D; // current_1
|
||||
@@ -56,29 +56,29 @@ static const uint8_t BL0906_I_5_RMS = 0x13;
|
||||
static const uint8_t BL0906_I_6_RMS = 0x14; // current_6
|
||||
|
||||
// Power1~6
|
||||
static const uint8_t BL0906_WATT_1 = 0X23; // power_1
|
||||
static const uint8_t BL0906_WATT_2 = 0X24;
|
||||
static const uint8_t BL0906_WATT_3 = 0X25;
|
||||
static const uint8_t BL0906_WATT_4 = 0X26;
|
||||
static const uint8_t BL0906_WATT_5 = 0X29;
|
||||
static const uint8_t BL0906_WATT_6 = 0X2A; // power_6
|
||||
static const uint8_t BL0906_WATT_1 = 0x23; // power_1
|
||||
static const uint8_t BL0906_WATT_2 = 0x24;
|
||||
static const uint8_t BL0906_WATT_3 = 0x25;
|
||||
static const uint8_t BL0906_WATT_4 = 0x26;
|
||||
static const uint8_t BL0906_WATT_5 = 0x29;
|
||||
static const uint8_t BL0906_WATT_6 = 0x2A; // power_6
|
||||
|
||||
// Active pulse count, unsigned
|
||||
static const uint8_t BL0906_CF_1_CNT = 0X30; // Channel_1
|
||||
static const uint8_t BL0906_CF_2_CNT = 0X31;
|
||||
static const uint8_t BL0906_CF_3_CNT = 0X32;
|
||||
static const uint8_t BL0906_CF_4_CNT = 0X33;
|
||||
static const uint8_t BL0906_CF_5_CNT = 0X36;
|
||||
static const uint8_t BL0906_CF_6_CNT = 0X37; // Channel_6
|
||||
static const uint8_t BL0906_CF_1_CNT = 0x30; // Channel_1
|
||||
static const uint8_t BL0906_CF_2_CNT = 0x31;
|
||||
static const uint8_t BL0906_CF_3_CNT = 0x32;
|
||||
static const uint8_t BL0906_CF_4_CNT = 0x33;
|
||||
static const uint8_t BL0906_CF_5_CNT = 0x36;
|
||||
static const uint8_t BL0906_CF_6_CNT = 0x37; // Channel_6
|
||||
|
||||
// Total active pulse count, unsigned
|
||||
static const uint8_t BL0906_CF_SUM_CNT = 0X39;
|
||||
static const uint8_t BL0906_CF_SUM_CNT = 0x39;
|
||||
|
||||
// Voltage frequency cycle
|
||||
static const uint8_t BL0906_FREQUENCY = 0X4E;
|
||||
static const uint8_t BL0906_FREQUENCY = 0x4E;
|
||||
|
||||
// Internal temperature
|
||||
static const uint8_t BL0906_TEMPERATURE = 0X5E;
|
||||
static const uint8_t BL0906_TEMPERATURE = 0x5E;
|
||||
|
||||
// Calibration register
|
||||
// RMS gain adjustment register
|
||||
|
||||
@@ -25,6 +25,22 @@ std::vector<uint64_t> get_128bit_uuid_vec(esp_bt_uuid_t uuid_source) {
|
||||
|
||||
BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; }
|
||||
|
||||
void BluetoothProxy::setup() {
|
||||
this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
|
||||
if (this->api_connection_ != nullptr) {
|
||||
this->send_bluetooth_scanner_state_(state);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state) {
|
||||
api::BluetoothScannerStateResponse resp;
|
||||
resp.state = static_cast<api::enums::BluetoothScannerState>(state);
|
||||
resp.mode = this->parent_->get_scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
|
||||
: api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
|
||||
this->api_connection_->send_bluetooth_scanner_state_response(resp);
|
||||
}
|
||||
|
||||
bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || this->raw_advertisements_)
|
||||
return false;
|
||||
@@ -453,6 +469,8 @@ void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection
|
||||
this->api_connection_ = api_connection;
|
||||
this->raw_advertisements_ = flags & BluetoothProxySubscriptionFlag::SUBSCRIPTION_RAW_ADVERTISEMENTS;
|
||||
this->parent_->recalculate_advertisement_parser_types();
|
||||
|
||||
this->send_bluetooth_scanner_state_(this->parent_->get_scanner_state());
|
||||
}
|
||||
|
||||
void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connection) {
|
||||
@@ -525,6 +543,17 @@ void BluetoothProxy::send_device_unpairing(uint64_t address, bool success, esp_e
|
||||
this->api_connection_->send_bluetooth_device_unpairing_response(call);
|
||||
}
|
||||
|
||||
void BluetoothProxy::bluetooth_scanner_set_mode(bool active) {
|
||||
if (this->parent_->get_scan_active() == active) {
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, "Setting scanner mode to %s", active ? "active" : "passive");
|
||||
this->parent_->set_scan_active(active);
|
||||
this->parent_->stop_scan();
|
||||
this->parent_->set_scan_continuous(
|
||||
true); // Set this to true to automatically start scanning again when it has cleaned up.
|
||||
}
|
||||
|
||||
BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace bluetooth_proxy
|
||||
|
||||
@@ -41,6 +41,7 @@ enum BluetoothProxyFeature : uint32_t {
|
||||
FEATURE_PAIRING = 1 << 3,
|
||||
FEATURE_CACHE_CLEARING = 1 << 4,
|
||||
FEATURE_RAW_ADVERTISEMENTS = 1 << 5,
|
||||
FEATURE_STATE_AND_MODE = 1 << 6,
|
||||
};
|
||||
|
||||
enum BluetoothProxySubscriptionFlag : uint32_t {
|
||||
@@ -53,6 +54,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
|
||||
bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) override;
|
||||
void dump_config() override;
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
|
||||
|
||||
@@ -84,6 +86,8 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
||||
void send_device_unpairing(uint64_t address, bool success, esp_err_t error = ESP_OK);
|
||||
void send_device_clear_cache(uint64_t address, bool success, esp_err_t error = ESP_OK);
|
||||
|
||||
void bluetooth_scanner_set_mode(bool active);
|
||||
|
||||
static void uint64_to_bd_addr(uint64_t address, esp_bd_addr_t bd_addr) {
|
||||
bd_addr[0] = (address >> 40) & 0xff;
|
||||
bd_addr[1] = (address >> 32) & 0xff;
|
||||
@@ -107,6 +111,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
||||
uint32_t flags = 0;
|
||||
flags |= BluetoothProxyFeature::FEATURE_PASSIVE_SCAN;
|
||||
flags |= BluetoothProxyFeature::FEATURE_RAW_ADVERTISEMENTS;
|
||||
flags |= BluetoothProxyFeature::FEATURE_STATE_AND_MODE;
|
||||
if (this->active_) {
|
||||
flags |= BluetoothProxyFeature::FEATURE_ACTIVE_CONNECTIONS;
|
||||
flags |= BluetoothProxyFeature::FEATURE_REMOTE_CACHING;
|
||||
@@ -124,6 +129,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
||||
|
||||
protected:
|
||||
void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device);
|
||||
void send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state);
|
||||
|
||||
BluetoothConnection *get_connection_(uint64_t address, bool reserve);
|
||||
|
||||
|
||||
@@ -86,6 +86,9 @@ void Canbus::loop() {
|
||||
data.push_back(can_message.data[i]);
|
||||
}
|
||||
|
||||
this->callback_manager_(can_message.can_id, can_message.use_extended_id, can_message.remote_transmission_request,
|
||||
data);
|
||||
|
||||
// fire all triggers
|
||||
for (auto *trigger : this->triggers_) {
|
||||
if ((trigger->can_id_ == (can_message.can_id & trigger->can_id_mask_)) &&
|
||||
|
||||
@@ -81,6 +81,20 @@ class Canbus : public Component {
|
||||
void set_bitrate(CanSpeed bit_rate) { this->bit_rate_ = bit_rate; }
|
||||
|
||||
void add_trigger(CanbusTrigger *trigger);
|
||||
/**
|
||||
* Add a callback to be called when a CAN message is received. All received messages
|
||||
* are passed to the callback without filtering.
|
||||
*
|
||||
* The callback function receives:
|
||||
* - can_id of the received data
|
||||
* - extended_id True if the can_id is an extended id
|
||||
* - rtr If this is a remote transmission request
|
||||
* - data The message data
|
||||
*/
|
||||
void add_callback(
|
||||
std::function<void(uint32_t can_id, bool extended_id, bool rtr, const std::vector<uint8_t> &data)> callback) {
|
||||
this->callback_manager_.add(std::move(callback));
|
||||
}
|
||||
|
||||
protected:
|
||||
template<typename... Ts> friend class CanbusSendAction;
|
||||
@@ -88,6 +102,8 @@ class Canbus : public Component {
|
||||
uint32_t can_id_;
|
||||
bool use_extended_id_;
|
||||
CanSpeed bit_rate_;
|
||||
CallbackManager<void(uint32_t can_id, bool extended_id, bool rtr, const std::vector<uint8_t> &data)>
|
||||
callback_manager_{};
|
||||
|
||||
virtual bool setup_internal();
|
||||
virtual Error send_message(struct CanFrame *frame);
|
||||
|
||||
@@ -20,7 +20,7 @@ enum ClimateMode : uint8_t {
|
||||
CLIMATE_MODE_FAN_ONLY = 4,
|
||||
/// The climate device is set to dry/humidity mode
|
||||
CLIMATE_MODE_DRY = 5,
|
||||
/** The climate device is adjusting the temperatre dynamically.
|
||||
/** The climate device is adjusting the temperature dynamically.
|
||||
* For example, the target temperature can be adjusted based on a schedule, or learned behavior.
|
||||
* The target temperature can't be adjusted when in this mode.
|
||||
*/
|
||||
|
||||
@@ -40,24 +40,24 @@ namespace climate {
|
||||
*/
|
||||
class ClimateTraits {
|
||||
public:
|
||||
bool get_supports_current_temperature() const { return supports_current_temperature_; }
|
||||
bool get_supports_current_temperature() const { return this->supports_current_temperature_; }
|
||||
void set_supports_current_temperature(bool supports_current_temperature) {
|
||||
supports_current_temperature_ = supports_current_temperature;
|
||||
this->supports_current_temperature_ = supports_current_temperature;
|
||||
}
|
||||
bool get_supports_current_humidity() const { return supports_current_humidity_; }
|
||||
bool get_supports_current_humidity() const { return this->supports_current_humidity_; }
|
||||
void set_supports_current_humidity(bool supports_current_humidity) {
|
||||
supports_current_humidity_ = supports_current_humidity;
|
||||
this->supports_current_humidity_ = supports_current_humidity;
|
||||
}
|
||||
bool get_supports_two_point_target_temperature() const { return supports_two_point_target_temperature_; }
|
||||
bool get_supports_two_point_target_temperature() const { return this->supports_two_point_target_temperature_; }
|
||||
void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature) {
|
||||
supports_two_point_target_temperature_ = supports_two_point_target_temperature;
|
||||
this->supports_two_point_target_temperature_ = supports_two_point_target_temperature;
|
||||
}
|
||||
bool get_supports_target_humidity() const { return supports_target_humidity_; }
|
||||
bool get_supports_target_humidity() const { return this->supports_target_humidity_; }
|
||||
void set_supports_target_humidity(bool supports_target_humidity) {
|
||||
supports_target_humidity_ = supports_target_humidity;
|
||||
this->supports_target_humidity_ = supports_target_humidity;
|
||||
}
|
||||
void set_supported_modes(std::set<ClimateMode> modes) { supported_modes_ = std::move(modes); }
|
||||
void add_supported_mode(ClimateMode mode) { supported_modes_.insert(mode); }
|
||||
void set_supported_modes(std::set<ClimateMode> modes) { this->supported_modes_ = std::move(modes); }
|
||||
void add_supported_mode(ClimateMode mode) { this->supported_modes_.insert(mode); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
|
||||
void set_supports_auto_mode(bool supports_auto_mode) { set_mode_support_(CLIMATE_MODE_AUTO, supports_auto_mode); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
|
||||
@@ -72,15 +72,15 @@ class ClimateTraits {
|
||||
}
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
|
||||
void set_supports_dry_mode(bool supports_dry_mode) { set_mode_support_(CLIMATE_MODE_DRY, supports_dry_mode); }
|
||||
bool supports_mode(ClimateMode mode) const { return supported_modes_.count(mode); }
|
||||
const std::set<ClimateMode> &get_supported_modes() const { return supported_modes_; }
|
||||
bool supports_mode(ClimateMode mode) const { return this->supported_modes_.count(mode); }
|
||||
const std::set<ClimateMode> &get_supported_modes() const { return this->supported_modes_; }
|
||||
|
||||
void set_supports_action(bool supports_action) { supports_action_ = supports_action; }
|
||||
bool get_supports_action() const { return supports_action_; }
|
||||
void set_supports_action(bool supports_action) { this->supports_action_ = supports_action; }
|
||||
bool get_supports_action() const { return this->supports_action_; }
|
||||
|
||||
void set_supported_fan_modes(std::set<ClimateFanMode> modes) { supported_fan_modes_ = std::move(modes); }
|
||||
void add_supported_fan_mode(ClimateFanMode mode) { supported_fan_modes_.insert(mode); }
|
||||
void add_supported_custom_fan_mode(const std::string &mode) { supported_custom_fan_modes_.insert(mode); }
|
||||
void set_supported_fan_modes(std::set<ClimateFanMode> modes) { this->supported_fan_modes_ = std::move(modes); }
|
||||
void add_supported_fan_mode(ClimateFanMode mode) { this->supported_fan_modes_.insert(mode); }
|
||||
void add_supported_custom_fan_mode(const std::string &mode) { this->supported_custom_fan_modes_.insert(mode); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
|
||||
void set_supports_fan_mode_on(bool supported) { set_fan_mode_support_(CLIMATE_FAN_ON, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
|
||||
@@ -99,35 +99,37 @@ class ClimateTraits {
|
||||
void set_supports_fan_mode_focus(bool supported) { set_fan_mode_support_(CLIMATE_FAN_FOCUS, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead", "v1.20")
|
||||
void set_supports_fan_mode_diffuse(bool supported) { set_fan_mode_support_(CLIMATE_FAN_DIFFUSE, supported); }
|
||||
bool supports_fan_mode(ClimateFanMode fan_mode) const { return supported_fan_modes_.count(fan_mode); }
|
||||
bool get_supports_fan_modes() const { return !supported_fan_modes_.empty() || !supported_custom_fan_modes_.empty(); }
|
||||
const std::set<ClimateFanMode> &get_supported_fan_modes() const { return supported_fan_modes_; }
|
||||
bool supports_fan_mode(ClimateFanMode fan_mode) const { return this->supported_fan_modes_.count(fan_mode); }
|
||||
bool get_supports_fan_modes() const {
|
||||
return !this->supported_fan_modes_.empty() || !this->supported_custom_fan_modes_.empty();
|
||||
}
|
||||
const std::set<ClimateFanMode> &get_supported_fan_modes() const { return this->supported_fan_modes_; }
|
||||
|
||||
void set_supported_custom_fan_modes(std::set<std::string> supported_custom_fan_modes) {
|
||||
supported_custom_fan_modes_ = std::move(supported_custom_fan_modes);
|
||||
this->supported_custom_fan_modes_ = std::move(supported_custom_fan_modes);
|
||||
}
|
||||
const std::set<std::string> &get_supported_custom_fan_modes() const { return supported_custom_fan_modes_; }
|
||||
const std::set<std::string> &get_supported_custom_fan_modes() const { return this->supported_custom_fan_modes_; }
|
||||
bool supports_custom_fan_mode(const std::string &custom_fan_mode) const {
|
||||
return supported_custom_fan_modes_.count(custom_fan_mode);
|
||||
return this->supported_custom_fan_modes_.count(custom_fan_mode);
|
||||
}
|
||||
|
||||
void set_supported_presets(std::set<ClimatePreset> presets) { supported_presets_ = std::move(presets); }
|
||||
void add_supported_preset(ClimatePreset preset) { supported_presets_.insert(preset); }
|
||||
void add_supported_custom_preset(const std::string &preset) { supported_custom_presets_.insert(preset); }
|
||||
bool supports_preset(ClimatePreset preset) const { return supported_presets_.count(preset); }
|
||||
bool get_supports_presets() const { return !supported_presets_.empty(); }
|
||||
const std::set<climate::ClimatePreset> &get_supported_presets() const { return supported_presets_; }
|
||||
void set_supported_presets(std::set<ClimatePreset> presets) { this->supported_presets_ = std::move(presets); }
|
||||
void add_supported_preset(ClimatePreset preset) { this->supported_presets_.insert(preset); }
|
||||
void add_supported_custom_preset(const std::string &preset) { this->supported_custom_presets_.insert(preset); }
|
||||
bool supports_preset(ClimatePreset preset) const { return this->supported_presets_.count(preset); }
|
||||
bool get_supports_presets() const { return !this->supported_presets_.empty(); }
|
||||
const std::set<climate::ClimatePreset> &get_supported_presets() const { return this->supported_presets_; }
|
||||
|
||||
void set_supported_custom_presets(std::set<std::string> supported_custom_presets) {
|
||||
supported_custom_presets_ = std::move(supported_custom_presets);
|
||||
this->supported_custom_presets_ = std::move(supported_custom_presets);
|
||||
}
|
||||
const std::set<std::string> &get_supported_custom_presets() const { return supported_custom_presets_; }
|
||||
const std::set<std::string> &get_supported_custom_presets() const { return this->supported_custom_presets_; }
|
||||
bool supports_custom_preset(const std::string &custom_preset) const {
|
||||
return supported_custom_presets_.count(custom_preset);
|
||||
return this->supported_custom_presets_.count(custom_preset);
|
||||
}
|
||||
|
||||
void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { supported_swing_modes_ = std::move(modes); }
|
||||
void add_supported_swing_mode(ClimateSwingMode mode) { supported_swing_modes_.insert(mode); }
|
||||
void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { this->supported_swing_modes_ = std::move(modes); }
|
||||
void add_supported_swing_mode(ClimateSwingMode mode) { this->supported_swing_modes_.insert(mode); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead", "v1.20")
|
||||
void set_supports_swing_mode_off(bool supported) { set_swing_mode_support_(CLIMATE_SWING_OFF, supported); }
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_swing_modes() instead", "v1.20")
|
||||
@@ -138,54 +140,58 @@ class ClimateTraits {
|
||||
void set_supports_swing_mode_horizontal(bool supported) {
|
||||
set_swing_mode_support_(CLIMATE_SWING_HORIZONTAL, supported);
|
||||
}
|
||||
bool supports_swing_mode(ClimateSwingMode swing_mode) const { return supported_swing_modes_.count(swing_mode); }
|
||||
bool get_supports_swing_modes() const { return !supported_swing_modes_.empty(); }
|
||||
const std::set<ClimateSwingMode> &get_supported_swing_modes() const { return supported_swing_modes_; }
|
||||
bool supports_swing_mode(ClimateSwingMode swing_mode) const { return this->supported_swing_modes_.count(swing_mode); }
|
||||
bool get_supports_swing_modes() const { return !this->supported_swing_modes_.empty(); }
|
||||
const std::set<ClimateSwingMode> &get_supported_swing_modes() const { return this->supported_swing_modes_; }
|
||||
|
||||
float get_visual_min_temperature() const { return visual_min_temperature_; }
|
||||
void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; }
|
||||
float get_visual_max_temperature() const { return visual_max_temperature_; }
|
||||
void set_visual_max_temperature(float visual_max_temperature) { visual_max_temperature_ = visual_max_temperature; }
|
||||
float get_visual_target_temperature_step() const { return visual_target_temperature_step_; }
|
||||
float get_visual_current_temperature_step() const { return visual_current_temperature_step_; }
|
||||
float get_visual_min_temperature() const { return this->visual_min_temperature_; }
|
||||
void set_visual_min_temperature(float visual_min_temperature) {
|
||||
this->visual_min_temperature_ = visual_min_temperature;
|
||||
}
|
||||
float get_visual_max_temperature() const { return this->visual_max_temperature_; }
|
||||
void set_visual_max_temperature(float visual_max_temperature) {
|
||||
this->visual_max_temperature_ = visual_max_temperature;
|
||||
}
|
||||
float get_visual_target_temperature_step() const { return this->visual_target_temperature_step_; }
|
||||
float get_visual_current_temperature_step() const { return this->visual_current_temperature_step_; }
|
||||
void set_visual_target_temperature_step(float temperature_step) {
|
||||
visual_target_temperature_step_ = temperature_step;
|
||||
this->visual_target_temperature_step_ = temperature_step;
|
||||
}
|
||||
void set_visual_current_temperature_step(float temperature_step) {
|
||||
visual_current_temperature_step_ = temperature_step;
|
||||
this->visual_current_temperature_step_ = temperature_step;
|
||||
}
|
||||
void set_visual_temperature_step(float temperature_step) {
|
||||
visual_target_temperature_step_ = temperature_step;
|
||||
visual_current_temperature_step_ = temperature_step;
|
||||
this->visual_target_temperature_step_ = temperature_step;
|
||||
this->visual_current_temperature_step_ = temperature_step;
|
||||
}
|
||||
int8_t get_target_temperature_accuracy_decimals() const;
|
||||
int8_t get_current_temperature_accuracy_decimals() const;
|
||||
|
||||
float get_visual_min_humidity() const { return visual_min_humidity_; }
|
||||
void set_visual_min_humidity(float visual_min_humidity) { visual_min_humidity_ = visual_min_humidity; }
|
||||
float get_visual_max_humidity() const { return visual_max_humidity_; }
|
||||
void set_visual_max_humidity(float visual_max_humidity) { visual_max_humidity_ = visual_max_humidity; }
|
||||
float get_visual_min_humidity() const { return this->visual_min_humidity_; }
|
||||
void set_visual_min_humidity(float visual_min_humidity) { this->visual_min_humidity_ = visual_min_humidity; }
|
||||
float get_visual_max_humidity() const { return this->visual_max_humidity_; }
|
||||
void set_visual_max_humidity(float visual_max_humidity) { this->visual_max_humidity_ = visual_max_humidity; }
|
||||
|
||||
protected:
|
||||
void set_mode_support_(climate::ClimateMode mode, bool supported) {
|
||||
if (supported) {
|
||||
supported_modes_.insert(mode);
|
||||
this->supported_modes_.insert(mode);
|
||||
} else {
|
||||
supported_modes_.erase(mode);
|
||||
this->supported_modes_.erase(mode);
|
||||
}
|
||||
}
|
||||
void set_fan_mode_support_(climate::ClimateFanMode mode, bool supported) {
|
||||
if (supported) {
|
||||
supported_fan_modes_.insert(mode);
|
||||
this->supported_fan_modes_.insert(mode);
|
||||
} else {
|
||||
supported_fan_modes_.erase(mode);
|
||||
this->supported_fan_modes_.erase(mode);
|
||||
}
|
||||
}
|
||||
void set_swing_mode_support_(climate::ClimateSwingMode mode, bool supported) {
|
||||
if (supported) {
|
||||
supported_swing_modes_.insert(mode);
|
||||
this->supported_swing_modes_.insert(mode);
|
||||
} else {
|
||||
supported_swing_modes_.erase(mode);
|
||||
this->supported_swing_modes_.erase(mode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ const uint32_t FAN_MAX = 0x40;
|
||||
|
||||
// Temperature
|
||||
const uint8_t TEMP_RANGE = TEMP_MAX - TEMP_MIN + 1;
|
||||
const uint32_t TEMP_MASK = 0XF00;
|
||||
const uint32_t TEMP_MASK = 0xF00;
|
||||
const uint32_t TEMP_SHIFT = 8;
|
||||
|
||||
const uint16_t BITS = 28;
|
||||
@@ -43,11 +43,11 @@ void LgIrClimate::transmit_state() {
|
||||
// ESP_LOGD(TAG, "climate_lg_ir mode_before_ code: 0x%02X", modeBefore_);
|
||||
|
||||
// Set command
|
||||
if (send_swing_cmd_) {
|
||||
send_swing_cmd_ = false;
|
||||
if (this->send_swing_cmd_) {
|
||||
this->send_swing_cmd_ = false;
|
||||
remote_state |= COMMAND_SWING;
|
||||
} else {
|
||||
bool climate_is_off = (mode_before_ == climate::CLIMATE_MODE_OFF);
|
||||
bool climate_is_off = (this->mode_before_ == climate::CLIMATE_MODE_OFF);
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
remote_state |= climate_is_off ? COMMAND_ON_COOL : COMMAND_COOL;
|
||||
@@ -71,7 +71,7 @@ void LgIrClimate::transmit_state() {
|
||||
}
|
||||
}
|
||||
|
||||
mode_before_ = this->mode;
|
||||
this->mode_before_ = this->mode;
|
||||
|
||||
ESP_LOGD(TAG, "climate_lg_ir mode code: 0x%02X", this->mode);
|
||||
|
||||
@@ -102,7 +102,7 @@ void LgIrClimate::transmit_state() {
|
||||
remote_state |= ((temp - 15) << TEMP_SHIFT);
|
||||
}
|
||||
|
||||
transmit_(remote_state);
|
||||
this->transmit_(remote_state);
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
}
|
||||
|
||||
void LgIrClimate::transmit_(uint32_t value) {
|
||||
calc_checksum_(value);
|
||||
this->calc_checksum_(value);
|
||||
ESP_LOGD(TAG, "Sending climate_lg_ir code: 0x%02" PRIX32, value);
|
||||
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
|
||||
@@ -21,7 +21,7 @@ class LgIrClimate : public climate_ir::ClimateIR {
|
||||
|
||||
/// Override control to change settings of the climate device.
|
||||
void control(const climate::ClimateCall &call) override {
|
||||
send_swing_cmd_ = call.get_swing_mode().has_value();
|
||||
this->send_swing_cmd_ = call.get_swing_mode().has_value();
|
||||
// swing resets after unit powered off
|
||||
if (call.get_mode().has_value() && *call.get_mode() == climate::CLIMATE_MODE_OFF)
|
||||
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||
|
||||
@@ -3,6 +3,8 @@ from esphome.const import CONF_BLUE, CONF_GREEN, CONF_ID, CONF_RED, CONF_WHITE
|
||||
|
||||
ColorStruct = cg.esphome_ns.struct("Color")
|
||||
|
||||
INSTANCE_TYPE = ColorStruct
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_RED_INT = "red_int"
|
||||
|
||||
5
esphome/components/const/__init__.py
Normal file
5
esphome/components/const/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Constants used by esphome components."""
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
|
||||
CONF_DRAW_ROUNDING = "draw_rounding"
|
||||
28
esphome/components/cst226/binary_sensor/__init__.py
Normal file
28
esphome/components/cst226/binary_sensor/__init__.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from .. import cst226_ns
|
||||
from ..touchscreen import CST226ButtonListener, CST226Touchscreen
|
||||
|
||||
CONF_CST226_ID = "cst226_id"
|
||||
|
||||
CST226Button = cst226_ns.class_(
|
||||
"CST226Button",
|
||||
binary_sensor.BinarySensor,
|
||||
cg.Component,
|
||||
CST226ButtonListener,
|
||||
cg.Parented.template(CST226Touchscreen),
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(CST226Button).extend(
|
||||
{
|
||||
cv.GenerateID(CONF_CST226_ID): cv.use_id(CST226Touchscreen),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await binary_sensor.new_binary_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
await cg.register_parented(var, config[CONF_CST226_ID])
|
||||
22
esphome/components/cst226/binary_sensor/cs226_button.h
Normal file
22
esphome/components/cst226/binary_sensor/cs226_button.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "../touchscreen/cst226_touchscreen.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cst226 {
|
||||
|
||||
class CST226Button : public binary_sensor::BinarySensor,
|
||||
public Component,
|
||||
public CST226ButtonListener,
|
||||
public Parented<CST226Touchscreen> {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
void update_button(bool state) override;
|
||||
};
|
||||
|
||||
} // namespace cst226
|
||||
} // namespace esphome
|
||||
19
esphome/components/cst226/binary_sensor/cstt6_button.cpp
Normal file
19
esphome/components/cst226/binary_sensor/cstt6_button.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#include "cs226_button.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cst226 {
|
||||
|
||||
static const char *const TAG = "CST226.binary_sensor";
|
||||
|
||||
void CST226Button::setup() {
|
||||
this->parent_->register_button_listener(this);
|
||||
this->publish_initial_state(false);
|
||||
}
|
||||
|
||||
void CST226Button::dump_config() { LOG_BINARY_SENSOR("", "CST226 Button", this); }
|
||||
|
||||
void CST226Button::update_button(bool state) { this->publish_state(state); }
|
||||
|
||||
} // namespace cst226
|
||||
} // namespace esphome
|
||||
@@ -3,8 +3,10 @@
|
||||
namespace esphome {
|
||||
namespace cst226 {
|
||||
|
||||
static const char *const TAG = "cst226.touchscreen";
|
||||
|
||||
void CST226Touchscreen::setup() {
|
||||
esph_log_config(TAG, "Setting up CST226 Touchscreen...");
|
||||
ESP_LOGCONFIG(TAG, "Setting up CST226 Touchscreen...");
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
@@ -26,6 +28,11 @@ void CST226Touchscreen::update_touches() {
|
||||
return;
|
||||
}
|
||||
this->status_clear_warning();
|
||||
if (data[0] == 0x83 && data[1] == 0x17 && data[5] == 0x80) {
|
||||
this->update_button_state_(true);
|
||||
return;
|
||||
}
|
||||
this->update_button_state_(false);
|
||||
if (data[6] != 0xAB || data[0] == 0xAB || data[5] == 0x80) {
|
||||
this->skip_update_ = true;
|
||||
return;
|
||||
@@ -43,13 +50,21 @@ void CST226Touchscreen::update_touches() {
|
||||
int16_t y = (data[index + 2] << 4) | (data[index + 3] & 0x0F);
|
||||
int16_t z = data[index + 4];
|
||||
this->add_raw_touch_position_(id, x, y, z);
|
||||
esph_log_v(TAG, "Read touch %d: %d/%d", id, x, y);
|
||||
ESP_LOGV(TAG, "Read touch %d: %d/%d", id, x, y);
|
||||
index += 5;
|
||||
if (i == 0)
|
||||
index += 2;
|
||||
}
|
||||
}
|
||||
|
||||
bool CST226Touchscreen::read16_(uint16_t addr, uint8_t *data, size_t len) {
|
||||
if (this->read_register16(addr, data, len) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Read data from 0x%04X failed", addr);
|
||||
this->mark_failed();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void CST226Touchscreen::continue_setup_() {
|
||||
uint8_t buffer[8];
|
||||
if (this->interrupt_pin_ != nullptr) {
|
||||
@@ -58,7 +73,7 @@ void CST226Touchscreen::continue_setup_() {
|
||||
}
|
||||
buffer[0] = 0xD1;
|
||||
if (this->write_register16(0xD1, buffer, 1) != i2c::ERROR_OK) {
|
||||
esph_log_e(TAG, "Write byte to 0xD1 failed");
|
||||
ESP_LOGE(TAG, "Write byte to 0xD1 failed");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -66,7 +81,7 @@ void CST226Touchscreen::continue_setup_() {
|
||||
if (this->read16_(0xD204, buffer, 4)) {
|
||||
uint16_t chip_id = buffer[2] + (buffer[3] << 8);
|
||||
uint16_t project_id = buffer[0] + (buffer[1] << 8);
|
||||
esph_log_config(TAG, "Chip ID %X, project ID %x", chip_id, project_id);
|
||||
ESP_LOGCONFIG(TAG, "Chip ID %X, project ID %x", chip_id, project_id);
|
||||
}
|
||||
if (this->x_raw_max_ == 0 || this->y_raw_max_ == 0) {
|
||||
if (this->read16_(0xD1F8, buffer, 4)) {
|
||||
@@ -80,7 +95,14 @@ void CST226Touchscreen::continue_setup_() {
|
||||
}
|
||||
}
|
||||
this->setup_complete_ = true;
|
||||
esph_log_config(TAG, "CST226 Touchscreen setup complete");
|
||||
ESP_LOGCONFIG(TAG, "CST226 Touchscreen setup complete");
|
||||
}
|
||||
void CST226Touchscreen::update_button_state_(bool state) {
|
||||
if (this->button_touched_ == state)
|
||||
return;
|
||||
this->button_touched_ = state;
|
||||
for (auto *listener : this->button_listeners_)
|
||||
listener->update_button(state);
|
||||
}
|
||||
|
||||
void CST226Touchscreen::dump_config() {
|
||||
|
||||
@@ -9,10 +9,13 @@
|
||||
namespace esphome {
|
||||
namespace cst226 {
|
||||
|
||||
static const char *const TAG = "cst226.touchscreen";
|
||||
|
||||
static const uint8_t CST226_REG_STATUS = 0x00;
|
||||
|
||||
class CST226ButtonListener {
|
||||
public:
|
||||
virtual void update_button(bool state) = 0;
|
||||
};
|
||||
|
||||
class CST226Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
@@ -22,22 +25,19 @@ class CST226Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
|
||||
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
||||
void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }
|
||||
bool can_proceed() override { return this->setup_complete_ || this->is_failed(); }
|
||||
void register_button_listener(CST226ButtonListener *listener) { this->button_listeners_.push_back(listener); }
|
||||
|
||||
protected:
|
||||
bool read16_(uint16_t addr, uint8_t *data, size_t len) {
|
||||
if (this->read_register16(addr, data, len) != i2c::ERROR_OK) {
|
||||
esph_log_e(TAG, "Read data from 0x%04X failed", addr);
|
||||
this->mark_failed();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool read16_(uint16_t addr, uint8_t *data, size_t len);
|
||||
void continue_setup_();
|
||||
void update_button_state_(bool state);
|
||||
|
||||
InternalGPIOPin *interrupt_pin_{};
|
||||
GPIOPin *reset_pin_{};
|
||||
uint8_t chip_id_{};
|
||||
bool setup_complete_{};
|
||||
std::vector<CST226ButtonListener *> button_listeners_;
|
||||
bool button_touched_{};
|
||||
};
|
||||
|
||||
} // namespace cst226
|
||||
|
||||
@@ -65,7 +65,7 @@ void DaikinClimate::transmit_state() {
|
||||
transmit.perform();
|
||||
}
|
||||
|
||||
uint8_t DaikinClimate::operation_mode_() {
|
||||
uint8_t DaikinClimate::operation_mode_() const {
|
||||
uint8_t operating_mode = DAIKIN_MODE_ON;
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
@@ -92,9 +92,12 @@ uint8_t DaikinClimate::operation_mode_() {
|
||||
return operating_mode;
|
||||
}
|
||||
|
||||
uint16_t DaikinClimate::fan_speed_() {
|
||||
uint16_t DaikinClimate::fan_speed_() const {
|
||||
uint16_t fan_speed;
|
||||
switch (this->fan_mode.value()) {
|
||||
case climate::CLIMATE_FAN_QUIET:
|
||||
fan_speed = DAIKIN_FAN_SILENT << 8;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
fan_speed = DAIKIN_FAN_1 << 8;
|
||||
break;
|
||||
@@ -126,12 +129,11 @@ uint16_t DaikinClimate::fan_speed_() {
|
||||
return fan_speed;
|
||||
}
|
||||
|
||||
uint8_t DaikinClimate::temperature_() {
|
||||
uint8_t DaikinClimate::temperature_() const {
|
||||
// Force special temperatures depending on the mode
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
return 0x32;
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
return 0xc0;
|
||||
default:
|
||||
@@ -148,19 +150,25 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) {
|
||||
if (frame[DAIKIN_STATE_FRAME_SIZE - 1] != checksum)
|
||||
return false;
|
||||
uint8_t mode = frame[5];
|
||||
// Temperature is given in degrees celcius * 2
|
||||
// only update for states that use the temperature
|
||||
uint8_t temperature = frame[6];
|
||||
if (mode & DAIKIN_MODE_ON) {
|
||||
switch (mode & 0xF0) {
|
||||
case DAIKIN_MODE_COOL:
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
this->target_temperature = static_cast<float>(temperature * 0.5f);
|
||||
break;
|
||||
case DAIKIN_MODE_DRY:
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
break;
|
||||
case DAIKIN_MODE_HEAT:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
this->target_temperature = static_cast<float>(temperature * 0.5f);
|
||||
break;
|
||||
case DAIKIN_MODE_AUTO:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
this->target_temperature = static_cast<float>(temperature * 0.5f);
|
||||
break;
|
||||
case DAIKIN_MODE_FAN:
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
@@ -169,10 +177,6 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) {
|
||||
} else {
|
||||
this->mode = climate::CLIMATE_MODE_OFF;
|
||||
}
|
||||
uint8_t temperature = frame[6];
|
||||
if (!(temperature & 0xC0)) {
|
||||
this->target_temperature = temperature >> 1;
|
||||
}
|
||||
uint8_t fan_mode = frame[8];
|
||||
uint8_t swing_mode = frame[9];
|
||||
if (fan_mode & 0xF && swing_mode & 0xF) {
|
||||
@@ -187,7 +191,6 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) {
|
||||
switch (fan_mode & 0xF0) {
|
||||
case DAIKIN_FAN_1:
|
||||
case DAIKIN_FAN_2:
|
||||
case DAIKIN_FAN_SILENT:
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
break;
|
||||
case DAIKIN_FAN_3:
|
||||
@@ -200,6 +203,9 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) {
|
||||
case DAIKIN_FAN_AUTO:
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
break;
|
||||
case DAIKIN_FAN_SILENT:
|
||||
this->fan_mode = climate::CLIMATE_FAN_QUIET;
|
||||
break;
|
||||
}
|
||||
this->publish_state();
|
||||
return true;
|
||||
|
||||
@@ -44,17 +44,17 @@ class DaikinClimate : public climate_ir::ClimateIR {
|
||||
public:
|
||||
DaikinClimate()
|
||||
: climate_ir::ClimateIR(DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX, 1.0f, true, true,
|
||||
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
|
||||
climate::CLIMATE_FAN_HIGH},
|
||||
{climate::CLIMATE_FAN_QUIET, climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW,
|
||||
climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH},
|
||||
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL,
|
||||
climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {}
|
||||
|
||||
protected:
|
||||
// Transmit via IR the state of this climate controller.
|
||||
void transmit_state() override;
|
||||
uint8_t operation_mode_();
|
||||
uint16_t fan_speed_();
|
||||
uint8_t temperature_();
|
||||
uint8_t operation_mode_() const;
|
||||
uint16_t fan_speed_() const;
|
||||
uint8_t temperature_() const;
|
||||
// Handle received IR Buffer
|
||||
bool on_receive(remote_base::RemoteReceiveData data) override;
|
||||
bool parse_state_frame_(const uint8_t frame[]);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "debug_component.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
@@ -25,6 +26,7 @@ void DebugComponent::dump_config() {
|
||||
#ifdef USE_SENSOR
|
||||
LOG_SENSOR(" ", "Free space on heap", this->free_sensor_);
|
||||
LOG_SENSOR(" ", "Largest free heap block", this->block_sensor_);
|
||||
LOG_SENSOR(" ", "CPU frequency", this->cpu_frequency_sensor_);
|
||||
#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
|
||||
LOG_SENSOR(" ", "Heap fragmentation", this->fragmentation_sensor_);
|
||||
#endif // defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
|
||||
@@ -86,6 +88,9 @@ void DebugComponent::update() {
|
||||
this->loop_time_sensor_->publish_state(this->max_loop_time_);
|
||||
this->max_loop_time_ = 0;
|
||||
}
|
||||
if (this->cpu_frequency_sensor_ != nullptr) {
|
||||
this->cpu_frequency_sensor_->publish_state(arch_get_cpu_freq_hz());
|
||||
}
|
||||
|
||||
#endif // USE_SENSOR
|
||||
update_platform_();
|
||||
|
||||
@@ -34,8 +34,12 @@ class DebugComponent : public PollingComponent {
|
||||
#endif
|
||||
void set_loop_time_sensor(sensor::Sensor *loop_time_sensor) { loop_time_sensor_ = loop_time_sensor; }
|
||||
#ifdef USE_ESP32
|
||||
void on_shutdown() override;
|
||||
void set_psram_sensor(sensor::Sensor *psram_sensor) { this->psram_sensor_ = psram_sensor; }
|
||||
#endif // USE_ESP32
|
||||
void set_cpu_frequency_sensor(sensor::Sensor *cpu_frequency_sensor) {
|
||||
this->cpu_frequency_sensor_ = cpu_frequency_sensor;
|
||||
}
|
||||
#endif // USE_SENSOR
|
||||
protected:
|
||||
uint32_t free_heap_{};
|
||||
@@ -53,6 +57,7 @@ class DebugComponent : public PollingComponent {
|
||||
#ifdef USE_ESP32
|
||||
sensor::Sensor *psram_sensor_{nullptr};
|
||||
#endif // USE_ESP32
|
||||
sensor::Sensor *cpu_frequency_sensor_{nullptr};
|
||||
#endif // USE_SENSOR
|
||||
|
||||
#ifdef USE_ESP32
|
||||
@@ -75,6 +80,7 @@ class DebugComponent : public PollingComponent {
|
||||
#endif // USE_TEXT_SENSOR
|
||||
|
||||
std::string get_reset_reason_();
|
||||
std::string get_wakeup_cause_();
|
||||
uint32_t get_free_heap_();
|
||||
void get_device_info_(std::string &device_info);
|
||||
void update_platform_();
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
#include "debug_component.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include <esp_sleep.h>
|
||||
|
||||
#include <esp_heap_caps.h>
|
||||
#include <esp_system.h>
|
||||
#include <esp_chip_info.h>
|
||||
#include <esp_partition.h>
|
||||
|
||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||
#include <esp32/rom/rtc.h>
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32C3)
|
||||
#include <esp32c3/rom/rtc.h>
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
#include <esp32c6/rom/rtc.h>
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32S2)
|
||||
#include <esp32s2/rom/rtc.h>
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
#include <esp32s3/rom/rtc.h>
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
#include <esp32h2/rom/rtc.h>
|
||||
#endif
|
||||
#include <map>
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include <Esp.h>
|
||||
#endif
|
||||
@@ -29,6 +22,90 @@ namespace debug {
|
||||
|
||||
static const char *const TAG = "debug";
|
||||
|
||||
// index by values returned by esp_reset_reason
|
||||
|
||||
static const char *const RESET_REASONS[] = {
|
||||
"unknown source",
|
||||
"power-on event",
|
||||
"external pin",
|
||||
"software via esp_restart",
|
||||
"exception/panic",
|
||||
"interrupt watchdog",
|
||||
"task watchdog",
|
||||
"other watchdogs",
|
||||
"exiting deep sleep mode",
|
||||
"brownout",
|
||||
"SDIO",
|
||||
"USB peripheral",
|
||||
"JTAG",
|
||||
"efuse error",
|
||||
"power glitch detected",
|
||||
"CPU lock up",
|
||||
};
|
||||
|
||||
static const char *const REBOOT_KEY = "reboot_source";
|
||||
static const size_t REBOOT_MAX_LEN = 24;
|
||||
|
||||
// on shutdown, store the source of the reboot request
|
||||
void DebugComponent::on_shutdown() {
|
||||
auto *component = App.get_current_component();
|
||||
char buffer[REBOOT_MAX_LEN]{};
|
||||
auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name()));
|
||||
if (component != nullptr) {
|
||||
strncpy(buffer, component->get_component_source(), REBOOT_MAX_LEN - 1);
|
||||
}
|
||||
ESP_LOGD(TAG, "Storing reboot source: %s", buffer);
|
||||
pref.save(&buffer);
|
||||
global_preferences->sync();
|
||||
}
|
||||
|
||||
std::string DebugComponent::get_reset_reason_() {
|
||||
std::string reset_reason;
|
||||
unsigned reason = esp_reset_reason();
|
||||
if (reason < sizeof(RESET_REASONS) / sizeof(RESET_REASONS[0])) {
|
||||
reset_reason = RESET_REASONS[reason];
|
||||
if (reason == ESP_RST_SW) {
|
||||
auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name()));
|
||||
char buffer[REBOOT_MAX_LEN]{};
|
||||
if (pref.load(&buffer)) {
|
||||
reset_reason = "Reboot request from " + std::string(buffer);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reset_reason = "unknown source";
|
||||
}
|
||||
ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str());
|
||||
return reset_reason;
|
||||
}
|
||||
|
||||
static const char *const WAKEUP_CAUSES[] = {
|
||||
"undefined",
|
||||
"undefined",
|
||||
"external signal using RTC_IO",
|
||||
"external signal using RTC_CNTL",
|
||||
"timer",
|
||||
"touchpad",
|
||||
"ULP program",
|
||||
"GPIO",
|
||||
"UART",
|
||||
"WIFI",
|
||||
"COCPU int",
|
||||
"COCPU crash",
|
||||
"BT",
|
||||
};
|
||||
|
||||
std::string DebugComponent::get_wakeup_cause_() {
|
||||
const char *wake_reason;
|
||||
unsigned reason = esp_sleep_get_wakeup_cause();
|
||||
if (reason < sizeof(WAKEUP_CAUSES) / sizeof(WAKEUP_CAUSES[0])) {
|
||||
wake_reason = WAKEUP_CAUSES[reason];
|
||||
} else {
|
||||
wake_reason = "unknown source";
|
||||
}
|
||||
ESP_LOGD(TAG, "Wakeup Reason: %s", wake_reason);
|
||||
return wake_reason;
|
||||
}
|
||||
|
||||
void DebugComponent::log_partition_info_() {
|
||||
ESP_LOGCONFIG(TAG, "Partition table:");
|
||||
ESP_LOGCONFIG(TAG, " %-12s %-4s %-8s %-10s %-10s", "Name", "Type", "Subtype", "Address", "Size");
|
||||
@@ -42,171 +119,16 @@ void DebugComponent::log_partition_info_() {
|
||||
esp_partition_iterator_release(it);
|
||||
}
|
||||
|
||||
std::string DebugComponent::get_reset_reason_() {
|
||||
std::string reset_reason;
|
||||
switch (esp_reset_reason()) {
|
||||
case ESP_RST_POWERON:
|
||||
reset_reason = "Reset due to power-on event";
|
||||
break;
|
||||
case ESP_RST_EXT:
|
||||
reset_reason = "Reset by external pin";
|
||||
break;
|
||||
case ESP_RST_SW:
|
||||
reset_reason = "Software reset via esp_restart";
|
||||
break;
|
||||
case ESP_RST_PANIC:
|
||||
reset_reason = "Software reset due to exception/panic";
|
||||
break;
|
||||
case ESP_RST_INT_WDT:
|
||||
reset_reason = "Reset (software or hardware) due to interrupt watchdog";
|
||||
break;
|
||||
case ESP_RST_TASK_WDT:
|
||||
reset_reason = "Reset due to task watchdog";
|
||||
break;
|
||||
case ESP_RST_WDT:
|
||||
reset_reason = "Reset due to other watchdogs";
|
||||
break;
|
||||
case ESP_RST_DEEPSLEEP:
|
||||
reset_reason = "Reset after exiting deep sleep mode";
|
||||
break;
|
||||
case ESP_RST_BROWNOUT:
|
||||
reset_reason = "Brownout reset (software or hardware)";
|
||||
break;
|
||||
case ESP_RST_SDIO:
|
||||
reset_reason = "Reset over SDIO";
|
||||
break;
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 4))
|
||||
case ESP_RST_USB:
|
||||
reset_reason = "Reset by USB peripheral";
|
||||
break;
|
||||
case ESP_RST_JTAG:
|
||||
reset_reason = "Reset by JTAG";
|
||||
break;
|
||||
case ESP_RST_EFUSE:
|
||||
reset_reason = "Reset due to efuse error";
|
||||
break;
|
||||
case ESP_RST_PWR_GLITCH:
|
||||
reset_reason = "Reset due to power glitch detected";
|
||||
break;
|
||||
case ESP_RST_CPU_LOCKUP:
|
||||
reset_reason = "Reset due to CPU lock up (double exception)";
|
||||
break;
|
||||
#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 4)
|
||||
#endif // USE_ESP32_VARIANT_ESP32
|
||||
default: // Includes ESP_RST_UNKNOWN
|
||||
switch (rtc_get_reset_reason(0)) {
|
||||
case POWERON_RESET:
|
||||
reset_reason = "Power On Reset";
|
||||
break;
|
||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||
case SW_RESET:
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
case RTC_SW_SYS_RESET:
|
||||
#endif
|
||||
reset_reason = "Software Reset Digital Core";
|
||||
break;
|
||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||
case OWDT_RESET:
|
||||
reset_reason = "Watch Dog Reset Digital Core";
|
||||
break;
|
||||
#endif
|
||||
case DEEPSLEEP_RESET:
|
||||
reset_reason = "Deep Sleep Reset Digital Core";
|
||||
break;
|
||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||
case SDIO_RESET:
|
||||
reset_reason = "SLC Module Reset Digital Core";
|
||||
break;
|
||||
#endif
|
||||
case TG0WDT_SYS_RESET:
|
||||
reset_reason = "Timer Group 0 Watch Dog Reset Digital Core";
|
||||
break;
|
||||
case TG1WDT_SYS_RESET:
|
||||
reset_reason = "Timer Group 1 Watch Dog Reset Digital Core";
|
||||
break;
|
||||
case RTCWDT_SYS_RESET:
|
||||
reset_reason = "RTC Watch Dog Reset Digital Core";
|
||||
break;
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
case INTRUSION_RESET:
|
||||
reset_reason = "Intrusion Reset CPU";
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||
case TGWDT_CPU_RESET:
|
||||
reset_reason = "Timer Group Reset CPU";
|
||||
break;
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
case TG0WDT_CPU_RESET:
|
||||
reset_reason = "Timer Group 0 Reset CPU";
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||
case SW_CPU_RESET:
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
case RTC_SW_CPU_RESET:
|
||||
#endif
|
||||
reset_reason = "Software Reset CPU";
|
||||
break;
|
||||
case RTCWDT_CPU_RESET:
|
||||
reset_reason = "RTC Watch Dog Reset CPU";
|
||||
break;
|
||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||
case EXT_CPU_RESET:
|
||||
reset_reason = "External CPU Reset";
|
||||
break;
|
||||
#endif
|
||||
case RTCWDT_BROWN_OUT_RESET:
|
||||
reset_reason = "Voltage Unstable Reset";
|
||||
break;
|
||||
case RTCWDT_RTC_RESET:
|
||||
reset_reason = "RTC Watch Dog Reset Digital Core And RTC Module";
|
||||
break;
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
case TG1WDT_CPU_RESET:
|
||||
reset_reason = "Timer Group 1 Reset CPU";
|
||||
break;
|
||||
case SUPER_WDT_RESET:
|
||||
reset_reason = "Super Watchdog Reset Digital Core And RTC Module";
|
||||
break;
|
||||
case EFUSE_RESET:
|
||||
reset_reason = "eFuse Reset Digital Core";
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
case GLITCH_RTC_RESET:
|
||||
reset_reason = "Glitch Reset Digital Core And RTC Module";
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
case USB_UART_CHIP_RESET:
|
||||
reset_reason = "USB UART Reset Digital Core";
|
||||
break;
|
||||
case USB_JTAG_CHIP_RESET:
|
||||
reset_reason = "USB JTAG Reset Digital Core";
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
case POWER_GLITCH_RESET:
|
||||
reset_reason = "Power Glitch Reset Digital Core And RTC Module";
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
reset_reason = "Unknown Reset Reason";
|
||||
}
|
||||
break;
|
||||
}
|
||||
ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str());
|
||||
return reset_reason;
|
||||
}
|
||||
|
||||
uint32_t DebugComponent::get_free_heap_() { return heap_caps_get_free_size(MALLOC_CAP_INTERNAL); }
|
||||
|
||||
static const std::map<int, const char *> CHIP_FEATURES = {
|
||||
{CHIP_FEATURE_BLE, "BLE"},
|
||||
{CHIP_FEATURE_BT, "BT"},
|
||||
{CHIP_FEATURE_EMB_FLASH, "EMB Flash"},
|
||||
{CHIP_FEATURE_EMB_PSRAM, "EMB PSRAM"},
|
||||
{CHIP_FEATURE_WIFI_BGN, "2.4GHz WiFi"},
|
||||
};
|
||||
|
||||
void DebugComponent::get_device_info_(std::string &device_info) {
|
||||
#if defined(USE_ARDUINO)
|
||||
const char *flash_mode;
|
||||
@@ -242,44 +164,16 @@ void DebugComponent::get_device_info_(std::string &device_info) {
|
||||
|
||||
esp_chip_info_t info;
|
||||
esp_chip_info(&info);
|
||||
const char *model;
|
||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||
model = "ESP32";
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32C3)
|
||||
model = "ESP32-C3";
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
model = "ESP32-C6";
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32S2)
|
||||
model = "ESP32-S2";
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
model = "ESP32-S3";
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
model = "ESP32-H2";
|
||||
#else
|
||||
model = "UNKNOWN";
|
||||
#endif
|
||||
const char *model = ESPHOME_VARIANT;
|
||||
std::string features;
|
||||
if (info.features & CHIP_FEATURE_EMB_FLASH) {
|
||||
features += "EMB_FLASH,";
|
||||
info.features &= ~CHIP_FEATURE_EMB_FLASH;
|
||||
for (auto feature : CHIP_FEATURES) {
|
||||
if (info.features & feature.first) {
|
||||
features += feature.second;
|
||||
features += ", ";
|
||||
info.features &= ~feature.first;
|
||||
}
|
||||
}
|
||||
if (info.features & CHIP_FEATURE_WIFI_BGN) {
|
||||
features += "WIFI_BGN,";
|
||||
info.features &= ~CHIP_FEATURE_WIFI_BGN;
|
||||
}
|
||||
if (info.features & CHIP_FEATURE_BLE) {
|
||||
features += "BLE,";
|
||||
info.features &= ~CHIP_FEATURE_BLE;
|
||||
}
|
||||
if (info.features & CHIP_FEATURE_BT) {
|
||||
features += "BT,";
|
||||
info.features &= ~CHIP_FEATURE_BT;
|
||||
}
|
||||
if (info.features & CHIP_FEATURE_EMB_PSRAM) {
|
||||
features += "EMB_PSRAM,";
|
||||
info.features &= ~CHIP_FEATURE_EMB_PSRAM;
|
||||
}
|
||||
if (info.features)
|
||||
if (info.features != 0)
|
||||
features += "Other:" + format_hex(info.features);
|
||||
ESP_LOGD(TAG, "Chip: Model=%s, Features=%s Cores=%u, Revision=%u", model, features.c_str(), info.cores,
|
||||
info.revision);
|
||||
@@ -289,6 +183,8 @@ void DebugComponent::get_device_info_(std::string &device_info) {
|
||||
device_info += features;
|
||||
device_info += " Cores:" + to_string(info.cores);
|
||||
device_info += " Revision:" + to_string(info.revision);
|
||||
device_info += str_sprintf("|CPU Frequency: %" PRIu32 " MHz", arch_get_cpu_freq_hz() / 1000000);
|
||||
ESP_LOGD(TAG, "CPU Frequency: %" PRIu32 " MHz", arch_get_cpu_freq_hz() / 1000000);
|
||||
|
||||
// Framework detection
|
||||
device_info += "|Framework: ";
|
||||
@@ -315,48 +211,7 @@ void DebugComponent::get_device_info_(std::string &device_info) {
|
||||
device_info += "|Reset: ";
|
||||
device_info += get_reset_reason_();
|
||||
|
||||
const char *wakeup_reason;
|
||||
switch (rtc_get_wakeup_cause()) {
|
||||
case NO_SLEEP:
|
||||
wakeup_reason = "No Sleep";
|
||||
break;
|
||||
case EXT_EVENT0_TRIG:
|
||||
wakeup_reason = "External Event 0";
|
||||
break;
|
||||
case EXT_EVENT1_TRIG:
|
||||
wakeup_reason = "External Event 1";
|
||||
break;
|
||||
case GPIO_TRIG:
|
||||
wakeup_reason = "GPIO";
|
||||
break;
|
||||
case TIMER_EXPIRE:
|
||||
wakeup_reason = "Wakeup Timer";
|
||||
break;
|
||||
case SDIO_TRIG:
|
||||
wakeup_reason = "SDIO";
|
||||
break;
|
||||
case MAC_TRIG:
|
||||
wakeup_reason = "MAC";
|
||||
break;
|
||||
case UART0_TRIG:
|
||||
wakeup_reason = "UART0";
|
||||
break;
|
||||
case UART1_TRIG:
|
||||
wakeup_reason = "UART1";
|
||||
break;
|
||||
case TOUCH_TRIG:
|
||||
wakeup_reason = "Touch";
|
||||
break;
|
||||
case SAR_TRIG:
|
||||
wakeup_reason = "SAR";
|
||||
break;
|
||||
case BT_TRIG:
|
||||
wakeup_reason = "BT";
|
||||
break;
|
||||
default:
|
||||
wakeup_reason = "Unknown";
|
||||
}
|
||||
ESP_LOGD(TAG, "Wakeup Reason: %s", wakeup_reason);
|
||||
std::string wakeup_reason = this->get_wakeup_cause_();
|
||||
device_info += "|Wakeup: ";
|
||||
device_info += wakeup_reason;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
from esphome.components.esp32 import CONF_CPU_FREQUENCY
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BLOCK,
|
||||
@@ -10,6 +11,7 @@ from esphome.const import (
|
||||
ICON_COUNTER,
|
||||
ICON_TIMER,
|
||||
UNIT_BYTES,
|
||||
UNIT_HERTZ,
|
||||
UNIT_MILLISECOND,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
@@ -60,6 +62,14 @@ CONFIG_SCHEMA = {
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_CPU_FREQUENCY): cv.All(
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HERTZ,
|
||||
icon="mdi:speedometer",
|
||||
accuracy_decimals=0,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -85,3 +95,7 @@ async def to_code(config):
|
||||
if psram_conf := config.get(CONF_PSRAM):
|
||||
sens = await sensor.new_sensor(psram_conf)
|
||||
cg.add(debug_component.set_psram_sensor(sens))
|
||||
|
||||
if cpu_freq_conf := config.get(CONF_CPU_FREQUENCY):
|
||||
sens = await sensor.new_sensor(cpu_freq_conf)
|
||||
cg.add(debug_component.set_cpu_frequency_sensor(sens))
|
||||
|
||||
@@ -31,9 +31,12 @@ void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode) {
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wakeup_ = ext1_wakeup; }
|
||||
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
void DeepSleepComponent::set_touch_wakeup(bool touch_wakeup) { this->touch_wakeup_ = touch_wakeup; }
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
void DeepSleepComponent::set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration) {
|
||||
wakeup_cause_to_run_duration_ = wakeup_cause_to_run_duration;
|
||||
}
|
||||
@@ -65,7 +68,7 @@ bool DeepSleepComponent::prepare_to_sleep_() {
|
||||
}
|
||||
|
||||
void DeepSleepComponent::deep_sleep_() {
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
if (this->sleep_duration_.has_value())
|
||||
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
|
||||
if (this->wakeup_pin_ != nullptr) {
|
||||
@@ -84,6 +87,15 @@ void DeepSleepComponent::deep_sleep_() {
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
if (this->sleep_duration_.has_value())
|
||||
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
|
||||
if (this->ext1_wakeup_.has_value()) {
|
||||
esp_sleep_enable_ext1_wakeup(this->ext1_wakeup_->mask, this->ext1_wakeup_->wakeup_mode);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
if (this->sleep_duration_.has_value())
|
||||
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
|
||||
|
||||
@@ -69,21 +69,16 @@ bool Rect::inside(int16_t test_x, int16_t test_y, bool absolute) const { // NOL
|
||||
return true;
|
||||
}
|
||||
if (absolute) {
|
||||
return ((test_x >= this->x) && (test_x <= this->x2()) && (test_y >= this->y) && (test_y <= this->y2()));
|
||||
} else {
|
||||
return ((test_x >= 0) && (test_x <= this->w) && (test_y >= 0) && (test_y <= this->h));
|
||||
return test_x >= this->x && test_x < this->x2() && test_y >= this->y && test_y < this->y2();
|
||||
}
|
||||
return test_x >= 0 && test_x < this->w && test_y >= 0 && test_y < this->h;
|
||||
}
|
||||
|
||||
bool Rect::inside(Rect rect, bool absolute) const {
|
||||
bool Rect::inside(Rect rect) const {
|
||||
if (!this->is_set() || !rect.is_set()) {
|
||||
return true;
|
||||
}
|
||||
if (absolute) {
|
||||
return ((rect.x <= this->x2()) && (rect.x2() >= this->x) && (rect.y <= this->y2()) && (rect.y2() >= this->y));
|
||||
} else {
|
||||
return ((rect.x <= this->w) && (rect.w >= 0) && (rect.y <= this->h) && (rect.h >= 0));
|
||||
}
|
||||
return this->x2() >= rect.x && this->x <= rect.x2() && this->y2() >= rect.y && this->y <= rect.y2();
|
||||
}
|
||||
|
||||
void Rect::info(const std::string &prefix) {
|
||||
|
||||
@@ -26,7 +26,7 @@ class Rect {
|
||||
void extend(Rect rect);
|
||||
void shrink(Rect rect);
|
||||
|
||||
bool inside(Rect rect, bool absolute = true) const;
|
||||
bool inside(Rect rect) const;
|
||||
bool inside(int16_t test_x, int16_t test_y, bool absolute = true) const;
|
||||
bool equal(Rect rect) const;
|
||||
void info(const std::string &prefix = "rect info:");
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from dataclasses import dataclass
|
||||
import itertools
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
@@ -37,6 +38,7 @@ from esphome.const import (
|
||||
__version__,
|
||||
)
|
||||
from esphome.core import CORE, HexInt, TimePeriod
|
||||
from esphome.cpp_generator import RawExpression
|
||||
import esphome.final_validate as fv
|
||||
from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed
|
||||
|
||||
@@ -54,6 +56,12 @@ from .const import ( # noqa
|
||||
KEY_SUBMODULES,
|
||||
KEY_VARIANT,
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C2,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32H2,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
VARIANT_FRIENDLY,
|
||||
VARIANTS,
|
||||
)
|
||||
@@ -70,7 +78,43 @@ CONF_RELEASE = "release"
|
||||
CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES = "enable_idf_experimental_features"
|
||||
|
||||
|
||||
def get_cpu_frequencies(*frequencies):
|
||||
return [str(x) + "MHZ" for x in frequencies]
|
||||
|
||||
|
||||
CPU_FREQUENCIES = {
|
||||
VARIANT_ESP32: get_cpu_frequencies(80, 160, 240),
|
||||
VARIANT_ESP32S2: get_cpu_frequencies(80, 160, 240),
|
||||
VARIANT_ESP32S3: get_cpu_frequencies(80, 160, 240),
|
||||
VARIANT_ESP32C2: get_cpu_frequencies(80, 120),
|
||||
VARIANT_ESP32C3: get_cpu_frequencies(80, 160),
|
||||
VARIANT_ESP32C6: get_cpu_frequencies(80, 120, 160),
|
||||
VARIANT_ESP32H2: get_cpu_frequencies(16, 32, 48, 64, 96),
|
||||
}
|
||||
|
||||
# Make sure not missed here if a new variant added.
|
||||
assert all(v in CPU_FREQUENCIES for v in VARIANTS)
|
||||
|
||||
FULL_CPU_FREQUENCIES = set(itertools.chain.from_iterable(CPU_FREQUENCIES.values()))
|
||||
|
||||
|
||||
def set_core_data(config):
|
||||
cpu_frequency = config.get(CONF_CPU_FREQUENCY, None)
|
||||
variant = config[CONF_VARIANT]
|
||||
# if not specified in config, set to 160MHz if supported, the fastest otherwise
|
||||
if cpu_frequency is None:
|
||||
choices = CPU_FREQUENCIES[variant]
|
||||
if "160MHZ" in choices:
|
||||
cpu_frequency = "160MHZ"
|
||||
else:
|
||||
cpu_frequency = choices[-1]
|
||||
config[CONF_CPU_FREQUENCY] = cpu_frequency
|
||||
elif cpu_frequency not in CPU_FREQUENCIES[variant]:
|
||||
raise cv.Invalid(
|
||||
f"Invalid CPU frequency '{cpu_frequency}' for {config[CONF_VARIANT]}",
|
||||
path=[CONF_CPU_FREQUENCY],
|
||||
)
|
||||
|
||||
CORE.data[KEY_ESP32] = {}
|
||||
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = PLATFORM_ESP32
|
||||
conf = config[CONF_FRAMEWORK]
|
||||
@@ -83,6 +127,7 @@ def set_core_data(config):
|
||||
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse(
|
||||
config[CONF_FRAMEWORK][CONF_VERSION]
|
||||
)
|
||||
|
||||
CORE.data[KEY_ESP32][KEY_BOARD] = config[CONF_BOARD]
|
||||
CORE.data[KEY_ESP32][KEY_VARIANT] = config[CONF_VARIANT]
|
||||
CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES] = {}
|
||||
@@ -251,7 +296,7 @@ ARDUINO_PLATFORM_VERSION = cv.Version(5, 4, 0)
|
||||
# The default/recommended esp-idf framework version
|
||||
# - https://github.com/espressif/esp-idf/releases
|
||||
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
|
||||
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(5, 1, 5)
|
||||
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(5, 1, 6)
|
||||
# The platformio/espressif32 version to use for esp-idf frameworks
|
||||
# - https://github.com/platformio/platform-espressif32/releases
|
||||
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
|
||||
@@ -274,12 +319,15 @@ SUPPORTED_PLATFORMIO_ESP_IDF_5X = [
|
||||
# pioarduino versions that don't require a release number
|
||||
# List based on https://github.com/pioarduino/esp-idf/releases
|
||||
SUPPORTED_PIOARDUINO_ESP_IDF_5X = [
|
||||
cv.Version(5, 5, 0),
|
||||
cv.Version(5, 4, 1),
|
||||
cv.Version(5, 4, 0),
|
||||
cv.Version(5, 3, 3),
|
||||
cv.Version(5, 3, 2),
|
||||
cv.Version(5, 3, 1),
|
||||
cv.Version(5, 3, 0),
|
||||
cv.Version(5, 1, 5),
|
||||
cv.Version(5, 1, 6),
|
||||
]
|
||||
|
||||
|
||||
@@ -321,8 +369,8 @@ def _arduino_check_versions(value):
|
||||
def _esp_idf_check_versions(value):
|
||||
value = value.copy()
|
||||
lookups = {
|
||||
"dev": (cv.Version(5, 1, 5), "https://github.com/espressif/esp-idf.git"),
|
||||
"latest": (cv.Version(5, 1, 5), None),
|
||||
"dev": (cv.Version(5, 1, 6), "https://github.com/espressif/esp-idf.git"),
|
||||
"latest": (cv.Version(5, 1, 6), None),
|
||||
"recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None),
|
||||
}
|
||||
|
||||
@@ -550,11 +598,15 @@ FLASH_SIZES = [
|
||||
]
|
||||
|
||||
CONF_FLASH_SIZE = "flash_size"
|
||||
CONF_CPU_FREQUENCY = "cpu_frequency"
|
||||
CONF_PARTITIONS = "partitions"
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_BOARD): cv.string_strict,
|
||||
cv.Optional(CONF_CPU_FREQUENCY): cv.one_of(
|
||||
*FULL_CPU_FREQUENCIES, upper=True
|
||||
),
|
||||
cv.Optional(CONF_FLASH_SIZE, default="4MB"): cv.one_of(
|
||||
*FLASH_SIZES, upper=True
|
||||
),
|
||||
@@ -595,6 +647,7 @@ async def to_code(config):
|
||||
os.path.join(os.path.dirname(__file__), "post_build.py.script"),
|
||||
)
|
||||
|
||||
freq = config[CONF_CPU_FREQUENCY][:-3]
|
||||
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
|
||||
cg.add_platformio_option("framework", "espidf")
|
||||
cg.add_build_flag("-DUSE_ESP_IDF")
|
||||
@@ -628,6 +681,9 @@ async def to_code(config):
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0", False)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1", False)
|
||||
|
||||
# Set default CPU frequency
|
||||
add_idf_sdkconfig_option(f"CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_{freq}", True)
|
||||
|
||||
cg.add_platformio_option("board_build.partitions", "partitions.csv")
|
||||
if CONF_PARTITIONS in config:
|
||||
add_extra_build_file(
|
||||
@@ -693,6 +749,7 @@ async def to_code(config):
|
||||
f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})"
|
||||
),
|
||||
)
|
||||
cg.add(RawExpression(f"setCpuFrequencyMhz({freq})"))
|
||||
|
||||
|
||||
APP_PARTITION_SIZES = {
|
||||
|
||||
@@ -13,11 +13,13 @@
|
||||
#include <hal/cpu_hal.h>
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include <esp32-hal.h>
|
||||
#endif
|
||||
#include <Esp.h>
|
||||
#else
|
||||
#include <esp_clk_tree.h>
|
||||
|
||||
void setup();
|
||||
void loop();
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
|
||||
@@ -59,9 +61,13 @@ uint32_t arch_get_cpu_cycle_count() { return esp_cpu_get_cycle_count(); }
|
||||
uint32_t arch_get_cpu_cycle_count() { return cpu_hal_get_cycle_count(); }
|
||||
#endif
|
||||
uint32_t arch_get_cpu_freq_hz() {
|
||||
rtc_cpu_freq_config_t config;
|
||||
rtc_clk_cpu_freq_get_config(&config);
|
||||
return config.freq_mhz * 1000000U;
|
||||
uint32_t freq = 0;
|
||||
#ifdef USE_ESP_IDF
|
||||
esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_CPU, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &freq);
|
||||
#elif defined(USE_ARDUINO)
|
||||
freq = ESP.getCpuFreqMHz() * 1000000;
|
||||
#endif
|
||||
return freq;
|
||||
}
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
|
||||
#include "ble.h"
|
||||
|
||||
#ifdef USE_ESP32_VARIANT_ESP32C6
|
||||
#include "const_esp32c6.h"
|
||||
#endif // USE_ESP32_VARIANT_ESP32C6
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
@@ -114,6 +110,7 @@ void ESP32BLE::advertising_init_() {
|
||||
|
||||
this->advertising_->set_scan_response(true);
|
||||
this->advertising_->set_min_preferred_interval(0x06);
|
||||
this->advertising_->set_appearance(this->appearance_);
|
||||
}
|
||||
|
||||
bool ESP32BLE::ble_setup_() {
|
||||
@@ -127,11 +124,7 @@ bool ESP32BLE::ble_setup_() {
|
||||
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
|
||||
// start bt controller
|
||||
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {
|
||||
#ifdef USE_ESP32_VARIANT_ESP32C6
|
||||
esp_bt_controller_config_t cfg = BT_CONTROLLER_CONFIG;
|
||||
#else
|
||||
esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
#endif
|
||||
err = esp_bt_controller_init(&cfg);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_bt_controller_init failed: %s", esp_err_to_name(err));
|
||||
|
||||
@@ -95,6 +95,7 @@ class ESP32BLE : public Component {
|
||||
void advertising_start();
|
||||
void advertising_set_service_data(const std::vector<uint8_t> &data);
|
||||
void advertising_set_manufacturer_data(const std::vector<uint8_t> &data);
|
||||
void advertising_set_appearance(uint16_t appearance) { this->appearance_ = appearance; }
|
||||
void advertising_add_service_uuid(ESPBTUUID uuid);
|
||||
void advertising_remove_service_uuid(ESPBTUUID uuid);
|
||||
void advertising_register_raw_advertisement_callback(std::function<void(bool)> &&callback);
|
||||
@@ -128,11 +129,12 @@ class ESP32BLE : public Component {
|
||||
BLEComponentState state_{BLE_COMPONENT_STATE_OFF};
|
||||
|
||||
Queue<BLEEvent> ble_events_;
|
||||
BLEAdvertising *advertising_;
|
||||
BLEAdvertising *advertising_{};
|
||||
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE};
|
||||
uint32_t advertising_cycle_time_;
|
||||
bool enable_on_boot_;
|
||||
uint32_t advertising_cycle_time_{};
|
||||
bool enable_on_boot_{};
|
||||
optional<std::string> name_;
|
||||
uint16_t appearance_{0};
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
@@ -32,6 +32,7 @@ class BLEAdvertising {
|
||||
void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; }
|
||||
void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; }
|
||||
void set_manufacturer_data(const std::vector<uint8_t> &data);
|
||||
void set_appearance(uint16_t appearance) { this->advertising_data_.appearance = appearance; }
|
||||
void set_service_data(const std::vector<uint8_t> &data);
|
||||
void register_raw_advertisement_callback(std::function<void(bool)> &&callback);
|
||||
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32_VARIANT_ESP32C6
|
||||
|
||||
#include <esp_bt.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble {
|
||||
|
||||
static const esp_bt_controller_config_t BT_CONTROLLER_CONFIG = {
|
||||
.config_version = CONFIG_VERSION,
|
||||
.ble_ll_resolv_list_size = CONFIG_BT_LE_LL_RESOLV_LIST_SIZE,
|
||||
.ble_hci_evt_hi_buf_count = DEFAULT_BT_LE_HCI_EVT_HI_BUF_COUNT,
|
||||
.ble_hci_evt_lo_buf_count = DEFAULT_BT_LE_HCI_EVT_LO_BUF_COUNT,
|
||||
.ble_ll_sync_list_cnt = DEFAULT_BT_LE_MAX_PERIODIC_ADVERTISER_LIST,
|
||||
.ble_ll_sync_cnt = DEFAULT_BT_LE_MAX_PERIODIC_SYNCS,
|
||||
.ble_ll_rsp_dup_list_count = CONFIG_BT_LE_LL_DUP_SCAN_LIST_COUNT,
|
||||
.ble_ll_adv_dup_list_count = CONFIG_BT_LE_LL_DUP_SCAN_LIST_COUNT,
|
||||
.ble_ll_tx_pwr_dbm = BLE_LL_TX_PWR_DBM_N,
|
||||
.rtc_freq = RTC_FREQ_N,
|
||||
.ble_ll_sca = CONFIG_BT_LE_LL_SCA,
|
||||
.ble_ll_scan_phy_number = BLE_LL_SCAN_PHY_NUMBER_N,
|
||||
.ble_ll_conn_def_auth_pyld_tmo = BLE_LL_CONN_DEF_AUTH_PYLD_TMO_N,
|
||||
.ble_ll_jitter_usecs = BLE_LL_JITTER_USECS_N,
|
||||
.ble_ll_sched_max_adv_pdu_usecs = BLE_LL_SCHED_MAX_ADV_PDU_USECS_N,
|
||||
.ble_ll_sched_direct_adv_max_usecs = BLE_LL_SCHED_DIRECT_ADV_MAX_USECS_N,
|
||||
.ble_ll_sched_adv_max_usecs = BLE_LL_SCHED_ADV_MAX_USECS_N,
|
||||
.ble_scan_rsp_data_max_len = DEFAULT_BT_LE_SCAN_RSP_DATA_MAX_LEN_N,
|
||||
.ble_ll_cfg_num_hci_cmd_pkts = BLE_LL_CFG_NUM_HCI_CMD_PKTS_N,
|
||||
.ble_ll_ctrl_proc_timeout_ms = BLE_LL_CTRL_PROC_TIMEOUT_MS_N,
|
||||
.nimble_max_connections = DEFAULT_BT_LE_MAX_CONNECTIONS,
|
||||
.ble_whitelist_size = DEFAULT_BT_NIMBLE_WHITELIST_SIZE, // NOLINT
|
||||
.ble_acl_buf_size = DEFAULT_BT_LE_ACL_BUF_SIZE,
|
||||
.ble_acl_buf_count = DEFAULT_BT_LE_ACL_BUF_COUNT,
|
||||
.ble_hci_evt_buf_size = DEFAULT_BT_LE_HCI_EVT_BUF_SIZE,
|
||||
.ble_multi_adv_instances = DEFAULT_BT_LE_MAX_EXT_ADV_INSTANCES,
|
||||
.ble_ext_adv_max_size = DEFAULT_BT_LE_EXT_ADV_MAX_SIZE,
|
||||
.controller_task_stack_size = NIMBLE_LL_STACK_SIZE,
|
||||
.controller_task_prio = ESP_TASK_BT_CONTROLLER_PRIO,
|
||||
.controller_run_cpu = 0,
|
||||
.enable_qa_test = RUN_QA_TEST,
|
||||
.enable_bqb_test = RUN_BQB_TEST,
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 1)
|
||||
// The following fields have been removed since ESP IDF version 5.3.1, see commit:
|
||||
// https://github.com/espressif/esp-idf/commit/e761c1de8f9c0777829d597b4d5a33bb070a30a8
|
||||
.enable_uart_hci = HCI_UART_EN,
|
||||
.ble_hci_uart_port = DEFAULT_BT_LE_HCI_UART_PORT,
|
||||
.ble_hci_uart_baud = DEFAULT_BT_LE_HCI_UART_BAUD,
|
||||
.ble_hci_uart_data_bits = DEFAULT_BT_LE_HCI_UART_DATA_BITS,
|
||||
.ble_hci_uart_stop_bits = DEFAULT_BT_LE_HCI_UART_STOP_BITS,
|
||||
.ble_hci_uart_flow_ctrl = DEFAULT_BT_LE_HCI_UART_FLOW_CTRL,
|
||||
.ble_hci_uart_uart_parity = DEFAULT_BT_LE_HCI_UART_PARITY,
|
||||
#endif
|
||||
.enable_tx_cca = DEFAULT_BT_LE_TX_CCA_ENABLED,
|
||||
.cca_rssi_thresh = 256 - DEFAULT_BT_LE_CCA_RSSI_THRESH,
|
||||
.sleep_en = NIMBLE_SLEEP_ENABLE,
|
||||
.coex_phy_coded_tx_rx_time_limit = DEFAULT_BT_LE_COEX_PHY_CODED_TX_RX_TLIM_EFF,
|
||||
.dis_scan_backoff = NIMBLE_DISABLE_SCAN_BACKOFF,
|
||||
.ble_scan_classify_filter_enable = 1,
|
||||
.main_xtal_freq = CONFIG_XTAL_FREQ,
|
||||
.version_num = (uint8_t) efuse_hal_chip_revision(),
|
||||
.cpu_freq_mhz = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ,
|
||||
.ignore_wl_for_direct_adv = 0,
|
||||
.enable_pcl = DEFAULT_BT_LE_POWER_CONTROL_ENABLED,
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 3)
|
||||
.csa2_select = DEFAULT_BT_LE_50_FEATURE_SUPPORT,
|
||||
#endif
|
||||
.config_magic = CONFIG_MAGIC,
|
||||
};
|
||||
|
||||
} // namespace esp32_ble
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32_VARIANT_ESP32C6
|
||||
@@ -32,6 +32,7 @@ DEPENDENCIES = ["esp32"]
|
||||
DOMAIN = "esp32_ble_server"
|
||||
|
||||
CONF_ADVERTISE = "advertise"
|
||||
CONF_APPEARANCE = "appearance"
|
||||
CONF_BROADCAST = "broadcast"
|
||||
CONF_CHARACTERISTICS = "characteristics"
|
||||
CONF_DESCRIPTION = "description"
|
||||
@@ -421,6 +422,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
cv.GenerateID(): cv.declare_id(BLEServer),
|
||||
cv.GenerateID(esp32_ble.CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE),
|
||||
cv.Optional(CONF_MANUFACTURER): value_schema("string", templatable=False),
|
||||
cv.Optional(CONF_APPEARANCE, default=0): cv.uint16_t,
|
||||
cv.Optional(CONF_MODEL): value_schema("string", templatable=False),
|
||||
cv.Optional(CONF_FIRMWARE_VERSION): value_schema("string", templatable=False),
|
||||
cv.Optional(CONF_MANUFACTURER_DATA): cv.Schema([cv.uint8_t]),
|
||||
@@ -531,6 +533,7 @@ async def to_code(config):
|
||||
cg.add(parent.register_gatts_event_handler(var))
|
||||
cg.add(parent.register_ble_status_event_handler(var))
|
||||
cg.add(var.set_parent(parent))
|
||||
cg.add(parent.advertising_set_appearance(config[CONF_APPEARANCE]))
|
||||
if CONF_MANUFACTURER_DATA in config:
|
||||
cg.add(var.set_manufacturer_data(config[CONF_MANUFACTURER_DATA]))
|
||||
for service_config in config[CONF_SERVICES]:
|
||||
|
||||
@@ -17,6 +17,7 @@ from esphome.components.esp32_ble import (
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ACTIVE,
|
||||
CONF_CONTINUOUS,
|
||||
CONF_DURATION,
|
||||
CONF_ID,
|
||||
CONF_INTERVAL,
|
||||
@@ -42,7 +43,6 @@ CONF_MAX_CONNECTIONS = "max_connections"
|
||||
CONF_ESP32_BLE_ID = "esp32_ble_id"
|
||||
CONF_SCAN_PARAMETERS = "scan_parameters"
|
||||
CONF_WINDOW = "window"
|
||||
CONF_CONTINUOUS = "continuous"
|
||||
CONF_ON_SCAN_END = "on_scan_end"
|
||||
|
||||
DEFAULT_MAX_CONNECTIONS = 3
|
||||
|
||||
@@ -57,7 +57,6 @@ void ESP32BLETracker::setup() {
|
||||
|
||||
global_esp32_ble_tracker = this;
|
||||
this->scan_result_lock_ = xSemaphoreCreateMutex();
|
||||
this->scan_end_lock_ = xSemaphoreCreateMutex();
|
||||
|
||||
#ifdef USE_OTA
|
||||
ota::get_global_ota_callback()->add_on_state_callback(
|
||||
@@ -117,119 +116,104 @@ void ESP32BLETracker::loop() {
|
||||
}
|
||||
bool promote_to_connecting = discovered && !searching && !connecting;
|
||||
|
||||
if (!this->scanner_idle_) {
|
||||
if (this->scan_result_index_ && // if it looks like we have a scan result we will take the lock
|
||||
xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) {
|
||||
uint32_t index = this->scan_result_index_;
|
||||
if (index >= ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) {
|
||||
ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up.");
|
||||
}
|
||||
if (this->scanner_state_ == ScannerState::RUNNING &&
|
||||
this->scan_result_index_ && // if it looks like we have a scan result we will take the lock
|
||||
xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) {
|
||||
uint32_t index = this->scan_result_index_;
|
||||
if (index >= ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) {
|
||||
ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up.");
|
||||
}
|
||||
|
||||
if (this->raw_advertisements_) {
|
||||
if (this->raw_advertisements_) {
|
||||
for (auto *listener : this->listeners_) {
|
||||
listener->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
|
||||
}
|
||||
for (auto *client : this->clients_) {
|
||||
client->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->parse_advertisements_) {
|
||||
for (size_t i = 0; i < index; i++) {
|
||||
ESPBTDevice device;
|
||||
device.parse_scan_rst(this->scan_result_buffer_[i]);
|
||||
|
||||
bool found = false;
|
||||
for (auto *listener : this->listeners_) {
|
||||
listener->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
|
||||
if (listener->parse_device(device))
|
||||
found = true;
|
||||
}
|
||||
|
||||
for (auto *client : this->clients_) {
|
||||
client->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->parse_advertisements_) {
|
||||
for (size_t i = 0; i < index; i++) {
|
||||
ESPBTDevice device;
|
||||
device.parse_scan_rst(this->scan_result_buffer_[i]);
|
||||
|
||||
bool found = false;
|
||||
for (auto *listener : this->listeners_) {
|
||||
if (listener->parse_device(device))
|
||||
found = true;
|
||||
}
|
||||
|
||||
for (auto *client : this->clients_) {
|
||||
if (client->parse_device(device)) {
|
||||
found = true;
|
||||
if (!connecting && client->state() == ClientState::DISCOVERED) {
|
||||
promote_to_connecting = true;
|
||||
}
|
||||
if (client->parse_device(device)) {
|
||||
found = true;
|
||||
if (!connecting && client->state() == ClientState::DISCOVERED) {
|
||||
promote_to_connecting = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found && !this->scan_continuous_) {
|
||||
this->print_bt_device_info(device);
|
||||
}
|
||||
if (!found && !this->scan_continuous_) {
|
||||
this->print_bt_device_info(device);
|
||||
}
|
||||
}
|
||||
this->scan_result_index_ = 0;
|
||||
xSemaphoreGive(this->scan_result_lock_);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Avoid starting the scanner if:
|
||||
- we are already scanning
|
||||
- we are connecting to a device
|
||||
- we are disconnecting from a device
|
||||
|
||||
Otherwise the scanner could fail to ever start again
|
||||
and our only way to recover is to reboot.
|
||||
|
||||
https://github.com/espressif/esp-idf/issues/6688
|
||||
|
||||
*/
|
||||
if (!connecting && xSemaphoreTake(this->scan_end_lock_, 0L)) {
|
||||
if (this->scan_continuous_) {
|
||||
if (!disconnecting && !promote_to_connecting && !this->scan_start_failed_ && !this->scan_set_param_failed_) {
|
||||
this->start_scan_(false);
|
||||
} else {
|
||||
// We didn't start the scan, so we need to release the lock
|
||||
xSemaphoreGive(this->scan_end_lock_);
|
||||
}
|
||||
} else if (!this->scanner_idle_) {
|
||||
this->end_of_scan_();
|
||||
return;
|
||||
}
|
||||
this->scan_result_index_ = 0;
|
||||
xSemaphoreGive(this->scan_result_lock_);
|
||||
}
|
||||
if (this->scanner_state_ == ScannerState::STOPPED) {
|
||||
this->end_of_scan_(); // Change state to IDLE
|
||||
}
|
||||
if (this->scanner_state_ == ScannerState::FAILED ||
|
||||
(this->scan_set_param_failed_ && this->scanner_state_ == ScannerState::RUNNING)) {
|
||||
this->stop_scan_();
|
||||
if (this->scan_start_fail_count_ == std::numeric_limits<uint8_t>::max()) {
|
||||
ESP_LOGE(TAG, "ESP-IDF BLE scan could not restart after %d attempts, rebooting to restore BLE stack...",
|
||||
std::numeric_limits<uint8_t>::max());
|
||||
App.reboot();
|
||||
}
|
||||
|
||||
if (this->scan_start_failed_ || this->scan_set_param_failed_) {
|
||||
if (this->scan_start_fail_count_ == std::numeric_limits<uint8_t>::max()) {
|
||||
ESP_LOGE(TAG, "ESP-IDF BLE scan could not restart after %d attempts, rebooting to restore BLE stack...",
|
||||
std::numeric_limits<uint8_t>::max());
|
||||
App.reboot();
|
||||
}
|
||||
if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
|
||||
xSemaphoreGive(this->scan_end_lock_);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Stopping scan after failure...");
|
||||
this->stop_scan_();
|
||||
}
|
||||
if (this->scan_start_failed_) {
|
||||
ESP_LOGE(TAG, "Scan start failed: %d", this->scan_start_failed_);
|
||||
this->scan_start_failed_ = ESP_BT_STATUS_SUCCESS;
|
||||
}
|
||||
if (this->scan_set_param_failed_) {
|
||||
ESP_LOGE(TAG, "Scan set param failed: %d", this->scan_set_param_failed_);
|
||||
this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
|
||||
}
|
||||
if (this->scan_start_failed_) {
|
||||
ESP_LOGE(TAG, "Scan start failed: %d", this->scan_start_failed_);
|
||||
this->scan_start_failed_ = ESP_BT_STATUS_SUCCESS;
|
||||
}
|
||||
if (this->scan_set_param_failed_) {
|
||||
ESP_LOGE(TAG, "Scan set param failed: %d", this->scan_set_param_failed_);
|
||||
this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
||||
Avoid starting the scanner if:
|
||||
- we are already scanning
|
||||
- we are connecting to a device
|
||||
- we are disconnecting from a device
|
||||
|
||||
Otherwise the scanner could fail to ever start again
|
||||
and our only way to recover is to reboot.
|
||||
|
||||
https://github.com/espressif/esp-idf/issues/6688
|
||||
|
||||
*/
|
||||
if (this->scanner_state_ == ScannerState::IDLE && this->scan_continuous_ && !connecting && !disconnecting &&
|
||||
!promote_to_connecting) {
|
||||
this->start_scan_(false); // first = false
|
||||
}
|
||||
// If there is a discovered client and no connecting
|
||||
// clients and no clients using the scanner to search for
|
||||
// devices, then stop scanning and promote the discovered
|
||||
// client to ready to connect.
|
||||
if (promote_to_connecting) {
|
||||
if (promote_to_connecting &&
|
||||
(this->scanner_state_ == ScannerState::RUNNING || this->scanner_state_ == ScannerState::IDLE)) {
|
||||
for (auto *client : this->clients_) {
|
||||
if (client->state() == ClientState::DISCOVERED) {
|
||||
if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
|
||||
// Scanner is not running since we got the
|
||||
// lock, so we can promote the client.
|
||||
xSemaphoreGive(this->scan_end_lock_);
|
||||
if (this->scanner_state_ == ScannerState::RUNNING) {
|
||||
ESP_LOGD(TAG, "Stopping scan to make connection...");
|
||||
this->stop_scan_();
|
||||
} else if (this->scanner_state_ == ScannerState::IDLE) {
|
||||
ESP_LOGD(TAG, "Promoting client to connect...");
|
||||
// We only want to promote one client at a time.
|
||||
// once the scanner is fully stopped.
|
||||
client->set_state(ClientState::READY_TO_CONNECT);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Pausing scan to make connection...");
|
||||
this->stop_scan_();
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -237,13 +221,7 @@ void ESP32BLETracker::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
void ESP32BLETracker::start_scan() {
|
||||
if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
|
||||
this->start_scan_(true);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Scan requested when a scan is already in progress. Ignoring.");
|
||||
}
|
||||
}
|
||||
void ESP32BLETracker::start_scan() { this->start_scan_(true); }
|
||||
|
||||
void ESP32BLETracker::stop_scan() {
|
||||
ESP_LOGD(TAG, "Stopping scan.");
|
||||
@@ -251,16 +229,23 @@ void ESP32BLETracker::stop_scan() {
|
||||
this->stop_scan_();
|
||||
}
|
||||
|
||||
void ESP32BLETracker::ble_before_disabled_event_handler() {
|
||||
this->stop_scan_();
|
||||
xSemaphoreGive(this->scan_end_lock_);
|
||||
}
|
||||
void ESP32BLETracker::ble_before_disabled_event_handler() { this->stop_scan_(); }
|
||||
|
||||
void ESP32BLETracker::stop_scan_() {
|
||||
this->cancel_timeout("scan");
|
||||
if (this->scanner_idle_) {
|
||||
if (this->scanner_state_ != ScannerState::RUNNING && this->scanner_state_ != ScannerState::FAILED) {
|
||||
if (this->scanner_state_ == ScannerState::IDLE) {
|
||||
ESP_LOGE(TAG, "Scan is already stopped while trying to stop.");
|
||||
} else if (this->scanner_state_ == ScannerState::STARTING) {
|
||||
ESP_LOGE(TAG, "Scan is starting while trying to stop.");
|
||||
} else if (this->scanner_state_ == ScannerState::STOPPING) {
|
||||
ESP_LOGE(TAG, "Scan is already stopping while trying to stop.");
|
||||
} else if (this->scanner_state_ == ScannerState::STOPPED) {
|
||||
ESP_LOGE(TAG, "Scan is already stopped while trying to stop.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
this->cancel_timeout("scan");
|
||||
this->set_scanner_state_(ScannerState::STOPPING);
|
||||
esp_err_t err = esp_ble_gap_stop_scanning();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gap_stop_scanning failed: %d", err);
|
||||
@@ -273,13 +258,22 @@ void ESP32BLETracker::start_scan_(bool first) {
|
||||
ESP_LOGW(TAG, "Cannot start scan while ESP32BLE is disabled.");
|
||||
return;
|
||||
}
|
||||
// The lock must be held when calling this function.
|
||||
if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
|
||||
ESP_LOGE(TAG, "start_scan called without holding scan_end_lock_");
|
||||
if (this->scanner_state_ != ScannerState::IDLE) {
|
||||
if (this->scanner_state_ == ScannerState::STARTING) {
|
||||
ESP_LOGE(TAG, "Cannot start scan while already starting.");
|
||||
} else if (this->scanner_state_ == ScannerState::RUNNING) {
|
||||
ESP_LOGE(TAG, "Cannot start scan while already running.");
|
||||
} else if (this->scanner_state_ == ScannerState::STOPPING) {
|
||||
ESP_LOGE(TAG, "Cannot start scan while already stopping.");
|
||||
} else if (this->scanner_state_ == ScannerState::FAILED) {
|
||||
ESP_LOGE(TAG, "Cannot start scan while already failed.");
|
||||
} else if (this->scanner_state_ == ScannerState::STOPPED) {
|
||||
ESP_LOGE(TAG, "Cannot start scan while already stopped.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Starting scan...");
|
||||
this->set_scanner_state_(ScannerState::STARTING);
|
||||
ESP_LOGD(TAG, "Starting scan, set scanner state to STARTING.");
|
||||
if (!first) {
|
||||
for (auto *listener : this->listeners_)
|
||||
listener->on_scan_end();
|
||||
@@ -307,24 +301,21 @@ void ESP32BLETracker::start_scan_(bool first) {
|
||||
ESP_LOGE(TAG, "esp_ble_gap_start_scanning failed: %d", err);
|
||||
return;
|
||||
}
|
||||
this->scanner_idle_ = false;
|
||||
}
|
||||
|
||||
void ESP32BLETracker::end_of_scan_() {
|
||||
// The lock must be held when calling this function.
|
||||
if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
|
||||
ESP_LOGE(TAG, "end_of_scan_ called without holding the scan_end_lock_");
|
||||
if (this->scanner_state_ != ScannerState::STOPPED) {
|
||||
ESP_LOGE(TAG, "end_of_scan_ called while scanner is not stopped.");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "End of scan.");
|
||||
this->scanner_idle_ = true;
|
||||
ESP_LOGD(TAG, "End of scan, set scanner state to IDLE.");
|
||||
this->already_discovered_.clear();
|
||||
xSemaphoreGive(this->scan_end_lock_);
|
||||
this->cancel_timeout("scan");
|
||||
|
||||
for (auto *listener : this->listeners_)
|
||||
listener->on_scan_end();
|
||||
this->set_scanner_state_(ScannerState::IDLE);
|
||||
}
|
||||
|
||||
void ESP32BLETracker::register_client(ESPBTClient *client) {
|
||||
@@ -392,19 +383,46 @@ void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t:
|
||||
void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param ¶m) {
|
||||
ESP_LOGV(TAG, "gap_scan_start_complete - status %d", param.status);
|
||||
this->scan_start_failed_ = param.status;
|
||||
if (this->scanner_state_ != ScannerState::STARTING) {
|
||||
if (this->scanner_state_ == ScannerState::RUNNING) {
|
||||
ESP_LOGE(TAG, "Scan was already running when start complete.");
|
||||
} else if (this->scanner_state_ == ScannerState::STOPPING) {
|
||||
ESP_LOGE(TAG, "Scan was stopping when start complete.");
|
||||
} else if (this->scanner_state_ == ScannerState::FAILED) {
|
||||
ESP_LOGE(TAG, "Scan was in failed state when start complete.");
|
||||
} else if (this->scanner_state_ == ScannerState::IDLE) {
|
||||
ESP_LOGE(TAG, "Scan was idle when start complete.");
|
||||
} else if (this->scanner_state_ == ScannerState::STOPPED) {
|
||||
ESP_LOGE(TAG, "Scan was stopped when start complete.");
|
||||
}
|
||||
}
|
||||
if (param.status == ESP_BT_STATUS_SUCCESS) {
|
||||
this->scan_start_fail_count_ = 0;
|
||||
this->set_scanner_state_(ScannerState::RUNNING);
|
||||
} else {
|
||||
this->set_scanner_state_(ScannerState::FAILED);
|
||||
if (this->scan_start_fail_count_ != std::numeric_limits<uint8_t>::max()) {
|
||||
this->scan_start_fail_count_++;
|
||||
}
|
||||
xSemaphoreGive(this->scan_end_lock_);
|
||||
}
|
||||
}
|
||||
|
||||
void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m) {
|
||||
ESP_LOGV(TAG, "gap_scan_stop_complete - status %d", param.status);
|
||||
xSemaphoreGive(this->scan_end_lock_);
|
||||
if (this->scanner_state_ != ScannerState::STOPPING) {
|
||||
if (this->scanner_state_ == ScannerState::RUNNING) {
|
||||
ESP_LOGE(TAG, "Scan was not running when stop complete.");
|
||||
} else if (this->scanner_state_ == ScannerState::STARTING) {
|
||||
ESP_LOGE(TAG, "Scan was not started when stop complete.");
|
||||
} else if (this->scanner_state_ == ScannerState::FAILED) {
|
||||
ESP_LOGE(TAG, "Scan was in failed state when stop complete.");
|
||||
} else if (this->scanner_state_ == ScannerState::IDLE) {
|
||||
ESP_LOGE(TAG, "Scan was idle when stop complete.");
|
||||
} else if (this->scanner_state_ == ScannerState::STOPPED) {
|
||||
ESP_LOGE(TAG, "Scan was stopped when stop complete.");
|
||||
}
|
||||
}
|
||||
this->set_scanner_state_(ScannerState::STOPPED);
|
||||
}
|
||||
|
||||
void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) {
|
||||
@@ -417,7 +435,21 @@ void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_re
|
||||
xSemaphoreGive(this->scan_result_lock_);
|
||||
}
|
||||
} else if (param.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) {
|
||||
xSemaphoreGive(this->scan_end_lock_);
|
||||
// Scan finished on its own
|
||||
if (this->scanner_state_ != ScannerState::RUNNING) {
|
||||
if (this->scanner_state_ == ScannerState::STOPPING) {
|
||||
ESP_LOGE(TAG, "Scan was not running when scan completed.");
|
||||
} else if (this->scanner_state_ == ScannerState::STARTING) {
|
||||
ESP_LOGE(TAG, "Scan was not started when scan completed.");
|
||||
} else if (this->scanner_state_ == ScannerState::FAILED) {
|
||||
ESP_LOGE(TAG, "Scan was in failed state when scan completed.");
|
||||
} else if (this->scanner_state_ == ScannerState::IDLE) {
|
||||
ESP_LOGE(TAG, "Scan was idle when scan completed.");
|
||||
} else if (this->scanner_state_ == ScannerState::STOPPED) {
|
||||
ESP_LOGE(TAG, "Scan was stopped when scan completed.");
|
||||
}
|
||||
}
|
||||
this->set_scanner_state_(ScannerState::STOPPED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,6 +460,11 @@ void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
|
||||
}
|
||||
}
|
||||
|
||||
void ESP32BLETracker::set_scanner_state_(ScannerState state) {
|
||||
this->scanner_state_ = state;
|
||||
this->scanner_state_callbacks_.call(state);
|
||||
}
|
||||
|
||||
ESPBLEiBeacon::ESPBLEiBeacon(const uint8_t *data) { memcpy(&this->beacon_data_, data, sizeof(beacon_data_)); }
|
||||
optional<ESPBLEiBeacon> ESPBLEiBeacon::from_manufacturer_data(const ServiceData &data) {
|
||||
if (!data.uuid.contains(0x4C, 0x00))
|
||||
@@ -680,8 +717,26 @@ void ESP32BLETracker::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " Scan Window: %.1f ms", this->scan_window_ * 0.625f);
|
||||
ESP_LOGCONFIG(TAG, " Scan Type: %s", this->scan_active_ ? "ACTIVE" : "PASSIVE");
|
||||
ESP_LOGCONFIG(TAG, " Continuous Scanning: %s", YESNO(this->scan_continuous_));
|
||||
ESP_LOGCONFIG(TAG, " Scanner Idle: %s", YESNO(this->scanner_idle_));
|
||||
ESP_LOGCONFIG(TAG, " Scan End: %s", YESNO(xSemaphoreGetMutexHolder(this->scan_end_lock_) == nullptr));
|
||||
switch (this->scanner_state_) {
|
||||
case ScannerState::IDLE:
|
||||
ESP_LOGCONFIG(TAG, " Scanner State: IDLE");
|
||||
break;
|
||||
case ScannerState::STARTING:
|
||||
ESP_LOGCONFIG(TAG, " Scanner State: STARTING");
|
||||
break;
|
||||
case ScannerState::RUNNING:
|
||||
ESP_LOGCONFIG(TAG, " Scanner State: RUNNING");
|
||||
break;
|
||||
case ScannerState::STOPPING:
|
||||
ESP_LOGCONFIG(TAG, " Scanner State: STOPPING");
|
||||
break;
|
||||
case ScannerState::STOPPED:
|
||||
ESP_LOGCONFIG(TAG, " Scanner State: STOPPED");
|
||||
break;
|
||||
case ScannerState::FAILED:
|
||||
ESP_LOGCONFIG(TAG, " Scanner State: FAILED");
|
||||
break;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Connecting: %d, discovered: %d, searching: %d, disconnecting: %d", connecting_, discovered_,
|
||||
searching_, disconnecting_);
|
||||
if (this->scan_start_fail_count_) {
|
||||
|
||||
@@ -154,6 +154,21 @@ enum class ClientState {
|
||||
ESTABLISHED,
|
||||
};
|
||||
|
||||
enum class ScannerState {
|
||||
// Scanner is idle, init state, set from the main loop when processing STOPPED
|
||||
IDLE,
|
||||
// Scanner is starting, set from the main loop only
|
||||
STARTING,
|
||||
// Scanner is running, set from the ESP callback only
|
||||
RUNNING,
|
||||
// Scanner failed to start, set from the ESP callback only
|
||||
FAILED,
|
||||
// Scanner is stopping, set from the main loop only
|
||||
STOPPING,
|
||||
// Scanner is stopped, set from the ESP callback only
|
||||
STOPPED,
|
||||
};
|
||||
|
||||
enum class ConnectionType {
|
||||
// The default connection type, we hold all the services in ram
|
||||
// for the duration of the connection.
|
||||
@@ -203,6 +218,7 @@ class ESP32BLETracker : public Component,
|
||||
void set_scan_interval(uint32_t scan_interval) { scan_interval_ = scan_interval; }
|
||||
void set_scan_window(uint32_t scan_window) { scan_window_ = scan_window; }
|
||||
void set_scan_active(bool scan_active) { scan_active_ = scan_active; }
|
||||
bool get_scan_active() const { return scan_active_; }
|
||||
void set_scan_continuous(bool scan_continuous) { scan_continuous_ = scan_continuous; }
|
||||
|
||||
/// Setup the FreeRTOS task and the Bluetooth stack.
|
||||
@@ -226,6 +242,11 @@ class ESP32BLETracker : public Component,
|
||||
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
|
||||
void ble_before_disabled_event_handler() override;
|
||||
|
||||
void add_scanner_state_callback(std::function<void(ScannerState)> &&callback) {
|
||||
this->scanner_state_callbacks_.add(std::move(callback));
|
||||
}
|
||||
ScannerState get_scanner_state() const { return this->scanner_state_; }
|
||||
|
||||
protected:
|
||||
void stop_scan_();
|
||||
/// Start a single scan by setting up the parameters and doing some esp-idf calls.
|
||||
@@ -240,6 +261,8 @@ class ESP32BLETracker : public Component,
|
||||
void gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param ¶m);
|
||||
/// Called when a `ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT` event is received.
|
||||
void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m);
|
||||
/// Called to set the scanner state. Will also call callbacks to let listeners know when state is changed.
|
||||
void set_scanner_state_(ScannerState state);
|
||||
|
||||
int app_id_{0};
|
||||
|
||||
@@ -257,12 +280,12 @@ class ESP32BLETracker : public Component,
|
||||
uint8_t scan_start_fail_count_{0};
|
||||
bool scan_continuous_;
|
||||
bool scan_active_;
|
||||
bool scanner_idle_{true};
|
||||
ScannerState scanner_state_{ScannerState::IDLE};
|
||||
CallbackManager<void(ScannerState)> scanner_state_callbacks_;
|
||||
bool ble_was_disabled_{true};
|
||||
bool raw_advertisements_{false};
|
||||
bool parse_advertisements_{false};
|
||||
SemaphoreHandle_t scan_result_lock_;
|
||||
SemaphoreHandle_t scan_end_lock_;
|
||||
size_t scan_result_index_{0};
|
||||
#ifdef USE_PSRAM
|
||||
const static u_int8_t SCAN_RESULT_BUFFER_SIZE = 32;
|
||||
|
||||
@@ -17,7 +17,7 @@ static const char *const TAG = "esp32_can";
|
||||
static bool get_bitrate(canbus::CanSpeed bitrate, twai_timing_config_t *t_config) {
|
||||
switch (bitrate) {
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H6)
|
||||
defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
case canbus::CAN_1KBPS:
|
||||
*t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_1KBITS();
|
||||
return true;
|
||||
|
||||
@@ -58,7 +58,7 @@ void ESP32RMTLEDStripLightOutput::setup() {
|
||||
channel.flags.io_loop_back = 0;
|
||||
channel.flags.io_od_mode = 0;
|
||||
channel.flags.invert_out = 0;
|
||||
channel.flags.with_dma = 0;
|
||||
channel.flags.with_dma = this->use_dma_;
|
||||
channel.intr_priority = 0;
|
||||
if (rmt_new_tx_channel(&channel, &this->channel_) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Channel creation failed");
|
||||
|
||||
@@ -51,6 +51,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
|
||||
void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; }
|
||||
void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; }
|
||||
void set_is_wrgb(bool is_wrgb) { this->is_wrgb_ = is_wrgb; }
|
||||
void set_use_dma(bool use_dma) { this->use_dma_ = use_dma; }
|
||||
void set_use_psram(bool use_psram) { this->use_psram_ = use_psram; }
|
||||
|
||||
/// Set a maximum refresh rate in µs as some lights do not like being updated too often.
|
||||
@@ -85,7 +86,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
|
||||
rmt_encoder_handle_t encoder_{nullptr};
|
||||
rmt_symbol_word_t *rmt_buf_{nullptr};
|
||||
rmt_symbol_word_t bit0_, bit1_, reset_;
|
||||
uint32_t rmt_symbols_;
|
||||
uint32_t rmt_symbols_{48};
|
||||
#else
|
||||
rmt_item32_t *rmt_buf_{nullptr};
|
||||
rmt_item32_t bit0_, bit1_, reset_;
|
||||
@@ -94,11 +95,12 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
|
||||
|
||||
uint8_t pin_;
|
||||
uint16_t num_leds_;
|
||||
bool is_rgbw_;
|
||||
bool is_wrgb_;
|
||||
bool use_psram_;
|
||||
bool is_rgbw_{false};
|
||||
bool is_wrgb_{false};
|
||||
bool use_dma_{false};
|
||||
bool use_psram_{false};
|
||||
|
||||
RGBOrder rgb_order_;
|
||||
RGBOrder rgb_order_{ORDER_RGB};
|
||||
|
||||
uint32_t last_refresh_{0};
|
||||
optional<uint32_t> max_refresh_rate_{};
|
||||
|
||||
@@ -3,7 +3,7 @@ import logging
|
||||
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32_rmt, light
|
||||
from esphome.components import esp32, esp32_rmt, light
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_CHIPSET,
|
||||
@@ -15,6 +15,7 @@ from esphome.const import (
|
||||
CONF_RGB_ORDER,
|
||||
CONF_RMT_CHANNEL,
|
||||
CONF_RMT_SYMBOLS,
|
||||
CONF_USE_DMA,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
|
||||
@@ -138,6 +139,11 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
|
||||
cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,
|
||||
cv.Optional(CONF_IS_WRGB, default=False): cv.boolean,
|
||||
cv.Optional(CONF_USE_DMA): cv.All(
|
||||
esp32.only_on_variant(supported=[esp32.const.VARIANT_ESP32S3]),
|
||||
cv.only_with_esp_idf,
|
||||
cv.boolean,
|
||||
),
|
||||
cv.Optional(CONF_USE_PSRAM, default=True): cv.boolean,
|
||||
cv.Inclusive(
|
||||
CONF_BIT0_HIGH,
|
||||
@@ -211,6 +217,8 @@ async def to_code(config):
|
||||
|
||||
if esp32_rmt.use_new_rmt_driver():
|
||||
cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS]))
|
||||
if CONF_USE_DMA in config:
|
||||
cg.add(var.set_use_dma(config[CONF_USE_DMA]))
|
||||
else:
|
||||
rmt_channel_t = cg.global_ns.enum("rmt_channel_t")
|
||||
cg.add(
|
||||
|
||||
@@ -40,9 +40,6 @@ async def new_fastled_light(config):
|
||||
if CONF_MAX_REFRESH_RATE in config:
|
||||
cg.add(var.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE]))
|
||||
|
||||
cg.add_library("fastled/FastLED", "3.9.16")
|
||||
await light.register_light(var, config)
|
||||
# https://github.com/FastLED/FastLED/blob/master/library.json
|
||||
# 3.3.3 has an issue on ESP32 with RMT and fastled_clockless:
|
||||
# https://github.com/esphome/issues/issues/1375
|
||||
cg.add_library("fastled/FastLED", "3.3.2")
|
||||
return var
|
||||
|
||||
@@ -34,7 +34,7 @@ void FastLEDLightOutput::write_state(light::LightState *state) {
|
||||
this->mark_shown_();
|
||||
|
||||
ESP_LOGVV(TAG, "Writing RGB values to bus...");
|
||||
this->controller_->showLeds();
|
||||
this->controller_->showLeds(this->state_parent_->current_values.get_brightness() * 255);
|
||||
}
|
||||
|
||||
} // namespace fastled_base
|
||||
|
||||
@@ -8,30 +8,45 @@ namespace esphome {
|
||||
namespace gpio_expander {
|
||||
|
||||
/// @brief A class to cache the read state of a GPIO expander.
|
||||
/// This class caches reads between GPIO Pins which are on the same bank.
|
||||
/// This means that for reading whole Port (ex. 8 pins) component needs only one
|
||||
/// I2C/SPI read per main loop call. It assumes, that one bit in byte identifies one GPIO pin
|
||||
/// Template parameters:
|
||||
/// T - Type which represents internal register. Could be uint8_t or uint16_t. Adjust to
|
||||
/// match size of your internal GPIO bank register.
|
||||
/// N - Number of pins
|
||||
template<typename T, T N> class CachedGpioExpander {
|
||||
public:
|
||||
bool digital_read(T pin) {
|
||||
if (!this->read_cache_invalidated_[pin]) {
|
||||
this->read_cache_invalidated_[pin] = true;
|
||||
return this->digital_read_cache(pin);
|
||||
uint8_t bank = pin / (sizeof(T) * BITS_PER_BYTE);
|
||||
if (this->read_cache_invalidated_[bank]) {
|
||||
this->read_cache_invalidated_[bank] = false;
|
||||
if (!this->digital_read_hw(pin))
|
||||
return false;
|
||||
}
|
||||
return this->digital_read_hw(pin);
|
||||
return this->digital_read_cache(pin);
|
||||
}
|
||||
|
||||
void digital_write(T pin, bool value) { this->digital_write_hw(pin, value); }
|
||||
|
||||
protected:
|
||||
/// @brief Call component low level function to read GPIO state from device
|
||||
virtual bool digital_read_hw(T pin) = 0;
|
||||
/// @brief Call component read function from internal cache.
|
||||
virtual bool digital_read_cache(T pin) = 0;
|
||||
/// @brief Call component low level function to write GPIO state to device
|
||||
virtual void digital_write_hw(T pin, bool value) = 0;
|
||||
const uint8_t cache_byte_size_ = N / (sizeof(T) * BITS_PER_BYTE);
|
||||
|
||||
/// @brief Invalidate cache. This function should be called in component loop().
|
||||
void reset_pin_cache_() {
|
||||
for (T i = 0; i < N; i++) {
|
||||
this->read_cache_invalidated_[i] = false;
|
||||
for (T i = 0; i < this->cache_byte_size_; i++) {
|
||||
this->read_cache_invalidated_[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::array<bool, N> read_cache_invalidated_{};
|
||||
static const uint8_t BITS_PER_BYTE = 8;
|
||||
std::array<bool, N / (sizeof(T) * BITS_PER_BYTE)> read_cache_invalidated_{};
|
||||
};
|
||||
|
||||
} // namespace gpio_expander
|
||||
|
||||
@@ -5,6 +5,7 @@ import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BORDER,
|
||||
CONF_COLOR,
|
||||
CONF_CONTINUOUS,
|
||||
CONF_DIRECTION,
|
||||
CONF_DURATION,
|
||||
CONF_HEIGHT,
|
||||
@@ -61,8 +62,6 @@ VALUE_POSITION_TYPE = {
|
||||
"BELOW": ValuePositionType.VALUE_POSITION_TYPE_BELOW,
|
||||
}
|
||||
|
||||
CONF_CONTINUOUS = "continuous"
|
||||
|
||||
GRAPH_TRACE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(GraphTrace),
|
||||
|
||||
@@ -18,6 +18,7 @@ MODELS = {
|
||||
"yac": Model.GREE_YAC,
|
||||
"yac1fb9": Model.GREE_YAC1FB9,
|
||||
"yx1ff": Model.GREE_YX1FF,
|
||||
"yag": Model.GREE_YAG,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
|
||||
|
||||
@@ -22,13 +22,21 @@ void GreeClimate::transmit_state() {
|
||||
remote_state[0] = this->fan_speed_() | this->operation_mode_();
|
||||
remote_state[1] = this->temperature_();
|
||||
|
||||
if (this->model_ == GREE_YAN || this->model_ == GREE_YX1FF) {
|
||||
if (this->model_ == GREE_YAN || this->model_ == GREE_YX1FF || this->model_ == GREE_YAG) {
|
||||
remote_state[2] = 0x60;
|
||||
remote_state[3] = 0x50;
|
||||
remote_state[4] = this->vertical_swing_();
|
||||
}
|
||||
|
||||
if (this->model_ == GREE_YAC) {
|
||||
if (this->model_ == GREE_YAG) {
|
||||
remote_state[5] = 0x40;
|
||||
|
||||
if (this->vertical_swing_() == GREE_VDIR_SWING || this->horizontal_swing_() == GREE_HDIR_SWING) {
|
||||
remote_state[0] |= (1 << 6);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->model_ == GREE_YAC || this->model_ == GREE_YAG) {
|
||||
remote_state[4] |= (this->horizontal_swing_() << 4);
|
||||
}
|
||||
|
||||
@@ -57,6 +65,12 @@ void GreeClimate::transmit_state() {
|
||||
// Calculate the checksum
|
||||
if (this->model_ == GREE_YAN || this->model_ == GREE_YX1FF) {
|
||||
remote_state[7] = ((remote_state[0] << 4) + (remote_state[1] << 4) + 0xC0);
|
||||
} else if (this->model_ == GREE_YAG) {
|
||||
remote_state[7] =
|
||||
((((remote_state[0] & 0x0F) + (remote_state[1] & 0x0F) + (remote_state[2] & 0x0F) + (remote_state[3] & 0x0F) +
|
||||
((remote_state[4] & 0xF0) >> 4) + ((remote_state[5] & 0xF0) >> 4) + ((remote_state[6] & 0xF0) >> 4) + 0x0A) &
|
||||
0x0F)
|
||||
<< 4);
|
||||
} else {
|
||||
remote_state[7] =
|
||||
((((remote_state[0] & 0x0F) + (remote_state[1] & 0x0F) + (remote_state[2] & 0x0F) + (remote_state[3] & 0x0F) +
|
||||
|
||||
@@ -58,7 +58,7 @@ const uint8_t GREE_VDIR_MIDDLE = 0x04;
|
||||
const uint8_t GREE_VDIR_MDOWN = 0x05;
|
||||
const uint8_t GREE_VDIR_DOWN = 0x06;
|
||||
|
||||
// Only available on YAC
|
||||
// Only available on YAC/YAG
|
||||
// Horizontal air directions. Note that these cannot be set on all heat pumps
|
||||
const uint8_t GREE_HDIR_AUTO = 0x00;
|
||||
const uint8_t GREE_HDIR_MANUAL = 0x00;
|
||||
@@ -78,7 +78,7 @@ const uint8_t GREE_PRESET_SLEEP = 0x01;
|
||||
const uint8_t GREE_PRESET_SLEEP_BIT = 0x80;
|
||||
|
||||
// Model codes
|
||||
enum Model { GREE_GENERIC, GREE_YAN, GREE_YAA, GREE_YAC, GREE_YAC1FB9, GREE_YX1FF };
|
||||
enum Model { GREE_GENERIC, GREE_YAN, GREE_YAA, GREE_YAC, GREE_YAC1FB9, GREE_YX1FF, GREE_YAG };
|
||||
|
||||
class GreeClimate : public climate_ir::ClimateIR {
|
||||
public:
|
||||
|
||||
@@ -69,7 +69,7 @@ void HLW8012Component::update() {
|
||||
|
||||
float power = cf_hz * this->power_multiplier_;
|
||||
|
||||
if (this->change_mode_at_ != 0) {
|
||||
if (this->change_mode_at_ != 0 || this->change_mode_every_ == 0) {
|
||||
// Only read cf1 after one cycle. Apparently it's quite unstable after being changed.
|
||||
if (this->current_mode_) {
|
||||
float current = cf1_hz * this->current_multiplier_;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
namespace esphome {
|
||||
namespace hm3301 {
|
||||
|
||||
static const uint8_t SELECT_COMM_CMD = 0X88;
|
||||
static const uint8_t SELECT_COMM_CMD = 0x88;
|
||||
|
||||
class HM3301Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
|
||||
@@ -10,9 +10,11 @@ from esphome.const import (
|
||||
CONF_TIMEOUT,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_URL,
|
||||
PLATFORM_HOST,
|
||||
__version__,
|
||||
)
|
||||
from esphome.core import CORE, Lambda
|
||||
from esphome.helpers import IS_MACOS
|
||||
|
||||
DEPENDENCIES = ["network"]
|
||||
AUTO_LOAD = ["json", "watchdog"]
|
||||
@@ -21,6 +23,7 @@ http_request_ns = cg.esphome_ns.namespace("http_request")
|
||||
HttpRequestComponent = http_request_ns.class_("HttpRequestComponent", cg.Component)
|
||||
HttpRequestArduino = http_request_ns.class_("HttpRequestArduino", HttpRequestComponent)
|
||||
HttpRequestIDF = http_request_ns.class_("HttpRequestIDF", HttpRequestComponent)
|
||||
HttpRequestHost = http_request_ns.class_("HttpRequestHost", HttpRequestComponent)
|
||||
|
||||
HttpContainer = http_request_ns.class_("HttpContainer")
|
||||
|
||||
@@ -43,10 +46,13 @@ CONF_REDIRECT_LIMIT = "redirect_limit"
|
||||
CONF_WATCHDOG_TIMEOUT = "watchdog_timeout"
|
||||
CONF_BUFFER_SIZE_RX = "buffer_size_rx"
|
||||
CONF_BUFFER_SIZE_TX = "buffer_size_tx"
|
||||
CONF_CA_CERTIFICATE_PATH = "ca_certificate_path"
|
||||
|
||||
CONF_MAX_RESPONSE_BUFFER_SIZE = "max_response_buffer_size"
|
||||
CONF_ON_RESPONSE = "on_response"
|
||||
CONF_HEADERS = "headers"
|
||||
CONF_REQUEST_HEADERS = "request_headers"
|
||||
CONF_COLLECT_HEADERS = "collect_headers"
|
||||
CONF_BODY = "body"
|
||||
CONF_JSON = "json"
|
||||
CONF_CAPTURE_RESPONSE = "capture_response"
|
||||
@@ -85,6 +91,8 @@ def validate_ssl_verification(config):
|
||||
|
||||
|
||||
def _declare_request_class(value):
|
||||
if CORE.is_host:
|
||||
return cv.declare_id(HttpRequestHost)(value)
|
||||
if CORE.using_esp_idf:
|
||||
return cv.declare_id(HttpRequestIDF)(value)
|
||||
if CORE.is_esp8266 or CORE.is_esp32 or CORE.is_rp2040:
|
||||
@@ -119,6 +127,10 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.SplitDefault(CONF_BUFFER_SIZE_TX, esp32_idf=512): cv.All(
|
||||
cv.uint16_t, cv.only_with_esp_idf
|
||||
),
|
||||
cv.Optional(CONF_CA_CERTIFICATE_PATH): cv.All(
|
||||
cv.file_,
|
||||
cv.only_on(PLATFORM_HOST),
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.require_framework_version(
|
||||
@@ -126,6 +138,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
esp32_arduino=cv.Version(0, 0, 0),
|
||||
esp_idf=cv.Version(0, 0, 0),
|
||||
rp2040_arduino=cv.Version(0, 0, 0),
|
||||
host=cv.Version(0, 0, 0),
|
||||
),
|
||||
validate_ssl_verification,
|
||||
)
|
||||
@@ -168,6 +181,21 @@ async def to_code(config):
|
||||
cg.add_library("ESP8266HTTPClient", None)
|
||||
if CORE.is_rp2040 and CORE.using_arduino:
|
||||
cg.add_library("HTTPClient", None)
|
||||
if CORE.is_host:
|
||||
if IS_MACOS:
|
||||
cg.add_build_flag("-I/opt/homebrew/opt/openssl/include")
|
||||
cg.add_build_flag("-L/opt/homebrew/opt/openssl/lib")
|
||||
cg.add_build_flag("-lssl")
|
||||
cg.add_build_flag("-lcrypto")
|
||||
cg.add_build_flag("-Wl,-framework,CoreFoundation")
|
||||
cg.add_build_flag("-Wl,-framework,Security")
|
||||
cg.add_define("CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN")
|
||||
cg.add_define("CPPHTTPLIB_OPENSSL_SUPPORT")
|
||||
elif path := config.get(CONF_CA_CERTIFICATE_PATH):
|
||||
cg.add_define("CPPHTTPLIB_OPENSSL_SUPPORT")
|
||||
cg.add(var.set_ca_path(path))
|
||||
cg.add_build_flag("-lssl")
|
||||
cg.add_build_flag("-lcrypto")
|
||||
|
||||
await cg.register_component(var, config)
|
||||
|
||||
@@ -176,9 +204,13 @@ HTTP_REQUEST_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(HttpRequestComponent),
|
||||
cv.Required(CONF_URL): cv.templatable(validate_url),
|
||||
cv.Optional(CONF_HEADERS): cv.All(
|
||||
cv.Optional(CONF_HEADERS): cv.invalid(
|
||||
"The 'headers' options has been renamed to 'request_headers'"
|
||||
),
|
||||
cv.Optional(CONF_REQUEST_HEADERS): cv.All(
|
||||
cv.Schema({cv.string: cv.templatable(cv.string)})
|
||||
),
|
||||
cv.Optional(CONF_COLLECT_HEADERS): cv.ensure_list(cv.string),
|
||||
cv.Optional(CONF_VERIFY_SSL): cv.invalid(
|
||||
f"{CONF_VERIFY_SSL} has moved to the base component configuration."
|
||||
),
|
||||
@@ -263,11 +295,12 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
|
||||
for key in json_:
|
||||
template_ = await cg.templatable(json_[key], args, cg.std_string)
|
||||
cg.add(var.add_json(key, template_))
|
||||
for key in config.get(CONF_HEADERS, []):
|
||||
template_ = await cg.templatable(
|
||||
config[CONF_HEADERS][key], args, cg.const_char_ptr
|
||||
)
|
||||
cg.add(var.add_header(key, template_))
|
||||
for key, value in config.get(CONF_REQUEST_HEADERS, {}).items():
|
||||
template_ = await cg.templatable(value, args, cg.const_char_ptr)
|
||||
cg.add(var.add_request_header(key, template_))
|
||||
|
||||
for value in config.get(CONF_COLLECT_HEADERS, []):
|
||||
cg.add(var.add_collect_header(value))
|
||||
|
||||
for conf in config.get(CONF_ON_RESPONSE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
|
||||
|
||||
@@ -20,5 +20,25 @@ void HttpRequestComponent::dump_config() {
|
||||
}
|
||||
}
|
||||
|
||||
std::string HttpContainer::get_response_header(const std::string &header_name) {
|
||||
auto response_headers = this->get_response_headers();
|
||||
auto header_name_lower_case = str_lower_case(header_name);
|
||||
if (response_headers.count(header_name_lower_case) == 0) {
|
||||
ESP_LOGW(TAG, "No header with name %s found", header_name_lower_case.c_str());
|
||||
return "";
|
||||
} else {
|
||||
auto values = response_headers[header_name_lower_case];
|
||||
if (values.empty()) {
|
||||
ESP_LOGE(TAG, "header with name %s returned an empty list, this shouldn't happen",
|
||||
header_name_lower_case.c_str());
|
||||
return "";
|
||||
} else {
|
||||
auto header_value = values.front();
|
||||
ESP_LOGD(TAG, "Header with name %s found with value %s", header_name_lower_case.c_str(), header_value.c_str());
|
||||
return header_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -95,9 +96,19 @@ class HttpContainer : public Parented<HttpRequestComponent> {
|
||||
|
||||
size_t get_bytes_read() const { return this->bytes_read_; }
|
||||
|
||||
/**
|
||||
* @brief Get response headers.
|
||||
*
|
||||
* @return The key is the lower case response header name, the value is the header value.
|
||||
*/
|
||||
std::map<std::string, std::list<std::string>> get_response_headers() { return this->response_headers_; }
|
||||
|
||||
std::string get_response_header(const std::string &header_name);
|
||||
|
||||
protected:
|
||||
size_t bytes_read_{0};
|
||||
bool secure_{false};
|
||||
std::map<std::string, std::list<std::string>> response_headers_{};
|
||||
};
|
||||
|
||||
class HttpRequestResponseTrigger : public Trigger<std::shared_ptr<HttpContainer>, std::string &> {
|
||||
@@ -119,21 +130,46 @@ class HttpRequestComponent : public Component {
|
||||
void set_follow_redirects(bool follow_redirects) { this->follow_redirects_ = follow_redirects; }
|
||||
void set_redirect_limit(uint16_t limit) { this->redirect_limit_ = limit; }
|
||||
|
||||
std::shared_ptr<HttpContainer> get(std::string url) { return this->start(std::move(url), "GET", "", {}); }
|
||||
std::shared_ptr<HttpContainer> get(std::string url, std::list<Header> headers) {
|
||||
return this->start(std::move(url), "GET", "", std::move(headers));
|
||||
std::shared_ptr<HttpContainer> get(const std::string &url) { return this->start(url, "GET", "", {}); }
|
||||
std::shared_ptr<HttpContainer> get(const std::string &url, const std::list<Header> &request_headers) {
|
||||
return this->start(url, "GET", "", request_headers);
|
||||
}
|
||||
std::shared_ptr<HttpContainer> post(std::string url, std::string body) {
|
||||
return this->start(std::move(url), "POST", std::move(body), {});
|
||||
std::shared_ptr<HttpContainer> get(const std::string &url, const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &collect_headers) {
|
||||
return this->start(url, "GET", "", request_headers, collect_headers);
|
||||
}
|
||||
std::shared_ptr<HttpContainer> post(std::string url, std::string body, std::list<Header> headers) {
|
||||
return this->start(std::move(url), "POST", std::move(body), std::move(headers));
|
||||
std::shared_ptr<HttpContainer> post(const std::string &url, const std::string &body) {
|
||||
return this->start(url, "POST", body, {});
|
||||
}
|
||||
std::shared_ptr<HttpContainer> post(const std::string &url, const std::string &body,
|
||||
const std::list<Header> &request_headers) {
|
||||
return this->start(url, "POST", body, request_headers);
|
||||
}
|
||||
std::shared_ptr<HttpContainer> post(const std::string &url, const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &collect_headers) {
|
||||
return this->start(url, "POST", body, request_headers, collect_headers);
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body,
|
||||
std::list<Header> headers) = 0;
|
||||
std::shared_ptr<HttpContainer> start(const std::string &url, const std::string &method, const std::string &body,
|
||||
const std::list<Header> &request_headers) {
|
||||
return this->start(url, method, body, request_headers, {});
|
||||
}
|
||||
|
||||
std::shared_ptr<HttpContainer> start(const std::string &url, const std::string &method, const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &collect_headers) {
|
||||
std::set<std::string> lower_case_collect_headers;
|
||||
for (const std::string &collect_header : collect_headers) {
|
||||
lower_case_collect_headers.insert(str_lower_case(collect_header));
|
||||
}
|
||||
return this->perform(url, method, body, request_headers, lower_case_collect_headers);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual std::shared_ptr<HttpContainer> perform(std::string url, std::string method, std::string body,
|
||||
std::list<Header> request_headers,
|
||||
std::set<std::string> collect_headers) = 0;
|
||||
const char *useragent_{nullptr};
|
||||
bool follow_redirects_{};
|
||||
uint16_t redirect_limit_{};
|
||||
@@ -149,7 +185,11 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
||||
TEMPLATABLE_VALUE(std::string, body)
|
||||
TEMPLATABLE_VALUE(bool, capture_response)
|
||||
|
||||
void add_header(const char *key, TemplatableValue<const char *, Ts...> value) { this->headers_.insert({key, value}); }
|
||||
void add_request_header(const char *key, TemplatableValue<const char *, Ts...> value) {
|
||||
this->request_headers_.insert({key, value});
|
||||
}
|
||||
|
||||
void add_collect_header(const char *value) { this->collect_headers_.insert(value); }
|
||||
|
||||
void add_json(const char *key, TemplatableValue<std::string, Ts...> value) { this->json_.insert({key, value}); }
|
||||
|
||||
@@ -176,16 +216,17 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
||||
auto f = std::bind(&HttpRequestSendAction<Ts...>::encode_json_func_, this, x..., std::placeholders::_1);
|
||||
body = json::build_json(f);
|
||||
}
|
||||
std::list<Header> headers;
|
||||
for (const auto &item : this->headers_) {
|
||||
std::list<Header> request_headers;
|
||||
for (const auto &item : this->request_headers_) {
|
||||
auto val = item.second;
|
||||
Header header;
|
||||
header.name = item.first;
|
||||
header.value = val.value(x...);
|
||||
headers.push_back(header);
|
||||
request_headers.push_back(header);
|
||||
}
|
||||
|
||||
auto container = this->parent_->start(this->url_.value(x...), this->method_.value(x...), body, headers);
|
||||
auto container = this->parent_->start(this->url_.value(x...), this->method_.value(x...), body, request_headers,
|
||||
this->collect_headers_);
|
||||
|
||||
if (container == nullptr) {
|
||||
for (auto *trigger : this->error_triggers_)
|
||||
@@ -238,7 +279,8 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
||||
}
|
||||
void encode_json_func_(Ts... x, JsonObject root) { this->json_func_(x..., root); }
|
||||
HttpRequestComponent *parent_;
|
||||
std::map<const char *, TemplatableValue<const char *, Ts...>> headers_{};
|
||||
std::map<const char *, TemplatableValue<const char *, Ts...>> request_headers_{};
|
||||
std::set<std::string> collect_headers_{"content-type", "content-length"};
|
||||
std::map<const char *, TemplatableValue<std::string, Ts...>> json_{};
|
||||
std::function<void(Ts..., JsonObject)> json_func_{nullptr};
|
||||
std::vector<HttpRequestResponseTrigger *> response_triggers_{};
|
||||
|
||||
@@ -14,8 +14,9 @@ namespace http_request {
|
||||
|
||||
static const char *const TAG = "http_request.arduino";
|
||||
|
||||
std::shared_ptr<HttpContainer> HttpRequestArduino::start(std::string url, std::string method, std::string body,
|
||||
std::list<Header> headers) {
|
||||
std::shared_ptr<HttpContainer> HttpRequestArduino::perform(std::string url, std::string method, std::string body,
|
||||
std::list<Header> request_headers,
|
||||
std::set<std::string> collect_headers) {
|
||||
if (!network::is_connected()) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");
|
||||
@@ -95,14 +96,17 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::start(std::string url, std::s
|
||||
if (this->useragent_ != nullptr) {
|
||||
container->client_.setUserAgent(this->useragent_);
|
||||
}
|
||||
for (const auto &header : headers) {
|
||||
for (const auto &header : request_headers) {
|
||||
container->client_.addHeader(header.name.c_str(), header.value.c_str(), false, true);
|
||||
}
|
||||
|
||||
// returned needed headers must be collected before the requests
|
||||
static const char *header_keys[] = {"Content-Length", "Content-Type"};
|
||||
static const size_t HEADER_COUNT = sizeof(header_keys) / sizeof(header_keys[0]);
|
||||
container->client_.collectHeaders(header_keys, HEADER_COUNT);
|
||||
const char *header_keys[collect_headers.size()];
|
||||
int index = 0;
|
||||
for (auto const &header_name : collect_headers) {
|
||||
header_keys[index++] = header_name.c_str();
|
||||
}
|
||||
container->client_.collectHeaders(header_keys, index);
|
||||
|
||||
App.feed_wdt();
|
||||
container->status_code = container->client_.sendRequest(method.c_str(), body.c_str());
|
||||
@@ -121,6 +125,18 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::start(std::string url, std::s
|
||||
// Still return the container, so it can be used to get the status code and error message
|
||||
}
|
||||
|
||||
container->response_headers_ = {};
|
||||
auto header_count = container->client_.headers();
|
||||
for (int i = 0; i < header_count; i++) {
|
||||
const std::string header_name = str_lower_case(container->client_.headerName(i).c_str());
|
||||
if (collect_headers.count(header_name) > 0) {
|
||||
std::string header_value = container->client_.header(i).c_str();
|
||||
ESP_LOGD(TAG, "Received response header, name: %s, value: %s", header_name.c_str(), header_value.c_str());
|
||||
container->response_headers_[header_name].push_back(header_value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int content_length = container->client_.getSize();
|
||||
ESP_LOGD(TAG, "Content-Length: %d", content_length);
|
||||
container->content_length = (size_t) content_length;
|
||||
|
||||
@@ -29,9 +29,10 @@ class HttpContainerArduino : public HttpContainer {
|
||||
};
|
||||
|
||||
class HttpRequestArduino : public HttpRequestComponent {
|
||||
public:
|
||||
std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body,
|
||||
std::list<Header> headers) override;
|
||||
protected:
|
||||
std::shared_ptr<HttpContainer> perform(std::string url, std::string method, std::string body,
|
||||
std::list<Header> request_headers,
|
||||
std::set<std::string> collect_headers) override;
|
||||
};
|
||||
|
||||
} // namespace http_request
|
||||
|
||||
141
esphome/components/http_request/http_request_host.cpp
Normal file
141
esphome/components/http_request/http_request_host.cpp
Normal file
@@ -0,0 +1,141 @@
|
||||
#include "http_request_host.h"
|
||||
|
||||
#ifdef USE_HOST
|
||||
|
||||
#include <regex>
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/components/watchdog/watchdog.h"
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
||||
static const char *const TAG = "http_request.host";
|
||||
|
||||
std::shared_ptr<HttpContainer> HttpRequestHost::perform(std::string url, std::string method, std::string body,
|
||||
std::list<Header> request_headers,
|
||||
std::set<std::string> response_headers) {
|
||||
if (!network::is_connected()) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::regex url_regex(R"(^(([^:\/?#]+):)?(//([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?)", std::regex::extended);
|
||||
std::smatch url_match_result;
|
||||
|
||||
if (!std::regex_match(url, url_match_result, url_regex) || url_match_result.length() < 7) {
|
||||
ESP_LOGE(TAG, "HTTP Request failed; Malformed URL: %s", url.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
auto host = url_match_result[4].str();
|
||||
auto scheme_host = url_match_result[1].str() + url_match_result[3].str();
|
||||
auto path = url_match_result[5].str() + url_match_result[6].str();
|
||||
if (path.empty())
|
||||
path = "/";
|
||||
|
||||
std::shared_ptr<HttpContainerHost> container = std::make_shared<HttpContainerHost>();
|
||||
container->set_parent(this);
|
||||
|
||||
const uint32_t start = millis();
|
||||
|
||||
watchdog::WatchdogManager wdm(this->get_watchdog_timeout());
|
||||
|
||||
httplib::Headers h_headers;
|
||||
h_headers.emplace("Host", host.c_str());
|
||||
h_headers.emplace("User-Agent", this->useragent_);
|
||||
for (const auto &[name, value] : request_headers) {
|
||||
h_headers.emplace(name, value);
|
||||
}
|
||||
httplib::Client client(scheme_host.c_str());
|
||||
if (!client.is_valid()) {
|
||||
ESP_LOGE(TAG, "HTTP Request failed; Invalid URL: %s", url.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
client.set_follow_location(this->follow_redirects_);
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
if (this->ca_path_ != nullptr)
|
||||
client.set_ca_cert_path(this->ca_path_);
|
||||
#endif
|
||||
|
||||
httplib::Result result;
|
||||
if (method == "GET") {
|
||||
result = client.Get(path, h_headers, [&](const char *data, size_t data_length) {
|
||||
ESP_LOGV(TAG, "Got data length: %zu", data_length);
|
||||
container->response_body_.insert(container->response_body_.end(), (const uint8_t *) data,
|
||||
(const uint8_t *) data + data_length);
|
||||
return true;
|
||||
});
|
||||
} else if (method == "HEAD") {
|
||||
result = client.Head(path, h_headers);
|
||||
} else if (method == "PUT") {
|
||||
result = client.Put(path, h_headers, body, "");
|
||||
if (result) {
|
||||
auto data = std::vector<uint8_t>(result->body.begin(), result->body.end());
|
||||
container->response_body_.insert(container->response_body_.end(), data.begin(), data.end());
|
||||
}
|
||||
} else if (method == "PATCH") {
|
||||
result = client.Patch(path, h_headers, body, "");
|
||||
if (result) {
|
||||
auto data = std::vector<uint8_t>(result->body.begin(), result->body.end());
|
||||
container->response_body_.insert(container->response_body_.end(), data.begin(), data.end());
|
||||
}
|
||||
} else if (method == "POST") {
|
||||
result = client.Post(path, h_headers, body, "");
|
||||
if (result) {
|
||||
auto data = std::vector<uint8_t>(result->body.begin(), result->body.end());
|
||||
container->response_body_.insert(container->response_body_.end(), data.begin(), data.end());
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "HTTP Request failed - unsupported method %s; URL: %s", method.c_str(), url.c_str());
|
||||
container->end();
|
||||
return nullptr;
|
||||
}
|
||||
App.feed_wdt();
|
||||
if (!result) {
|
||||
ESP_LOGW(TAG, "HTTP Request failed; URL: %s, error code: %u", url.c_str(), (unsigned) result.error());
|
||||
container->end();
|
||||
this->status_momentary_error("failed", 1000);
|
||||
return nullptr;
|
||||
}
|
||||
App.feed_wdt();
|
||||
auto response = *result;
|
||||
container->status_code = response.status;
|
||||
if (!is_success(response.status)) {
|
||||
ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), response.status);
|
||||
this->status_momentary_error("failed", 1000);
|
||||
// Still return the container, so it can be used to get the status code and error message
|
||||
}
|
||||
|
||||
container->content_length = container->response_body_.size();
|
||||
for (auto header : response.headers) {
|
||||
ESP_LOGD(TAG, "Header: %s: %s", header.first.c_str(), header.second.c_str());
|
||||
auto lower_name = str_lower_case(header.first);
|
||||
if (response_headers.find(lower_name) != response_headers.end()) {
|
||||
container->response_headers_[lower_name].emplace_back(header.second);
|
||||
}
|
||||
}
|
||||
container->duration_ms = millis() - start;
|
||||
return container;
|
||||
}
|
||||
|
||||
int HttpContainerHost::read(uint8_t *buf, size_t max_len) {
|
||||
auto bytes_remaining = this->response_body_.size() - this->bytes_read_;
|
||||
auto read_len = std::min(max_len, bytes_remaining);
|
||||
memcpy(buf, this->response_body_.data() + this->bytes_read_, read_len);
|
||||
this->bytes_read_ += read_len;
|
||||
return read_len;
|
||||
}
|
||||
|
||||
void HttpContainerHost::end() {
|
||||
watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
|
||||
this->response_body_ = std::vector<uint8_t>();
|
||||
this->bytes_read_ = 0;
|
||||
}
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_HOST
|
||||
37
esphome/components/http_request/http_request_host.h
Normal file
37
esphome/components/http_request/http_request_host.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "http_request.h"
|
||||
|
||||
#ifdef USE_HOST
|
||||
|
||||
#define CPPHTTPLIB_NO_EXCEPTIONS
|
||||
#include "httplib.h"
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
||||
class HttpRequestHost;
|
||||
class HttpContainerHost : public HttpContainer {
|
||||
public:
|
||||
int read(uint8_t *buf, size_t max_len) override;
|
||||
void end() override;
|
||||
|
||||
protected:
|
||||
friend class HttpRequestHost;
|
||||
std::vector<uint8_t> response_body_{};
|
||||
};
|
||||
|
||||
class HttpRequestHost : public HttpRequestComponent {
|
||||
public:
|
||||
std::shared_ptr<HttpContainer> perform(std::string url, std::string method, std::string body,
|
||||
std::list<Header> request_headers,
|
||||
std::set<std::string> response_headers) override;
|
||||
void set_ca_path(const char *ca_path) { this->ca_path_ = ca_path; }
|
||||
|
||||
protected:
|
||||
const char *ca_path_{};
|
||||
};
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_HOST
|
||||
@@ -19,14 +19,41 @@ namespace http_request {
|
||||
|
||||
static const char *const TAG = "http_request.idf";
|
||||
|
||||
struct UserData {
|
||||
const std::set<std::string> &collect_headers;
|
||||
std::map<std::string, std::list<std::string>> response_headers;
|
||||
};
|
||||
|
||||
void HttpRequestIDF::dump_config() {
|
||||
HttpRequestComponent::dump_config();
|
||||
ESP_LOGCONFIG(TAG, " Buffer Size RX: %u", this->buffer_size_rx_);
|
||||
ESP_LOGCONFIG(TAG, " Buffer Size TX: %u", this->buffer_size_tx_);
|
||||
}
|
||||
|
||||
std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::string method, std::string body,
|
||||
std::list<Header> headers) {
|
||||
esp_err_t HttpRequestIDF::http_event_handler(esp_http_client_event_t *evt) {
|
||||
UserData *user_data = (UserData *) evt->user_data;
|
||||
|
||||
switch (evt->event_id) {
|
||||
case HTTP_EVENT_ON_HEADER: {
|
||||
const std::string header_name = str_lower_case(evt->header_key);
|
||||
if (user_data->collect_headers.count(header_name)) {
|
||||
const std::string header_value = evt->header_value;
|
||||
ESP_LOGD(TAG, "Received response header, name: %s, value: %s", header_name.c_str(), header_value.c_str());
|
||||
user_data->response_headers[header_name].push_back(header_value);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
std::shared_ptr<HttpContainer> HttpRequestIDF::perform(std::string url, std::string method, std::string body,
|
||||
std::list<Header> request_headers,
|
||||
std::set<std::string> collect_headers) {
|
||||
if (!network::is_connected()) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
ESP_LOGE(TAG, "HTTP Request failed; Not connected to network");
|
||||
@@ -76,6 +103,10 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin
|
||||
const uint32_t start = millis();
|
||||
watchdog::WatchdogManager wdm(this->get_watchdog_timeout());
|
||||
|
||||
config.event_handler = http_event_handler;
|
||||
auto user_data = UserData{collect_headers, {}};
|
||||
config.user_data = static_cast<void *>(&user_data);
|
||||
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
|
||||
std::shared_ptr<HttpContainerIDF> container = std::make_shared<HttpContainerIDF>(client);
|
||||
@@ -83,7 +114,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin
|
||||
|
||||
container->set_secure(secure);
|
||||
|
||||
for (const auto &header : headers) {
|
||||
for (const auto &header : request_headers) {
|
||||
esp_http_client_set_header(client, header.name.c_str(), header.value.c_str());
|
||||
}
|
||||
|
||||
@@ -124,6 +155,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin
|
||||
container->feed_wdt();
|
||||
container->status_code = esp_http_client_get_status_code(client);
|
||||
container->feed_wdt();
|
||||
container->set_response_headers(user_data.response_headers);
|
||||
if (is_success(container->status_code)) {
|
||||
container->duration_ms = millis() - start;
|
||||
return container;
|
||||
|
||||
@@ -21,6 +21,10 @@ class HttpContainerIDF : public HttpContainer {
|
||||
/// @brief Feeds the watchdog timer if the executing task has one attached
|
||||
void feed_wdt();
|
||||
|
||||
void set_response_headers(std::map<std::string, std::list<std::string>> &response_headers) {
|
||||
this->response_headers_ = std::move(response_headers);
|
||||
}
|
||||
|
||||
protected:
|
||||
esp_http_client_handle_t client_;
|
||||
};
|
||||
@@ -29,16 +33,19 @@ class HttpRequestIDF : public HttpRequestComponent {
|
||||
public:
|
||||
void dump_config() override;
|
||||
|
||||
std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body,
|
||||
std::list<Header> headers) override;
|
||||
|
||||
void set_buffer_size_rx(uint16_t buffer_size_rx) { this->buffer_size_rx_ = buffer_size_rx; }
|
||||
void set_buffer_size_tx(uint16_t buffer_size_tx) { this->buffer_size_tx_ = buffer_size_tx; }
|
||||
|
||||
protected:
|
||||
std::shared_ptr<HttpContainer> perform(std::string url, std::string method, std::string body,
|
||||
std::list<Header> request_headers,
|
||||
std::set<std::string> collect_headers) override;
|
||||
// if zero ESP-IDF will use DEFAULT_HTTP_BUF_SIZE
|
||||
uint16_t buffer_size_rx_{};
|
||||
uint16_t buffer_size_tx_{};
|
||||
|
||||
/// @brief Monitors the http client events to gather response headers
|
||||
static esp_err_t http_event_handler(esp_http_client_event_t *evt);
|
||||
};
|
||||
|
||||
} // namespace http_request
|
||||
|
||||
9691
esphome/components/http_request/httplib.h
Normal file
9691
esphome/components/http_request/httplib.h
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user