mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 16:54:29 +00:00
Compare commits
166 Commits
store-yaml
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
faabafad2b | ||
|
|
cce7cfff29 | ||
|
|
77a91853be | ||
|
|
73dbc8214b | ||
|
|
7d7cdb6c66 | ||
|
|
0d7130c499 | ||
|
|
1d5d581734 | ||
|
|
d0e3e98d55 | ||
|
|
c4abc5476e | ||
|
|
6c10fc1272 | ||
|
|
036768c399 | ||
|
|
921758f87d | ||
|
|
c6ead57a9e | ||
|
|
78c6131bbf | ||
|
|
d8f883bd9d | ||
|
|
d1d77fc217 | ||
|
|
f273221cf4 | ||
|
|
03121d2efe | ||
|
|
7c2603d9bc | ||
|
|
21aee91e67 | ||
|
|
1bd937d89c | ||
|
|
63d8a344c5 | ||
|
|
dbdf125ec8 | ||
|
|
9ab2a573ab | ||
|
|
99d1c4eb69 | ||
|
|
b079be756f | ||
|
|
039a1f063e | ||
|
|
2354165e41 | ||
|
|
f5697b0ae5 | ||
|
|
fe794a26e8 | ||
|
|
8d77051b9a | ||
|
|
9609d370c0 | ||
|
|
59711b8e6a | ||
|
|
d77c0d2bc5 | ||
|
|
657d9bf4d0 | ||
|
|
3fb250133f | ||
|
|
d8bd80ef38 | ||
|
|
db6bd36cf9 | ||
|
|
f57d31374e | ||
|
|
50994704a3 | ||
|
|
350e7bb763 | ||
|
|
4ae6dc355f | ||
|
|
6a79dfb5c5 | ||
|
|
1dbd9af617 | ||
|
|
995eba9178 | ||
|
|
9534ab2a19 | ||
|
|
1b1c8d767d | ||
|
|
e3d68deef9 | ||
|
|
20cd6a1771 | ||
|
|
d27229a1c7 | ||
|
|
129aebe8f4 | ||
|
|
a84ad7b1f8 | ||
|
|
86096b96f5 | ||
|
|
ac5a28301a | ||
|
|
a497174da2 | ||
|
|
b97182d302 | ||
|
|
8e7518fe9d | ||
|
|
a0f546e375 | ||
|
|
53e85e07d4 | ||
|
|
f6c78f7415 | ||
|
|
19cca9e177 | ||
|
|
1a553018bf | ||
|
|
a39505f5ef | ||
|
|
14e89f3dae | ||
|
|
bf12af4645 | ||
|
|
1753ccd811 | ||
|
|
69f905f154 | ||
|
|
92028e53b5 | ||
|
|
11deff2bed | ||
|
|
2b38e4b7e2 | ||
|
|
c63bed8c21 | ||
|
|
3b2564bbf3 | ||
|
|
26c42af354 | ||
|
|
9ace0ffb26 | ||
|
|
d4b6426087 | ||
|
|
bd9375117a | ||
|
|
c2784c9fd8 | ||
|
|
3a1a8a8955 | ||
|
|
b6763cfaed | ||
|
|
f76dfd579c | ||
|
|
4b8568e948 | ||
|
|
ac6a0f34ec | ||
|
|
c214a8ce79 | ||
|
|
e3f164fff2 | ||
|
|
c9095841ae | ||
|
|
7cb6cf2f2a | ||
|
|
34844da668 | ||
|
|
e2157a3d26 | ||
|
|
d934fb3910 | ||
|
|
c4076ec8a9 | ||
|
|
9ac22f9244 | ||
|
|
77a99bceb2 | ||
|
|
ae7c800de8 | ||
|
|
9e7b3e0330 | ||
|
|
084398436d | ||
|
|
2abe272867 | ||
|
|
6d9490b5a3 | ||
|
|
db6b9166f4 | ||
|
|
7ab95ddcb1 | ||
|
|
cdd2bfbc60 | ||
|
|
41f7f8cccb | ||
|
|
045de436ba | ||
|
|
24e276c3f9 | ||
|
|
0f5defa67e | ||
|
|
900e0b8566 | ||
|
|
40d0cbee3f | ||
|
|
009c6dd995 | ||
|
|
e80461eba9 | ||
|
|
29e8949e3e | ||
|
|
ce11d38c9b | ||
|
|
f62d804a60 | ||
|
|
9e768bb510 | ||
|
|
53fd99578a | ||
|
|
310baab524 | ||
|
|
0422b581cb | ||
|
|
0ce89c17ab | ||
|
|
66be793cd8 | ||
|
|
1d38498ca7 | ||
|
|
aef9b5b72f | ||
|
|
9bf35ab8fb | ||
|
|
33ace9d698 | ||
|
|
32ab3abd7c | ||
|
|
930cf2b5b9 | ||
|
|
d8fa0e4140 | ||
|
|
b09a5f9e43 | ||
|
|
bb6cd97948 | ||
|
|
73f839437e | ||
|
|
a7a407c22c | ||
|
|
7a2657cea1 | ||
|
|
3420cff316 | ||
|
|
963465a0a6 | ||
|
|
1ee49720c7 | ||
|
|
c1a7a8ff55 | ||
|
|
f1fd5f2f49 | ||
|
|
8d2c2e6adc | ||
|
|
94b248527d | ||
|
|
a46aa594b3 | ||
|
|
99425e3a97 | ||
|
|
f83e3ad6a6 | ||
|
|
c768e2eabc | ||
|
|
9ffd350095 | ||
|
|
26ccaf70db | ||
|
|
20925b3220 | ||
|
|
83504d2de2 | ||
|
|
efebea3296 | ||
|
|
e191fc5d47 | ||
|
|
1e5771a3fa | ||
|
|
5b7f8cf90d | ||
|
|
35e5c7c7c3 | ||
|
|
10ce6024bf | ||
|
|
bf6c8568d3 | ||
|
|
88084f2ec7 | ||
|
|
6ef35b6d3d | ||
|
|
28dd935359 | ||
|
|
750cf1995b | ||
|
|
a56b6d8993 | ||
|
|
acbb662316 | ||
|
|
abf6212a5a | ||
|
|
6a527c7efc | ||
|
|
4dbc5ce920 | ||
|
|
e0b0c1e8d3 | ||
|
|
92c82f3d25 | ||
|
|
77009cfafe | ||
|
|
cd7e54dbf2 | ||
|
|
29a79b1373 | ||
|
|
dafc3560dd |
@@ -1 +0,0 @@
|
||||
442b8197be00e6fee6b1b64b07a0e3b3558188fddf1d9c510565da884687c451
|
||||
7
.github/actions/build-image/action.yaml
vendored
7
.github/actions/build-image/action.yaml
vendored
@@ -15,11 +15,6 @@ 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:
|
||||
@@ -60,7 +55,6 @@ runs:
|
||||
build-args: |
|
||||
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
|
||||
|
||||
@@ -86,7 +80,6 @@ runs:
|
||||
build-args: |
|
||||
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
|
||||
|
||||
|
||||
14
.github/actions/cache-esp-idf/action.yml
vendored
14
.github/actions/cache-esp-idf/action.yml
vendored
@@ -2,8 +2,8 @@ name: Cache ESP-IDF
|
||||
description: >
|
||||
Resolve the pinned ESP-IDF version and cache the native ESP-IDF install
|
||||
(toolchains + source) at ~/.esphome-idf. Every job that installs ESP-IDF
|
||||
natively (clang-tidy for IDF/Arduino and the native-IDF component build)
|
||||
shares one cache, since the install is identical (ESPHOME_IDF_DEFAULT_TARGETS
|
||||
natively (clang-tidy for IDF/Arduino and the component test batches) shares
|
||||
one cache, since the install is identical (ESPHOME_IDF_DEFAULT_TARGETS
|
||||
defaults to "all", so all toolchains are present regardless of the chip).
|
||||
Callers must set env ESPHOME_ESP_IDF_PREFIX: ~/.esphome-idf and have the
|
||||
Python venv already restored.
|
||||
@@ -11,6 +11,12 @@ inputs:
|
||||
framework:
|
||||
description: 'Which pinned IDF version to key on: "espidf" (recommended) or "arduino".'
|
||||
default: espidf
|
||||
restore-only:
|
||||
description: >
|
||||
When "true", only restore -- never save the cache, even on dev. Use from
|
||||
jobs that may not produce an ESP-IDF install (e.g. a component batch with
|
||||
no esp32 target), so a partial/empty install is never written to the key.
|
||||
default: "false"
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
@@ -33,13 +39,13 @@ runs:
|
||||
# PRs), and PRs are restore-only -- they never push multi-GB artifacts into
|
||||
# their own scope / the repo quota (e.g. on a version-bump PR).
|
||||
- name: Cache ESP-IDF install (write on dev)
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
if: github.ref == 'refs/heads/dev' && inputs.restore-only != 'true'
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: ~/.esphome-idf
|
||||
key: ${{ runner.os }}-esphome-idf-${{ steps.version.outputs.version }}
|
||||
- name: Cache ESP-IDF install (restore-only off dev)
|
||||
if: github.ref != 'refs/heads/dev'
|
||||
if: github.ref != 'refs/heads/dev' || inputs.restore-only == 'true'
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: ~/.esphome-idf
|
||||
|
||||
1
.github/scripts/detect-tags.js
vendored
1
.github/scripts/detect-tags.js
vendored
@@ -41,7 +41,6 @@ function hasCoreChanges(changedFiles) {
|
||||
*/
|
||||
function hasDashboardChanges(changedFiles) {
|
||||
return changedFiles.some(file =>
|
||||
file.startsWith('esphome/dashboard/') ||
|
||||
file.startsWith('esphome/components/dashboard_import/')
|
||||
);
|
||||
}
|
||||
|
||||
2
.github/workflows/auto-label-pr.yml
vendored
2
.github/workflows/auto-label-pr.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
if: github.event.pull_request.state == 'open' && (github.event.action != 'labeled' || github.event.sender.type != 'Bot')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
|
||||
2
.github/workflows/ci-api-proto.yml
vendored
2
.github/workflows/ci-api-proto.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
|
||||
76
.github/workflows/ci-clang-tidy-hash.yml
vendored
76
.github/workflows/ci-clang-tidy-hash.yml
vendored
@@ -1,76 +0,0 @@
|
||||
name: Clang-tidy Hash CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- ".clang-tidy"
|
||||
- "platformio.ini"
|
||||
- "requirements_dev.txt"
|
||||
- "sdkconfig.defaults"
|
||||
- ".clang-tidy.hash"
|
||||
- "script/clang_tidy_hash.py"
|
||||
- ".github/workflows/ci-clang-tidy-hash.yml"
|
||||
|
||||
permissions:
|
||||
contents: read # actions/checkout for the PR head
|
||||
pull-requests: write # pulls.createReview / listReviews / dismissReview when the clang-tidy hash is out of date
|
||||
|
||||
jobs:
|
||||
verify-hash:
|
||||
name: Verify clang-tidy hash
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Verify hash
|
||||
run: |
|
||||
python script/clang_tidy_hash.py --verify
|
||||
|
||||
- if: failure()
|
||||
name: Show hash details
|
||||
run: |
|
||||
python script/clang_tidy_hash.py
|
||||
echo "## Job Failed" | tee -a $GITHUB_STEP_SUMMARY
|
||||
echo "You have modified clang-tidy configuration but have not updated the hash." | tee -a $GITHUB_STEP_SUMMARY
|
||||
echo "Please run 'script/clang_tidy_hash.py --update' and commit the changes." | tee -a $GITHUB_STEP_SUMMARY
|
||||
|
||||
- if: failure() && github.event.pull_request.head.repo.full_name == github.repository
|
||||
name: Request changes
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
await github.rest.pulls.createReview({
|
||||
pull_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
event: 'REQUEST_CHANGES',
|
||||
body: 'You have modified clang-tidy configuration but have not updated the hash.\nPlease run `script/clang_tidy_hash.py --update` and commit the changes.'
|
||||
})
|
||||
|
||||
- if: success() && github.event.pull_request.head.repo.full_name == github.repository
|
||||
name: Dismiss review
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
let reviews = await github.rest.pulls.listReviews({
|
||||
pull_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo
|
||||
});
|
||||
for (let review of reviews.data) {
|
||||
if (review.user.login === 'github-actions[bot]' && review.state === 'CHANGES_REQUESTED') {
|
||||
await github.rest.pulls.dismissReview({
|
||||
pull_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
review_id: review.id,
|
||||
message: 'Clang-tidy hash now matches configuration.'
|
||||
});
|
||||
}
|
||||
}
|
||||
186
.github/workflows/ci-docker.yml
vendored
186
.github/workflows/ci-docker.yml
vendored
@@ -1,28 +1,41 @@
|
||||
---
|
||||
name: CI for docker images
|
||||
|
||||
# Only run when docker paths change
|
||||
# Only run on PRs that touch the docker image, its build inputs, or any code
|
||||
# whose toolchain the compile smoke test exercises (core + target platforms).
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [dev, beta, release]
|
||||
paths:
|
||||
- "docker/**"
|
||||
- ".github/workflows/ci-docker.yml"
|
||||
- "requirements*.txt"
|
||||
- "platformio.ini"
|
||||
- "script/platformio_install_deps.py"
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
# Docker image and its build inputs.
|
||||
- "docker/**"
|
||||
- ".github/workflows/ci-docker.yml"
|
||||
- "requirements*.txt"
|
||||
- "pyproject.toml"
|
||||
- "platformio.ini"
|
||||
- "esphome/idf_component.yml"
|
||||
- "script/platformio_install_deps.py"
|
||||
# Core, build pipeline, toolchain, and target-platform changes can change
|
||||
# how a toolchain is set up or built, so re-run the per-toolchain compile
|
||||
# smoke test when they change.
|
||||
- "esphome/core/**"
|
||||
- "esphome/writer.py"
|
||||
- "esphome/build_gen/**"
|
||||
- "esphome/espidf/**"
|
||||
- "esphome/platformio/**"
|
||||
- "esphome/components/bk72xx/**"
|
||||
- "esphome/components/esp32/**"
|
||||
- "esphome/components/esp8266/**"
|
||||
- "esphome/components/host/**"
|
||||
- "esphome/components/libretiny/**"
|
||||
- "esphome/components/ln882x/**"
|
||||
- "esphome/components/nrf52/**"
|
||||
- "esphome/components/rp2040/**"
|
||||
- "esphome/components/rtl87xx/**"
|
||||
- "esphome/components/zephyr/**"
|
||||
|
||||
permissions:
|
||||
contents: read # actions/checkout only; the build does not push images
|
||||
contents: read # actions/checkout only
|
||||
|
||||
concurrency:
|
||||
# yamllint disable-line rule:line-length
|
||||
@@ -33,6 +46,9 @@ jobs:
|
||||
check-docker:
|
||||
name: Build docker containers
|
||||
runs-on: ${{ matrix.os }}
|
||||
permissions:
|
||||
contents: read # actions/checkout to load Dockerfile and build context
|
||||
packages: write # push branch-tagged images to ghcr.io for local testing
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -41,8 +57,11 @@ jobs:
|
||||
- "ha-addon"
|
||||
- "docker"
|
||||
# - "lint"
|
||||
outputs:
|
||||
tag: ${{ steps.tag.outputs.tag }}
|
||||
push: ${{ steps.tag.outputs.push }}
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
@@ -50,14 +69,149 @@ jobs:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
|
||||
- name: Set TAG
|
||||
- name: Determine tag and whether to push
|
||||
id: tag
|
||||
run: |
|
||||
echo "TAG=check" >> $GITHUB_ENV
|
||||
# Sanitize the branch name into a valid docker tag: replace invalid
|
||||
# characters, ensure the first character is valid (tags must start
|
||||
# with [A-Za-z0-9_]), and cap the length at 128 characters.
|
||||
branch="${{ github.head_ref || github.ref_name }}"
|
||||
tag="${branch//[^a-zA-Z0-9_.-]/-}"
|
||||
case "$tag" in
|
||||
[a-zA-Z0-9_]*) ;;
|
||||
*) tag="pr-${tag}" ;;
|
||||
esac
|
||||
tag="${tag:0:128}"
|
||||
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
|
||||
# Only push branch images for same-repo pull requests. Push events
|
||||
# only fire for dev/beta/release, whose images are owned by the
|
||||
# release pipeline -- never overwrite those from here.
|
||||
if [ "${{ github.event_name }}" = "pull_request" ] \
|
||||
&& [ "${{ github.repository }}" = "esphome/esphome" ] \
|
||||
&& [ "${{ github.event.pull_request.head.repo.full_name }}" = "esphome/esphome" ]; then
|
||||
echo "push=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "push=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Log in to the GitHub container registry
|
||||
if: steps.tag.outputs.push == 'true'
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Run build
|
||||
run: |
|
||||
docker/build.py \
|
||||
--tag "${TAG}" \
|
||||
--tag "${{ steps.tag.outputs.tag }}" \
|
||||
--arch "${{ matrix.os == 'ubuntu-24.04-arm' && 'aarch64' || 'amd64' }}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
build
|
||||
--registry ghcr \
|
||||
build ${{ steps.tag.outputs.push == 'true' && '--push --no-cache-to' || '' }} ${{ (matrix.os == 'ubuntu-24.04' && matrix.build_type == 'docker') && '--load' || '' }}
|
||||
|
||||
# The amd64 "docker" image is also loaded locally (above) and handed to
|
||||
# compile-test as an artifact, so the smoke test reuses this build instead
|
||||
# of building the image a second time. Using an artifact (rather than the
|
||||
# pushed image) keeps it working for fork PRs, which never push to ghcr.io.
|
||||
- name: Export image for compile-test
|
||||
if: matrix.os == 'ubuntu-24.04' && matrix.build_type == 'docker'
|
||||
run: docker save "ghcr.io/esphome/esphome-amd64:${{ steps.tag.outputs.tag }}" | gzip > compile-test-image.tar.gz
|
||||
|
||||
- name: Upload compile-test image artifact
|
||||
if: matrix.os == 'ubuntu-24.04' && matrix.build_type == 'docker'
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
# The tar is already gzipped, so upload it as-is. archive: false skips
|
||||
# the redundant zip and makes the file name the artifact name (the
|
||||
# `name` input is ignored in that mode).
|
||||
path: compile-test-image.tar.gz
|
||||
retention-days: 1
|
||||
archive: false
|
||||
|
||||
manifest:
|
||||
name: Push ${{ matrix.build_type }} manifest to ghcr.io
|
||||
needs: [check-docker]
|
||||
if: needs.check-docker.outputs.push == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read # actions/checkout to run docker/build.py
|
||||
packages: write # buildx imagetools writes the multi-arch tag to ghcr.io
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build_type:
|
||||
- "ha-addon"
|
||||
- "docker"
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create and push manifest
|
||||
run: |
|
||||
docker/build.py \
|
||||
--tag "${{ needs.check-docker.outputs.tag }}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
--registry ghcr \
|
||||
manifest
|
||||
|
||||
# Smoke-test the built image by compiling one minimal config per target
|
||||
# platform / toolchain. This catches missing system dependencies in the image
|
||||
# that only surface when a given toolchain is downloaded and run. The image is
|
||||
# the amd64 "docker" build produced by check-docker (shared as an artifact).
|
||||
compile-test:
|
||||
name: Compile ${{ matrix.id }}
|
||||
needs: check-docker
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read # actions/checkout to load the test configs
|
||||
strategy:
|
||||
fail-fast: false
|
||||
# Cap concurrency so this smoke test doesn't hog all the shared runners.
|
||||
max-parallel: 2
|
||||
matrix:
|
||||
# One entry per distinct toolchain. ESP32 variants (c3/c6/s2/s3/p4)
|
||||
# share a toolchain bundle, so esp32 is exercised on the base variant
|
||||
# across the full framework x toolchain cross-product (arduino/esp-idf
|
||||
# framework, each built with the platformio and native esp-idf
|
||||
# toolchains) so both toolchains stay covered regardless of which one is
|
||||
# the default.
|
||||
id:
|
||||
- esp8266-arduino
|
||||
- esp32-arduino-platformio
|
||||
- esp32-arduino-esp-idf
|
||||
- esp32-idf-platformio
|
||||
- esp32-idf-esp-idf
|
||||
- rp2040-arduino
|
||||
- bk72xx-arduino
|
||||
- rtl87xx-arduino
|
||||
- ln882x-arduino
|
||||
- nrf52
|
||||
- host
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Download image artifact
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: compile-test-image.tar.gz
|
||||
- name: Load image
|
||||
run: docker load --input compile-test-image.tar.gz
|
||||
- name: Compile ${{ matrix.id }}
|
||||
run: |
|
||||
docker run --rm \
|
||||
-v "${{ github.workspace }}/docker/test_configs:/config" \
|
||||
"ghcr.io/esphome/esphome-amd64:${{ needs.check-docker.outputs.tag }}" \
|
||||
compile "${{ matrix.id }}.yaml"
|
||||
|
||||
2
.github/workflows/ci-github-scripts.yml
vendored
2
.github/workflows/ci-github-scripts.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Run tests
|
||||
working-directory: .github/scripts/auto-label-pr
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
|
||||
- name: Check out code from base repository
|
||||
if: steps.pr.outputs.skip != 'true'
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Always check out from the base repository (esphome/esphome), never from forks
|
||||
# Use the PR's target branch to ensure we run trusted code from the main repo
|
||||
|
||||
183
.github/workflows/ci.yml
vendored
183
.github/workflows/ci.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
cache-key: ${{ steps.cache-key.outputs.key }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Generate cache-key
|
||||
id: cache-key
|
||||
run: echo key="${{ hashFiles('requirements.txt', 'requirements_dev.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
if: needs.determine-jobs.outputs.python-linters == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
if: needs.determine-jobs.outputs.core-ci == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -124,7 +124,7 @@ jobs:
|
||||
if: needs.determine-jobs.outputs.import-time == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -152,11 +152,11 @@ jobs:
|
||||
if: needs.determine-jobs.outputs.device-builder == 'true'
|
||||
steps:
|
||||
- name: Check out esphome (this PR)
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
path: esphome
|
||||
- name: Check out esphome/device-builder
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
repository: esphome/device-builder
|
||||
ref: main
|
||||
@@ -225,7 +225,7 @@ jobs:
|
||||
if: needs.determine-jobs.outputs.core-ci == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Restore Python
|
||||
id: restore-python
|
||||
uses: ./.github/actions/restore-python
|
||||
@@ -270,8 +270,8 @@ jobs:
|
||||
python-linters: ${{ steps.determine.outputs.python-linters }}
|
||||
import-time: ${{ steps.determine.outputs.import-time }}
|
||||
device-builder: ${{ steps.determine.outputs.device-builder }}
|
||||
native-idf: ${{ steps.determine.outputs.native-idf }}
|
||||
native-idf-components: ${{ steps.determine.outputs.native-idf-components }}
|
||||
esp32-platformio: ${{ steps.determine.outputs.esp32-platformio }}
|
||||
esp32-platformio-components: ${{ steps.determine.outputs.esp32-platformio-components }}
|
||||
changed-components: ${{ steps.determine.outputs.changed-components }}
|
||||
changed-components-with-tests: ${{ steps.determine.outputs.changed-components-with-tests }}
|
||||
directly-changed-components-with-tests: ${{ steps.determine.outputs.directly-changed-components-with-tests }}
|
||||
@@ -285,7 +285,7 @@ jobs:
|
||||
benchmarks: ${{ steps.determine.outputs.benchmarks }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Fetch enough history to find the merge base
|
||||
fetch-depth: 2
|
||||
@@ -324,8 +324,8 @@ jobs:
|
||||
echo "python-linters=$(echo "$output" | jq -r '.python_linters')" >> $GITHUB_OUTPUT
|
||||
echo "import-time=$(echo "$output" | jq -r '.import_time')" >> $GITHUB_OUTPUT
|
||||
echo "device-builder=$(echo "$output" | jq -r '.device_builder')" >> $GITHUB_OUTPUT
|
||||
echo "native-idf=$(echo "$output" | jq -r '.native_idf')" >> $GITHUB_OUTPUT
|
||||
echo "native-idf-components=$(echo "$output" | jq -r '.native_idf_components')" >> $GITHUB_OUTPUT
|
||||
echo "esp32-platformio=$(echo "$output" | jq -r '.esp32_platformio')" >> $GITHUB_OUTPUT
|
||||
echo "esp32-platformio-components=$(echo "$output" | jq -r '.esp32_platformio_components')" >> $GITHUB_OUTPUT
|
||||
echo "changed-components=$(echo "$output" | jq -c '.changed_components')" >> $GITHUB_OUTPUT
|
||||
echo "changed-components-with-tests=$(echo "$output" | jq -c '.changed_components_with_tests')" >> $GITHUB_OUTPUT
|
||||
echo "directly-changed-components-with-tests=$(echo "$output" | jq -c '.directly_changed_components_with_tests')" >> $GITHUB_OUTPUT
|
||||
@@ -357,7 +357,7 @@ jobs:
|
||||
bucket: ${{ fromJson(needs.determine-jobs.outputs.integration-test-buckets) }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Set up Python 3.13
|
||||
id: python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
@@ -409,7 +409,7 @@ jobs:
|
||||
if: github.event_name == 'pull_request' && (needs.determine-jobs.outputs.cpp-unit-tests-run-all == 'true' || needs.determine-jobs.outputs.cpp-unit-tests-components != '[]')
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
@@ -438,7 +438,7 @@ jobs:
|
||||
(github.event_name == 'pull_request' && needs.determine-jobs.outputs.benchmarks == 'true')
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
@@ -456,7 +456,7 @@ jobs:
|
||||
echo "binary=$BINARY" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Run CodSpeed benchmarks
|
||||
uses: CodSpeedHQ/action@9d332c4d90b43981c3e55ae8e38e68709996240f # v4.17.0
|
||||
uses: CodSpeedHQ/action@63f3e98b61959fe67f146a3ff022e4136fe9bb9c # v4.17.6
|
||||
with:
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
@@ -496,7 +496,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Need history for HEAD~1 to work for checking changed files
|
||||
fetch-depth: 2
|
||||
@@ -522,7 +522,6 @@ jobs:
|
||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||
|
||||
- name: Cache ESP-IDF install
|
||||
# Shared with the IDF tidy + native-IDF build jobs (same install).
|
||||
if: matrix.cache_idf
|
||||
uses: ./.github/actions/cache-esp-idf
|
||||
with:
|
||||
@@ -537,15 +536,12 @@ jobs:
|
||||
id: check_full_scan
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
# determine-jobs.clang-tidy-full-scan is true when core C++ changed
|
||||
# OR the ci-run-all label forced --force-all. Independent of the
|
||||
# hash check, both must produce a full scan in the job itself.
|
||||
# determine-jobs.clang-tidy-full-scan is true when core C++ or a
|
||||
# clang-tidy-relevant config file changed, or the ci-run-all label
|
||||
# forced --force-all.
|
||||
if [ "${{ needs.determine-jobs.outputs.clang-tidy-full-scan }}" = "true" ]; then
|
||||
echo "full_scan=true" >> $GITHUB_OUTPUT
|
||||
echo "reason=determine_jobs" >> $GITHUB_OUTPUT
|
||||
elif python script/clang_tidy_hash.py --check; then
|
||||
echo "full_scan=true" >> $GITHUB_OUTPUT
|
||||
echo "reason=hash_changed" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "full_scan=false" >> $GITHUB_OUTPUT
|
||||
echo "reason=normal" >> $GITHUB_OUTPUT
|
||||
@@ -583,7 +579,7 @@ jobs:
|
||||
ESPHOME_ESP_IDF_PREFIX: ~/.esphome-idf
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Need history for HEAD~1 to work for checking changed files
|
||||
fetch-depth: 2
|
||||
@@ -595,7 +591,6 @@ jobs:
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
|
||||
- name: Cache ESP-IDF install
|
||||
# Shared with the Arduino tidy + native-IDF build jobs (same install).
|
||||
uses: ./.github/actions/cache-esp-idf
|
||||
|
||||
- name: Register problem matchers
|
||||
@@ -607,15 +602,12 @@ jobs:
|
||||
id: check_full_scan
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
# determine-jobs.clang-tidy-full-scan is true when core C++ changed
|
||||
# OR the ci-run-all label forced --force-all. Independent of the
|
||||
# hash check, both must produce a full scan in the job itself.
|
||||
# determine-jobs.clang-tidy-full-scan is true when core C++ or a
|
||||
# clang-tidy-relevant config file changed, or the ci-run-all label
|
||||
# forced --force-all.
|
||||
if [ "${{ needs.determine-jobs.outputs.clang-tidy-full-scan }}" = "true" ]; then
|
||||
echo "full_scan=true" >> $GITHUB_OUTPUT
|
||||
echo "reason=determine_jobs" >> $GITHUB_OUTPUT
|
||||
elif python script/clang_tidy_hash.py --check; then
|
||||
echo "full_scan=true" >> $GITHUB_OUTPUT
|
||||
echo "reason=hash_changed" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "full_scan=false" >> $GITHUB_OUTPUT
|
||||
echo "reason=normal" >> $GITHUB_OUTPUT
|
||||
@@ -667,7 +659,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Need history for HEAD~1 to work for checking changed files
|
||||
fetch-depth: 2
|
||||
@@ -679,7 +671,6 @@ jobs:
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
|
||||
- name: Cache ESP-IDF install
|
||||
# Shared with the Arduino tidy + native-IDF build jobs (same install).
|
||||
uses: ./.github/actions/cache-esp-idf
|
||||
|
||||
- name: Register problem matchers
|
||||
@@ -691,15 +682,12 @@ jobs:
|
||||
id: check_full_scan
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
# determine-jobs.clang-tidy-full-scan is true when core C++ changed
|
||||
# OR the ci-run-all label forced --force-all. Independent of the
|
||||
# hash check, both must produce a full scan in the job itself.
|
||||
# determine-jobs.clang-tidy-full-scan is true when core C++ or a
|
||||
# clang-tidy-relevant config file changed, or the ci-run-all label
|
||||
# forced --force-all.
|
||||
if [ "${{ needs.determine-jobs.outputs.clang-tidy-full-scan }}" = "true" ]; then
|
||||
echo "full_scan=true" >> $GITHUB_OUTPUT
|
||||
echo "reason=determine_jobs" >> $GITHUB_OUTPUT
|
||||
elif python script/clang_tidy_hash.py --check; then
|
||||
echo "full_scan=true" >> $GITHUB_OUTPUT
|
||||
echo "reason=hash_changed" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "full_scan=false" >> $GITHUB_OUTPUT
|
||||
echo "reason=normal" >> $GITHUB_OUTPUT
|
||||
@@ -755,7 +743,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# Need history for HEAD~1 to work for checking changed files
|
||||
fetch-depth: 2
|
||||
@@ -767,7 +755,6 @@ jobs:
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
|
||||
- name: Cache ESP-IDF install
|
||||
# Shared with the IDF/Arduino clang-tidy jobs + native-IDF build (same install).
|
||||
uses: ./.github/actions/cache-esp-idf
|
||||
|
||||
- name: Register problem matchers
|
||||
@@ -779,15 +766,12 @@ jobs:
|
||||
id: check_full_scan
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
# determine-jobs.clang-tidy-full-scan is true when core C++ changed
|
||||
# OR the ci-run-all label forced --force-all. Independent of the
|
||||
# hash check, both must produce a full scan in the job itself.
|
||||
# determine-jobs.clang-tidy-full-scan is true when core C++ or a
|
||||
# clang-tidy-relevant config file changed, or the ci-run-all label
|
||||
# forced --force-all.
|
||||
if [ "${{ needs.determine-jobs.outputs.clang-tidy-full-scan }}" = "true" ]; then
|
||||
echo "full_scan=true" >> $GITHUB_OUTPUT
|
||||
echo "reason=determine_jobs" >> $GITHUB_OUTPUT
|
||||
elif python script/clang_tidy_hash.py --check; then
|
||||
echo "full_scan=true" >> $GITHUB_OUTPUT
|
||||
echo "reason=hash_changed" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "full_scan=false" >> $GITHUB_OUTPUT
|
||||
echo "reason=normal" >> $GITHUB_OUTPUT
|
||||
@@ -817,6 +801,10 @@ jobs:
|
||||
- common
|
||||
- determine-jobs
|
||||
if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) > 0
|
||||
env:
|
||||
# esp32 component builds use the native ESP-IDF toolchain (default), so
|
||||
# share the tidy jobs' install location -- the restore below lands here.
|
||||
ESPHOME_ESP_IDF_PREFIX: ~/.esphome-idf
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: ${{ (startsWith(github.base_ref, 'beta') || startsWith(github.base_ref, 'release')) && 8 || 4 }}
|
||||
@@ -838,12 +826,18 @@ jobs:
|
||||
version: 1.0
|
||||
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Cache ESP-IDF install (restore-only)
|
||||
# A batch may contain no esp32 build, so never save -- just reuse the
|
||||
# shared install the dev tidy jobs already cached when present.
|
||||
uses: ./.github/actions/cache-esp-idf
|
||||
with:
|
||||
restore-only: true
|
||||
- name: Validate and compile components with intelligent grouping
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
@@ -947,23 +941,22 @@ jobs:
|
||||
echo "All components in this batch are validate-only -- skipping compile stage."
|
||||
fi
|
||||
|
||||
test-native-idf:
|
||||
name: Test components with native ESP-IDF
|
||||
test-esp32-platformio:
|
||||
name: Test esp32 components with PlatformIO
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- common
|
||||
- determine-jobs
|
||||
if: github.event_name == 'pull_request' && needs.determine-jobs.outputs.native-idf == 'true'
|
||||
if: github.event_name == 'pull_request' && needs.determine-jobs.outputs.esp32-platformio == 'true'
|
||||
env:
|
||||
ESPHOME_ESP_IDF_PREFIX: ~/.esphome-idf
|
||||
# Comma-joined subset of the native-IDF representative component list,
|
||||
# computed by script/determine-jobs.py (native_idf_components_to_test).
|
||||
# Comma-joined subset of the esp32 PlatformIO representative component list,
|
||||
# computed by script/determine-jobs.py (esp32_platformio_components_to_test).
|
||||
# Single source of truth -- the full list lives in
|
||||
# script/determine-jobs.py::NATIVE_IDF_TEST_COMPONENTS.
|
||||
TEST_COMPONENTS: ${{ needs.determine-jobs.outputs.native-idf-components }}
|
||||
# script/determine-jobs.py::ESP32_PLATFORMIO_TEST_COMPONENTS.
|
||||
TEST_COMPONENTS: ${{ needs.determine-jobs.outputs.esp32-platformio-components }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
@@ -971,66 +964,23 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
|
||||
- name: Prepare build storage on /mnt
|
||||
# Bind-mount the larger /mnt disk over the IDF install + build dirs BEFORE
|
||||
# restoring the cache, so the ~4.5GB restore lands on the roomier volume
|
||||
# instead of being shadowed by a mount set up later in the run step.
|
||||
run: |
|
||||
root_avail=$(df -k / | awk 'NR==2 {print $4}')
|
||||
mnt_avail=$(df -k /mnt 2>/dev/null | awk 'NR==2 {print $4}')
|
||||
echo "Available space: / has ${root_avail}KB, /mnt has ${mnt_avail}KB"
|
||||
if [ -n "$mnt_avail" ] && [ "$mnt_avail" -gt "$root_avail" ]; then
|
||||
echo "Using /mnt for build files (more space available)"
|
||||
sudo mkdir -p /mnt/esphome-idf
|
||||
sudo chown $USER:$USER /mnt/esphome-idf
|
||||
mkdir -p ~/.esphome-idf
|
||||
sudo mount --bind /mnt/esphome-idf ~/.esphome-idf
|
||||
sudo mkdir -p /mnt/test_build_components_build
|
||||
sudo chown $USER:$USER /mnt/test_build_components_build
|
||||
mkdir -p tests/test_build_components/build
|
||||
sudo mount --bind /mnt/test_build_components_build tests/test_build_components/build
|
||||
else
|
||||
echo "Using / for build files (more space available than /mnt or /mnt unavailable)"
|
||||
fi
|
||||
|
||||
- name: Cache ESP-IDF install
|
||||
# Shared with the IDF/Arduino clang-tidy jobs (same install); restores
|
||||
# into the /mnt bind-mount prepared above when present.
|
||||
uses: ./.github/actions/cache-esp-idf
|
||||
|
||||
- name: Run native ESP-IDF compile test
|
||||
- name: Run PlatformIO compile test
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
|
||||
echo "Testing components: $TEST_COMPONENTS"
|
||||
echo ""
|
||||
|
||||
# Show disk space before validation
|
||||
echo "Disk space before config validation:"
|
||||
df -h
|
||||
echo ""
|
||||
|
||||
# Run config validation (auto-grouped by test_build_components.py)
|
||||
python3 script/test_build_components.py -e config -t esp32-idf -c "$TEST_COMPONENTS" -f --toolchain esp-idf
|
||||
# compile validates config first, so a separate config pass is
|
||||
# redundant for this smoke test. ESP-IDF framework via PlatformIO:
|
||||
python3 script/test_build_components.py -e compile -t esp32-idf -c "$TEST_COMPONENTS" -f --toolchain platformio
|
||||
|
||||
echo ""
|
||||
echo "Config validation passed! Starting compilation..."
|
||||
echo "ESP-IDF-via-PlatformIO build passed! Starting Arduino smoke test..."
|
||||
echo ""
|
||||
|
||||
# Show disk space before compilation
|
||||
echo "Disk space before compilation:"
|
||||
df -h
|
||||
echo ""
|
||||
|
||||
# Run compilation (auto-grouped by test_build_components.py)
|
||||
python3 script/test_build_components.py -e compile -t esp32-idf -c "$TEST_COMPONENTS" -f --toolchain esp-idf
|
||||
|
||||
- name: Save ESPHome cache
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: ~/.esphome-idf
|
||||
key: ${{ runner.os }}-esphome-${{ needs.common.outputs.cache-key }}
|
||||
# Arduino framework via PlatformIO (only components with an esp32-ard test are built):
|
||||
python3 script/test_build_components.py -e compile -t esp32-ard -c "$TEST_COMPONENTS" -f --toolchain platformio
|
||||
|
||||
pre-commit-ci-lite:
|
||||
name: pre-commit.ci lite
|
||||
@@ -1041,7 +991,7 @@ jobs:
|
||||
if: github.event_name == 'pull_request' && !startsWith(github.base_ref, 'beta') && !startsWith(github.base_ref, 'release') && needs.determine-jobs.outputs.core-ci == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -1049,7 +999,7 @@ jobs:
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- uses: esphome/pre-commit-action@43cd1109c09c544d97196f7730ee5b2e0cc6d81e # v3.0.1 fork with pinned actions/cache
|
||||
env:
|
||||
SKIP: pylint,clang-tidy-hash,ci-custom
|
||||
SKIP: pylint,ci-custom
|
||||
- uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0
|
||||
if: always()
|
||||
|
||||
@@ -1067,7 +1017,7 @@ jobs:
|
||||
skip: ${{ steps.check-script.outputs.skip || steps.check-tests.outputs.skip }}
|
||||
steps:
|
||||
- name: Check out target branch
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: ${{ github.base_ref }}
|
||||
|
||||
@@ -1249,7 +1199,7 @@ jobs:
|
||||
flash_usage: ${{ steps.extract.outputs.flash_usage }}
|
||||
steps:
|
||||
- name: Check out PR branch
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -1318,7 +1268,7 @@ jobs:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -1365,7 +1315,7 @@ jobs:
|
||||
- determine-jobs
|
||||
- device-builder
|
||||
- test-build-components-split
|
||||
- test-native-idf
|
||||
- test-esp32-platformio
|
||||
- pre-commit-ci-lite
|
||||
- memory-impact-target-branch
|
||||
- memory-impact-pr-branch
|
||||
@@ -1381,4 +1331,7 @@ jobs:
|
||||
# 1. The target branch has a build issue independent of this PR
|
||||
# 2. This PR fixes a build issue on the target branch
|
||||
# In either case, we only care that the PR branch builds successfully.
|
||||
echo "$NEEDS_JSON" | jq -e 'del(.["memory-impact-target-branch"]) | all(.result != "failure")'
|
||||
# Every other job must have succeeded or been skipped; a "cancelled" or
|
||||
# "failure" result fails this check so CI is not reported green when the
|
||||
# workflow was cancelled.
|
||||
echo "$NEEDS_JSON" | jq -e 'del(.["memory-impact-target-branch"]) | all(.result == "success" or .result == "skipped")'
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout base branch
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.base.sha }}
|
||||
sparse-checkout: |
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout base branch
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.base.sha }}
|
||||
|
||||
|
||||
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@@ -52,7 +52,7 @@ jobs:
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
119
.github/workflows/dashboard-deprecation-comment.yml
vendored
119
.github/workflows/dashboard-deprecation-comment.yml
vendored
@@ -1,119 +0,0 @@
|
||||
name: Add Dashboard Deprecation Comment
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize]
|
||||
|
||||
# All API calls (pulls.listFiles + issues.{list,create,update}Comment) are performed with
|
||||
# the App token minted below, so the workflow's GITHUB_TOKEN does not need any scopes.
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
dashboard-deprecation-comment:
|
||||
name: Dashboard deprecation comment
|
||||
runs-on: ubuntu-latest
|
||||
# Release-bump PRs (bump-X.Y.Z -> beta, beta -> release) inevitably
|
||||
# roll up everything merged into dev since the last cut, which can
|
||||
# include dashboard changes that have already been reviewed once.
|
||||
# The bot's purpose is to warn new contributors before they invest
|
||||
# time -- that only applies to PRs entering dev.
|
||||
if: github.event.pull_request.base.ref == 'dev'
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||
with:
|
||||
client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
# pulls.listFiles + issues.{list,create,update}Comment on PRs. For PR resources
|
||||
# the issues.*Comment APIs require the pull-requests scope, not issues.
|
||||
permission-pull-requests: write
|
||||
|
||||
- name: Add dashboard deprecation comment
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
const commentMarker = "<!-- This comment was generated automatically by the dashboard-deprecation-comment workflow. -->";
|
||||
|
||||
const commentBody = `Thanks for opening this PR!
|
||||
|
||||
Heads up: the legacy ESPHome dashboard (\`esphome/dashboard/\` and \`tests/dashboard/\`) is **deprecated** and is being replaced by [ESPHome Device Builder](https://github.com/esphome/device-builder). We are not adding new features to the legacy dashboard and it will eventually be removed from this repository.
|
||||
|
||||
What this means for your PR:
|
||||
|
||||
- **New features / enhancements**: please port the change to [esphome/device-builder](https://github.com/esphome/device-builder) instead. We are unlikely to review or merge new dashboard features here.
|
||||
- **Bug fixes**: small fixes may still be considered, but please check first whether the same issue exists in Device Builder, where the fix will have a longer life.
|
||||
- **Security issues**: please do not file a public PR. Report privately via [GitHub security advisories](https://github.com/esphome/esphome/security/advisories/new) so we can coordinate a fix.
|
||||
|
||||
We appreciate the contribution and apologize for the friction; flagging this early so your time isn't spent on a change that may not land.
|
||||
|
||||
---
|
||||
(Added by the PR bot)
|
||||
|
||||
${commentMarker}`;
|
||||
|
||||
async function getDashboardChanges(github, owner, repo, prNumber) {
|
||||
const changedFiles = await github.paginate(
|
||||
github.rest.pulls.listFiles,
|
||||
{
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
pull_number: prNumber,
|
||||
per_page: 100,
|
||||
}
|
||||
);
|
||||
|
||||
return changedFiles.filter(file =>
|
||||
file.filename.startsWith('esphome/dashboard/') ||
|
||||
file.filename.startsWith('tests/dashboard/')
|
||||
);
|
||||
}
|
||||
|
||||
async function findBotComment(github, owner, repo, prNumber) {
|
||||
const comments = await github.paginate(
|
||||
github.rest.issues.listComments,
|
||||
{
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
issue_number: prNumber,
|
||||
per_page: 100,
|
||||
}
|
||||
);
|
||||
|
||||
return comments.find(comment =>
|
||||
comment.body.includes(commentMarker) && comment.user.type === "Bot"
|
||||
);
|
||||
}
|
||||
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
const { owner, repo } = context.repo;
|
||||
|
||||
const dashboardChanges = await getDashboardChanges(github, owner, repo, prNumber);
|
||||
const existingComment = await findBotComment(github, owner, repo, prNumber);
|
||||
|
||||
if (dashboardChanges.length === 0) {
|
||||
// PR doesn't (or no longer) touches the legacy dashboard. If we previously
|
||||
// commented (e.g. files were removed in a later push), leave the comment in
|
||||
// place for history rather than thrash on edit/delete.
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingComment) {
|
||||
if (existingComment.body === commentBody) {
|
||||
return;
|
||||
}
|
||||
await github.rest.issues.updateComment({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
comment_id: existingComment.id,
|
||||
body: commentBody,
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
issue_number: prNumber,
|
||||
body: commentBody,
|
||||
});
|
||||
}
|
||||
2
.github/workflows/pr-title-check.yml
vendored
2
.github/workflows/pr-title-check.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
name: Validate PR title
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
|
||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
branch_build: ${{ steps.tag.outputs.branch_build }}
|
||||
deploy_env: ${{ steps.tag.outputs.deploy_env }}
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Get tag
|
||||
id: tag
|
||||
# yamllint disable rule:line-length
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
contents: read # actions/checkout to build the sdist/wheel
|
||||
id-token: write # OIDC token for PyPI Trusted Publishing (pypa/gh-action-pypi-publish)
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
os: "ubuntu-24.04-arm"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
@@ -168,7 +168,7 @@ jobs:
|
||||
- ghcr
|
||||
- dockerhub
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
|
||||
4
.github/workflows/sync-device-classes.yml
vendored
4
.github/workflows/sync-device-classes.yml
vendored
@@ -28,10 +28,10 @@ jobs:
|
||||
permission-pull-requests: write # pulls.create / pulls.update to open or refresh the sync PR
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Checkout Home Assistant
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
repository: home-assistant/core
|
||||
path: lib/home-assistant
|
||||
|
||||
@@ -6,7 +6,7 @@ ci:
|
||||
autoupdate_commit_msg: 'pre-commit: autoupdate'
|
||||
autoupdate_schedule: off # Disabled until ruff versions are synced between deps and pre-commit
|
||||
# Skip hooks that have issues in pre-commit CI environment
|
||||
skip: [pylint, clang-tidy-hash]
|
||||
skip: [pylint]
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
@@ -59,13 +59,6 @@ repos:
|
||||
language: system
|
||||
types: [python]
|
||||
files: ^esphome/.+\.py$
|
||||
- id: clang-tidy-hash
|
||||
name: Update clang-tidy hash
|
||||
entry: python script/clang_tidy_hash.py --update-if-changed
|
||||
language: python
|
||||
files: ^(\.clang-tidy|platformio\.ini|requirements_dev\.txt|sdkconfig\.defaults|esphome/idf_component\.yml)$
|
||||
pass_filenames: false
|
||||
additional_dependencies: []
|
||||
- id: ci-custom
|
||||
name: ci-custom
|
||||
entry: python script/run-in-env.py script/ci-custom.py
|
||||
|
||||
19
AGENTS.md
19
AGENTS.md
@@ -14,7 +14,7 @@ This document provides essential context for AI models interacting with this pro
|
||||
* **Build Systems:** PlatformIO is the primary build system. CMake is used as an alternative.
|
||||
* **Configuration:** YAML.
|
||||
* **Key Libraries/Dependencies:**
|
||||
* **Python:** `voluptuous` (for configuration validation), `PyYAML` (for parsing configuration files), `paho-mqtt` (for MQTT communication), `tornado` (for the web server), `aioesphomeapi` (for the native API).
|
||||
* **Python:** `voluptuous` (for configuration validation), `PyYAML` (for parsing configuration files), `paho-mqtt` (for MQTT communication), `aioesphomeapi` (for the native API).
|
||||
* **C++:** `ArduinoJson` (for JSON serialization/deserialization), `AsyncMqttClient-esphome` (for MQTT), `ESPAsyncWebServer` (for the web server).
|
||||
* **Package Manager(s):** `pip` (for Python dependencies), `platformio` (for C++/PlatformIO dependencies).
|
||||
* **Communication Protocols:** Protobuf (for native API), MQTT, HTTP.
|
||||
@@ -35,7 +35,6 @@ This document provides essential context for AI models interacting with this pro
|
||||
2. **Code Generation** (`esphome/codegen.py`, `esphome/cpp_generator.py`): Manages Python to C++ code generation, template processing, and build flag management.
|
||||
3. **Component System** (`esphome/components/`): Contains modular hardware and software components with platform-specific implementations and dependency management.
|
||||
4. **Core Framework** (`esphome/core/`): Manages the application lifecycle, hardware abstraction, and component registration.
|
||||
5. **Dashboard** (`esphome/dashboard/`): A web-based interface for device configuration, management, and OTA updates.
|
||||
|
||||
* **Platform Support:**
|
||||
1. **ESP32** (`components/esp32/`): Espressif ESP32 family. Supports multiple variants (Original, C2, C3, C5, C6, H2, P4, S2, S3) with ESP-IDF framework. Arduino framework supports only a subset of the variants (Original, C3, S2, S3).
|
||||
@@ -59,6 +58,19 @@ This document provides essential context for AI models interacting with this pro
|
||||
- Protected/private fields: `lower_snake_case_with_trailing_underscore_`
|
||||
- Favor descriptive names over abbreviations
|
||||
|
||||
* **Python Idioms:**
|
||||
* **Assignment expressions (PEP 572):** Prefer the walrus operator (`:=`) wherever it removes a redundant lookup or a throwaway temporary. The most common case in component code is presence-checking a config key and then indexing it separately — fetch once with `.get()` and bind in the condition instead:
|
||||
```python
|
||||
# Bad - looks up CONF_BLAH twice
|
||||
if CONF_BLAH in config:
|
||||
cg.add(var.set_blah(config[CONF_BLAH]))
|
||||
|
||||
# Good - single lookup, value bound inline
|
||||
if (blah := config.get(CONF_BLAH)) is not None:
|
||||
cg.add(var.set_blah(blah))
|
||||
```
|
||||
The same applies to `while` loops and comprehensions where it avoids recomputing a value. Don't contort code to use it — reach for `:=` only when it genuinely cuts repetition or an extra assignment line.
|
||||
|
||||
* **C++ Field Visibility:**
|
||||
* **Prefer `protected`:** Use `protected` for most class fields to enable extensibility and testing. Fields should be `lower_snake_case_with_trailing_underscore_`.
|
||||
* **Use `private` for safety-critical cases:** Use `private` visibility when direct field access could introduce bugs or violate invariants:
|
||||
@@ -443,7 +455,6 @@ This document provides essential context for AI models interacting with this pro
|
||||
* **Debug Tools:**
|
||||
- `esphome config <file>.yaml` to validate configuration.
|
||||
- `esphome compile <file>.yaml` to compile without uploading.
|
||||
- Check the Dashboard for real-time logs.
|
||||
- Use component-specific debug logging.
|
||||
* **Common Issues:**
|
||||
- **Import Errors**: Check component dependencies and `PYTHONPATH`.
|
||||
@@ -645,7 +656,7 @@ This document provides essential context for AI models interacting with this pro
|
||||
If you need a real-world example, search for components that use `@dataclass` with `CORE.data` in the codebase. Note: Some components may use `TypedDict` for dictionary-based storage; both patterns are acceptable depending on your needs.
|
||||
|
||||
**Why this matters:**
|
||||
- Module-level globals persist between compilation runs if the dashboard doesn't fork/exec
|
||||
- Module-level globals persist between compilation runs if the host process (e.g. device-builder) doesn't fork/exec
|
||||
- `CORE.data` automatically clears between runs
|
||||
- Namespacing under `DOMAIN` prevents key collisions between components
|
||||
- `@dataclass` provides type safety and cleaner attribute access
|
||||
|
||||
@@ -445,7 +445,7 @@ esphome/components/select/* @esphome/core
|
||||
esphome/components/sen0321/* @notjj
|
||||
esphome/components/sen21231/* @shreyaskarnik
|
||||
esphome/components/sen5x/* @martgras
|
||||
esphome/components/sen6x/* @martgras @mebner86 @mikelawrence @tuct
|
||||
esphome/components/sen6x/* @martgras @mebner86 @tuct
|
||||
esphome/components/sendspin/* @kahrendt
|
||||
esphome/components/sendspin/media_player/* @kahrendt
|
||||
esphome/components/sendspin/media_source/* @kahrendt
|
||||
@@ -561,6 +561,7 @@ esphome/components/uart/packet_transport/* @clydebarrow
|
||||
esphome/components/udp/* @clydebarrow
|
||||
esphome/components/ufire_ec/* @pvizeli
|
||||
esphome/components/ufire_ise/* @pvizeli
|
||||
esphome/components/ufm01/* @ljungqvist
|
||||
esphome/components/ultrasonic/* @ssieb @swoboda1337
|
||||
esphome/components/update/* @jesserockz
|
||||
esphome/components/uponor_smatrix/* @kroimon
|
||||
|
||||
2
Doxyfile
2
Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 2026.6.0-dev
|
||||
PROJECT_NUMBER = 2026.7.0-dev
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
||||
102
THREAT_MODEL.md
Normal file
102
THREAT_MODEL.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# ESPHome Threat Model
|
||||
|
||||
This document defines the trust boundary for the **ESPHome** repository — the
|
||||
Python compiler/CLI and the device firmware it generates — so that real security
|
||||
bugs can be told apart from defense-in-depth improvements. It gives contributors,
|
||||
reviewers, and security researchers a clear answer to one question:
|
||||
**does this issue let an _unauthenticated_ attacker do something they shouldn't?**
|
||||
|
||||
Related documents:
|
||||
|
||||
- Deployment guidance for operators:
|
||||
https://esphome.io/guides/security_best_practices/
|
||||
- The **Device Builder dashboard** (the web UI, its authentication, ingress,
|
||||
Origin/Host gates, and peer-link pairing) lives in a separate repository and
|
||||
has its own threat model. If your report concerns any of that, please read and
|
||||
report there instead:
|
||||
https://github.com/esphome/device-builder/blob/main/docs/THREAT_MODEL.md
|
||||
|
||||
## The trust boundary
|
||||
|
||||
For this repository there are two trusted inputs by design:
|
||||
|
||||
1. **The configuration.** Anyone who can supply or edit a YAML config is trusted
|
||||
(see below).
|
||||
2. **Authenticated peers of a running device** — clients holding the device's
|
||||
API encryption key / password, OTA password, or web server credentials.
|
||||
|
||||
The security boundary is therefore **unauthenticated network traffic vs. those
|
||||
trusted inputs.** A bug that lets an unauthenticated attacker cross it is a
|
||||
security bug.
|
||||
|
||||
## Config authors are host-equivalent by design
|
||||
|
||||
Anyone who can supply or edit a configuration is **trusted with full code
|
||||
execution on the host that runs `esphome`**, on purpose. This is what the product
|
||||
does, not a flaw. A config author can already, through fully supported features:
|
||||
|
||||
- Run arbitrary **Python** at validation/compile time via `external_components:`
|
||||
(and other component-import mechanisms) — ESPHome imports those packages as
|
||||
ordinary Python.
|
||||
- Run arbitrary **shell** commands through the compile/validate/flash toolchain
|
||||
that ESPHome invokes as subprocesses.
|
||||
- Read and write arbitrary files reachable by the process (e.g. via `!include`,
|
||||
`packages:`, `dashboard_import:`, and generated build output).
|
||||
|
||||
Because of this, a malicious config author is equivalent to shell access on the
|
||||
host running the build.
|
||||
|
||||
## What is *not* a security vulnerability
|
||||
|
||||
If exploiting an issue requires the ability to supply or edit configuration, it
|
||||
is **not** a vulnerability in ESPHome, because that ability already grants host
|
||||
code execution. This explicitly includes, among others:
|
||||
|
||||
- Template / expression injection in substitutions or any YAML string value
|
||||
(e.g. Jinja `${...}` evaluation reaching Python internals). This grants no
|
||||
capability a config author lacks.
|
||||
- `!include` / `packages:` / `dashboard_import:` reading or fetching content
|
||||
from surprising or remote locations.
|
||||
- The validator or compiler crashing or behaving unexpectedly on adversarial
|
||||
YAML.
|
||||
- ESPHome running as root in the official container — that is the documented
|
||||
deployment posture, reachable by the same caller through the features above.
|
||||
|
||||
These do not warrant a CVE or coordinated disclosure. Hardening in these areas
|
||||
(for example, sandboxing template evaluation as least-surprise defense-in-depth)
|
||||
is welcome as a normal enhancement PR, framed as cleanliness rather than a
|
||||
security fix — not as a vulnerability remediation.
|
||||
|
||||
## What we do defend
|
||||
|
||||
These *are* security bugs in this repo, and we want to hear about them privately:
|
||||
|
||||
- Memory-safety or protocol bugs in the generated **device firmware** that are
|
||||
remotely triggerable over the network (native API, web server, OTA, BLE,
|
||||
captive portal, etc.) **without** valid credentials.
|
||||
- Authentication or encryption bypass on the device — reaching API calls, OTA
|
||||
updates, or the web server without the configured key/password.
|
||||
- Flaws that weaken the device's API encryption (Noise), OTA, or web server auth
|
||||
below their documented guarantees.
|
||||
|
||||
## Explicitly out of scope
|
||||
|
||||
- Local attackers who already have shell access on the host that runs `esphome`.
|
||||
- Supply-chain attacks against ESPHome or its dependencies.
|
||||
- Operator-supplied hostile YAML (covered above — config authoring is trusted).
|
||||
- Attacks that require an already-authenticated device peer (someone who already
|
||||
holds the API key / OTA / web credentials).
|
||||
- Anything in the dashboard / device-builder — report that in its own repository
|
||||
(linked at the top).
|
||||
- Deployments where the operator removed protections or exposed credentials. See
|
||||
the security best practices guide:
|
||||
https://esphome.io/guides/security_best_practices/
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
If you believe you've found an issue that crosses the unauthenticated boundary
|
||||
above, please report it privately via GitHub Security Advisories rather than a
|
||||
public issue. For issues that require config-write access, please review this
|
||||
document first — they are very likely out of scope by design. For dashboard /
|
||||
device-builder issues, report against that repository and consult its threat
|
||||
model (linked at the top).
|
||||
@@ -1,10 +1,9 @@
|
||||
ARG BUILD_VERSION=dev
|
||||
ARG BUILD_OS=alpine
|
||||
ARG BUILD_BASE_VERSION=2025.04.0
|
||||
ARG BUILD_BASE_VERSION=2026.06.0
|
||||
ARG BUILD_TYPE=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
|
||||
FROM ghcr.io/esphome/docker-base:debian-${BUILD_BASE_VERSION} AS base-source-docker
|
||||
FROM ghcr.io/esphome/docker-base:debian-ha-addon-${BUILD_BASE_VERSION} AS base-source-ha-addon
|
||||
|
||||
ARG BUILD_TYPE
|
||||
FROM base-source-${BUILD_TYPE} AS base
|
||||
@@ -18,13 +17,9 @@ RUN git config --system --add safe.directory "*" \
|
||||
# validate openocd-esp32 (it dynamically links libusb-1.0.so.0); without
|
||||
# it idf_tools.py rejects the openocd install with exit 127 and aborts
|
||||
# the whole framework setup.
|
||||
RUN if command -v apk > /dev/null; then \
|
||||
apk add --no-cache build-base libusb; \
|
||||
else \
|
||||
apt-get update \
|
||||
&& apt-get install -y --no-install-recommends build-essential libusb-1.0-0 \
|
||||
&& rm -rf /var/lib/apt/lists/*; \
|
||||
fi
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends build-essential libusb-1.0-0 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
|
||||
|
||||
@@ -36,6 +31,9 @@ RUN \
|
||||
uv pip install --no-cache-dir \
|
||||
-r /requirements.txt
|
||||
|
||||
# Install the ESPHome Device Builder dashboard.
|
||||
RUN uv pip install --no-cache-dir esphome-device-builder==1.0.12
|
||||
|
||||
RUN \
|
||||
platformio settings set enable_telemetry No \
|
||||
&& platformio settings set check_platformio_interval 1000000 \
|
||||
|
||||
@@ -20,6 +20,10 @@ TYPE_HA_ADDON = "ha-addon"
|
||||
TYPE_LINT = "lint"
|
||||
TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT]
|
||||
|
||||
REGISTRY_GHCR = "ghcr"
|
||||
REGISTRY_DOCKERHUB = "dockerhub"
|
||||
REGISTRIES = [REGISTRY_GHCR, REGISTRY_DOCKERHUB]
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
@@ -34,6 +38,12 @@ parser.add_argument(
|
||||
parser.add_argument(
|
||||
"--build-type", choices=TYPES, required=True, help="The type of build to run"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--registry",
|
||||
choices=REGISTRIES,
|
||||
action="append",
|
||||
help="Restrict to specific registries (default: all). May be passed multiple times.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run", action="store_true", help="Don't run any commands, just print them"
|
||||
)
|
||||
@@ -45,6 +55,11 @@ build_parser.add_argument("--push", help="Also push the images", action="store_t
|
||||
build_parser.add_argument(
|
||||
"--load", help="Load the docker image locally", action="store_true"
|
||||
)
|
||||
build_parser.add_argument(
|
||||
"--no-cache-to",
|
||||
help="Don't write the build cache (avoids polluting the shared cache)",
|
||||
action="store_true",
|
||||
)
|
||||
manifest_parser = subparsers.add_parser(
|
||||
"manifest", help="Create a manifest from already pushed images"
|
||||
)
|
||||
@@ -95,11 +110,14 @@ def main():
|
||||
print("Command failed")
|
||||
sys.exit(1)
|
||||
|
||||
registries = args.registry or REGISTRIES
|
||||
|
||||
# detect channel from tag
|
||||
match = re.match(r"^(\d+\.\d+)(?:\.\d+)?(b\d+)?$", args.tag)
|
||||
major_minor_version = None
|
||||
if match is None:
|
||||
channel = CHANNEL_DEV
|
||||
# Custom tag (e.g. a branch name) -- push only the tag itself
|
||||
channel = None
|
||||
elif match.group(2) is None:
|
||||
major_minor_version = match.group(1)
|
||||
channel = CHANNEL_RELEASE
|
||||
@@ -128,11 +146,18 @@ def main():
|
||||
CHANNEL_DEV: "cache-dev",
|
||||
CHANNEL_BETA: "cache-beta",
|
||||
CHANNEL_RELEASE: "cache-latest",
|
||||
}[channel]
|
||||
cache_img = f"ghcr.io/{params.build_to}:{cache_tag}"
|
||||
}.get(channel, "cache-dev")
|
||||
# Cache images live alongside the pushed images; prefer GHCR when it is
|
||||
# one of the selected registries, otherwise fall back to Docker Hub so a
|
||||
# registry-restricted build doesn't need GHCR auth.
|
||||
cache_prefix = "ghcr.io/" if REGISTRY_GHCR in registries else ""
|
||||
cache_img = f"{cache_prefix}{params.build_to}:{cache_tag}"
|
||||
|
||||
imgs = [f"{params.build_to}:{tag}" for tag in tags_to_push]
|
||||
imgs += [f"ghcr.io/{params.build_to}:{tag}" for tag in tags_to_push]
|
||||
imgs = []
|
||||
if REGISTRY_DOCKERHUB in registries:
|
||||
imgs += [f"{params.build_to}:{tag}" for tag in tags_to_push]
|
||||
if REGISTRY_GHCR in registries:
|
||||
imgs += [f"ghcr.io/{params.build_to}:{tag}" for tag in tags_to_push]
|
||||
|
||||
# 3. build
|
||||
cmd = [
|
||||
@@ -155,7 +180,9 @@ def main():
|
||||
for img in imgs:
|
||||
cmd += ["--tag", img]
|
||||
if args.push:
|
||||
cmd += ["--push", "--cache-to", f"type=registry,ref={cache_img},mode=max"]
|
||||
cmd += ["--push"]
|
||||
if not args.no_cache_to:
|
||||
cmd += ["--cache-to", f"type=registry,ref={cache_img},mode=max"]
|
||||
if args.load:
|
||||
cmd += ["--load"]
|
||||
|
||||
@@ -163,20 +190,22 @@ def main():
|
||||
elif args.command == "manifest":
|
||||
manifest = DockerParams.for_type_arch(args.build_type, ARCH_AMD64).manifest_to
|
||||
|
||||
targets = [f"{manifest}:{tag}" for tag in tags_to_push]
|
||||
targets += [f"ghcr.io/{manifest}:{tag}" for tag in tags_to_push]
|
||||
# 1. Create manifests
|
||||
targets = []
|
||||
if REGISTRY_DOCKERHUB in registries:
|
||||
targets += [f"{manifest}:{tag}" for tag in tags_to_push]
|
||||
if REGISTRY_GHCR in registries:
|
||||
targets += [f"ghcr.io/{manifest}:{tag}" for tag in tags_to_push]
|
||||
# Use buildx imagetools (not `docker manifest`) so the per-arch sources,
|
||||
# which buildx pushes as single-platform manifest lists, are combined
|
||||
# and pushed correctly in one step.
|
||||
for target in targets:
|
||||
cmd = ["docker", "manifest", "create", target]
|
||||
cmd = ["docker", "buildx", "imagetools", "create", "--tag", target]
|
||||
for arch in ARCHS:
|
||||
src = f"{DockerParams.for_type_arch(args.build_type, arch).build_to}:{args.tag}"
|
||||
if target.startswith("ghcr.io"):
|
||||
src = f"ghcr.io/{src}"
|
||||
cmd.append(src)
|
||||
run_command(*cmd)
|
||||
# 2. Push manifests
|
||||
for target in targets:
|
||||
run_command("docker", "manifest", "push", target)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -27,4 +27,12 @@ if [[ -d /build ]]; then
|
||||
export ESPHOME_BUILD_PATH=/build
|
||||
fi
|
||||
|
||||
# The default CMD is "dashboard /config". Route the dashboard to the new
|
||||
# Device Builder, but pass every other subcommand (compile, run, config,
|
||||
# logs, ...) straight through to the esphome CLI so direct CLI use keeps working.
|
||||
if [[ "$1" == "dashboard" ]]; then
|
||||
shift
|
||||
exec esphome-device-builder "$@"
|
||||
fi
|
||||
|
||||
exec esphome "$@"
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# ==============================================================================
|
||||
# Installs the latest prerelease of esphome-device-builder when the
|
||||
# `use_new_device_builder` config option is enabled.
|
||||
# This is a temporary install-on-boot step until esphome-device-builder
|
||||
# becomes a direct dependency of esphome.
|
||||
# ==============================================================================
|
||||
|
||||
if ! bashio::config.true 'use_new_device_builder'; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
bashio::log.info "Installing latest prerelease of esphome-device-builder..."
|
||||
if command -v uv > /dev/null; then
|
||||
uv pip install --system --no-cache-dir --prerelease=allow --upgrade \
|
||||
esphome-device-builder ||
|
||||
bashio::exit.nok "Failed installing esphome-device-builder."
|
||||
else
|
||||
pip install --no-cache-dir --pre --upgrade esphome-device-builder ||
|
||||
bashio::exit.nok "Failed installing esphome-device-builder."
|
||||
fi
|
||||
bashio::log.info "Installed esphome-device-builder."
|
||||
@@ -1,96 +0,0 @@
|
||||
types {
|
||||
text/html html htm shtml;
|
||||
text/css css;
|
||||
text/xml xml;
|
||||
image/gif gif;
|
||||
image/jpeg jpeg jpg;
|
||||
application/javascript js;
|
||||
application/atom+xml atom;
|
||||
application/rss+xml rss;
|
||||
|
||||
text/mathml mml;
|
||||
text/plain txt;
|
||||
text/vnd.sun.j2me.app-descriptor jad;
|
||||
text/vnd.wap.wml wml;
|
||||
text/x-component htc;
|
||||
|
||||
image/png png;
|
||||
image/svg+xml svg svgz;
|
||||
image/tiff tif tiff;
|
||||
image/vnd.wap.wbmp wbmp;
|
||||
image/webp webp;
|
||||
image/x-icon ico;
|
||||
image/x-jng jng;
|
||||
image/x-ms-bmp bmp;
|
||||
|
||||
font/woff woff;
|
||||
font/woff2 woff2;
|
||||
|
||||
application/java-archive jar war ear;
|
||||
application/json json;
|
||||
application/mac-binhex40 hqx;
|
||||
application/msword doc;
|
||||
application/pdf pdf;
|
||||
application/postscript ps eps ai;
|
||||
application/rtf rtf;
|
||||
application/vnd.apple.mpegurl m3u8;
|
||||
application/vnd.google-earth.kml+xml kml;
|
||||
application/vnd.google-earth.kmz kmz;
|
||||
application/vnd.ms-excel xls;
|
||||
application/vnd.ms-fontobject eot;
|
||||
application/vnd.ms-powerpoint ppt;
|
||||
application/vnd.oasis.opendocument.graphics odg;
|
||||
application/vnd.oasis.opendocument.presentation odp;
|
||||
application/vnd.oasis.opendocument.spreadsheet ods;
|
||||
application/vnd.oasis.opendocument.text odt;
|
||||
application/vnd.openxmlformats-officedocument.presentationml.presentation
|
||||
pptx;
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||
xlsx;
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
||||
docx;
|
||||
application/vnd.wap.wmlc wmlc;
|
||||
application/x-7z-compressed 7z;
|
||||
application/x-cocoa cco;
|
||||
application/x-java-archive-diff jardiff;
|
||||
application/x-java-jnlp-file jnlp;
|
||||
application/x-makeself run;
|
||||
application/x-perl pl pm;
|
||||
application/x-pilot prc pdb;
|
||||
application/x-rar-compressed rar;
|
||||
application/x-redhat-package-manager rpm;
|
||||
application/x-sea sea;
|
||||
application/x-shockwave-flash swf;
|
||||
application/x-stuffit sit;
|
||||
application/x-tcl tcl tk;
|
||||
application/x-x509-ca-cert der pem crt;
|
||||
application/x-xpinstall xpi;
|
||||
application/xhtml+xml xhtml;
|
||||
application/xspf+xml xspf;
|
||||
application/zip zip;
|
||||
|
||||
application/octet-stream bin exe dll;
|
||||
application/octet-stream deb;
|
||||
application/octet-stream dmg;
|
||||
application/octet-stream iso img;
|
||||
application/octet-stream msi msp msm;
|
||||
|
||||
audio/midi mid midi kar;
|
||||
audio/mpeg mp3;
|
||||
audio/ogg ogg;
|
||||
audio/x-m4a m4a;
|
||||
audio/x-realaudio ra;
|
||||
|
||||
video/3gpp 3gpp 3gp;
|
||||
video/mp2t ts;
|
||||
video/mp4 mp4;
|
||||
video/mpeg mpeg mpg;
|
||||
video/quicktime mov;
|
||||
video/webm webm;
|
||||
video/x-flv flv;
|
||||
video/x-m4v m4v;
|
||||
video/x-mng mng;
|
||||
video/x-ms-asf asx asf;
|
||||
video/x-ms-wmv wmv;
|
||||
video/x-msvideo avi;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
proxy_http_version 1.1;
|
||||
proxy_ignore_client_abort off;
|
||||
proxy_read_timeout 86400s;
|
||||
proxy_redirect off;
|
||||
proxy_send_timeout 86400s;
|
||||
proxy_max_temp_file_size 0;
|
||||
|
||||
proxy_set_header Accept-Encoding "";
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-NginX-Proxy true;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Authorization "";
|
||||
@@ -1,8 +0,0 @@
|
||||
root /dev/null;
|
||||
server_name $hostname;
|
||||
|
||||
client_max_body_size 512m;
|
||||
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Robots-Tag none;
|
||||
@@ -1,8 +0,0 @@
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers off;
|
||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
|
||||
ssl_session_timeout 10m;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_tickets off;
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
@@ -1,3 +0,0 @@
|
||||
upstream esphome {
|
||||
server unix:/var/run/esphome.sock;
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
daemon off;
|
||||
user root;
|
||||
pid /var/run/nginx.pid;
|
||||
worker_processes 1;
|
||||
error_log /proc/1/fd/1 error;
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/includes/mime.types;
|
||||
|
||||
access_log off;
|
||||
default_type application/octet-stream;
|
||||
gzip on;
|
||||
keepalive_timeout 65;
|
||||
sendfile on;
|
||||
server_tokens off;
|
||||
|
||||
tcp_nodelay on;
|
||||
tcp_nopush on;
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
include /etc/nginx/includes/upstream.conf;
|
||||
include /etc/nginx/servers/*.conf;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
Without requirements or design, programming is the art of adding bugs to an empty text file. (Louis Srygley)
|
||||
@@ -1,28 +0,0 @@
|
||||
server {
|
||||
{{ if not .ssl }}
|
||||
listen 6052 default_server;
|
||||
{{ else }}
|
||||
listen 6052 default_server ssl http2;
|
||||
{{ end }}
|
||||
|
||||
include /etc/nginx/includes/server_params.conf;
|
||||
include /etc/nginx/includes/proxy_params.conf;
|
||||
|
||||
{{ if .ssl }}
|
||||
include /etc/nginx/includes/ssl_params.conf;
|
||||
|
||||
ssl_certificate /ssl/{{ .certfile }};
|
||||
ssl_certificate_key /ssl/{{ .keyfile }};
|
||||
|
||||
# Redirect http requests to https on the same port.
|
||||
# https://rageagainstshell.com/2016/11/redirect-http-to-https-on-the-same-port-in-nginx/
|
||||
error_page 497 https://$http_host$request_uri;
|
||||
{{ end }}
|
||||
|
||||
# Clear Home Assistant Ingress header
|
||||
proxy_set_header X-HA-Ingress "";
|
||||
|
||||
location / {
|
||||
proxy_pass http://esphome;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
server {
|
||||
listen 127.0.0.1:{{ .port }} default_server;
|
||||
listen {{ .interface }}:{{ .port }} default_server;
|
||||
|
||||
include /etc/nginx/includes/server_params.conf;
|
||||
include /etc/nginx/includes/proxy_params.conf;
|
||||
|
||||
# Set Home Assistant Ingress header
|
||||
proxy_set_header X-HA-Ingress "YES";
|
||||
|
||||
location / {
|
||||
allow 172.30.32.2;
|
||||
allow 127.0.0.1;
|
||||
deny all;
|
||||
|
||||
proxy_pass http://esphome;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ fi
|
||||
|
||||
port=$(bashio::addon.ingress_port)
|
||||
|
||||
# Wait for NGINX to become available
|
||||
# Wait for the ESPHome Device Builder to become available
|
||||
bashio::net.wait_for "${port}" "127.0.0.1" 300
|
||||
|
||||
config=$(\
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# shellcheck shell=bash
|
||||
# ==============================================================================
|
||||
# Home Assistant Community Add-on: ESPHome
|
||||
# Take down the S6 supervision tree when ESPHome dashboard fails
|
||||
# Take down the S6 supervision tree when ESPHome Device Builder fails
|
||||
# ==============================================================================
|
||||
declare exit_code
|
||||
readonly exit_code_container=$(</run/s6-linux-init-container-results/exitcode)
|
||||
@@ -10,7 +10,7 @@ readonly exit_code_service="${1}"
|
||||
readonly exit_code_signal="${2}"
|
||||
|
||||
bashio::log.info \
|
||||
"Service ESPHome dashboard exited with code ${exit_code_service}" \
|
||||
"Service ESPHome Device Builder exited with code ${exit_code_service}" \
|
||||
"(by signal ${exit_code_signal})"
|
||||
|
||||
if [[ "${exit_code_service}" -eq 256 ]]; then
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# shellcheck shell=bash
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# Runs the ESPHome dashboard
|
||||
# Runs the ESPHome Device Builder
|
||||
# ==============================================================================
|
||||
readonly pio_cache_base=/data/cache/platformio
|
||||
|
||||
@@ -49,12 +49,21 @@ if bashio::fs.directory_exists '/config/esphome/.esphome'; then
|
||||
rm -rf /config/esphome/.esphome
|
||||
fi
|
||||
|
||||
if bashio::config.true 'use_new_device_builder'; then
|
||||
bashio::log.info "Starting ESPHome Device Builder..."
|
||||
exec esphome-device-builder /config/esphome \
|
||||
--ha-addon \
|
||||
--ingress-port "$(bashio::addon.ingress_port)"
|
||||
# Only signal device-builder to expose the public LAN port when the operator
|
||||
# mapped port 6052, matching the legacy dashboard where nginx listened on the
|
||||
# fixed port 6052 only when it was configured. We use the mapping purely as a
|
||||
# presence check and don't forward the published value; device-builder binds
|
||||
# its default port 6052 (the fixed container port, as the legacy
|
||||
# "listen 6052" did). --ha-addon-allow-public is inert on its own: the no-auth
|
||||
# gate is the DISABLE_HA_AUTHENTICATION env var set above, so both opt-ins are
|
||||
# required to bind 6052 unauthenticated; either alone stays ingress-only.
|
||||
set --
|
||||
if bashio::var.has_value "$(bashio::addon.port 6052)"; then
|
||||
set -- --ha-addon-allow-public
|
||||
fi
|
||||
|
||||
bashio::log.info "Starting ESPHome dashboard..."
|
||||
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --ha-addon
|
||||
bashio::log.info "Starting ESPHome Device Builder..."
|
||||
exec esphome-device-builder /config/esphome \
|
||||
--ha-addon \
|
||||
--ingress-port "$(bashio::addon.ingress_port)" \
|
||||
"$@"
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# Configures NGINX for use with ESPHome
|
||||
# ==============================================================================
|
||||
|
||||
# When the new device builder is enabled it serves HA ingress directly,
|
||||
# so nginx is not used at all -- skip configuration.
|
||||
if bashio::config.true 'use_new_device_builder'; then
|
||||
bashio::log.info "Skipping NGINX setup: new device builder serves ingress directly."
|
||||
bashio::exit.ok
|
||||
fi
|
||||
|
||||
mkdir -p /var/log/nginx
|
||||
|
||||
# Generate Ingress configuration
|
||||
bashio::var.json \
|
||||
interface "$(bashio::addon.ip_address)" \
|
||||
port "^$(bashio::addon.ingress_port)" \
|
||||
| tempio \
|
||||
-template /etc/nginx/templates/ingress.gtpl \
|
||||
-out /etc/nginx/servers/ingress.conf
|
||||
|
||||
# Generate direct access configuration, if enabled.
|
||||
if bashio::var.has_value "$(bashio::addon.port 6052)"; then
|
||||
bashio::config.require.ssl
|
||||
bashio::var.json \
|
||||
certfile "$(bashio::config 'certfile')" \
|
||||
keyfile "$(bashio::config 'keyfile')" \
|
||||
ssl "^$(bashio::config 'ssl')" \
|
||||
| tempio \
|
||||
-template /etc/nginx/templates/direct.gtpl \
|
||||
-out /etc/nginx/servers/direct.conf
|
||||
fi
|
||||
@@ -1 +0,0 @@
|
||||
oneshot
|
||||
@@ -1 +0,0 @@
|
||||
/etc/s6-overlay/s6-rc.d/init-nginx/run
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/command/with-contenv bashio
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# Take down the S6 supervision tree when NGINX fails
|
||||
# ==============================================================================
|
||||
declare exit_code
|
||||
readonly exit_code_container=$(</run/s6-linux-init-container-results/exitcode)
|
||||
readonly exit_code_service="${1}"
|
||||
readonly exit_code_signal="${2}"
|
||||
|
||||
bashio::log.info \
|
||||
"Service NGINX exited with code ${exit_code_service}" \
|
||||
"(by signal ${exit_code_signal})"
|
||||
|
||||
if [[ "${exit_code_service}" -eq 256 ]]; then
|
||||
if [[ "${exit_code_container}" -eq 0 ]]; then
|
||||
echo $((128 + $exit_code_signal)) > /run/s6-linux-init-container-results/exitcode
|
||||
fi
|
||||
[[ "${exit_code_signal}" -eq 15 ]] && exec /run/s6/basedir/bin/halt
|
||||
elif [[ "${exit_code_service}" -ne 0 ]]; then
|
||||
if [[ "${exit_code_container}" -eq 0 ]]; then
|
||||
echo "${exit_code_service}" > /run/s6-linux-init-container-results/exitcode
|
||||
fi
|
||||
exec /run/s6/basedir/bin/halt
|
||||
fi
|
||||
@@ -1,27 +0,0 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# Runs the NGINX proxy
|
||||
# ==============================================================================
|
||||
|
||||
# The new device builder handles HA ingress itself, so nginx is bypassed.
|
||||
# Block the longrun so s6 keeps the dependency satisfied, but exit 0 on
|
||||
# SIGTERM instead of being signal-killed; a 256/15 exit makes nginx/finish
|
||||
# stamp the container exit 143, which trips the Supervisor's SIGTERM check.
|
||||
if bashio::config.true 'use_new_device_builder'; then
|
||||
bashio::log.info "NGINX bypassed: new device builder serves ingress directly."
|
||||
trap 'exit 0' TERM
|
||||
sleep infinity &
|
||||
wait
|
||||
exit 0
|
||||
fi
|
||||
|
||||
bashio::log.info "Waiting for ESPHome dashboard to come up..."
|
||||
|
||||
while [[ ! -S /var/run/esphome.sock ]]; do
|
||||
sleep 0.5
|
||||
done
|
||||
|
||||
bashio::log.info "Starting NGINX..."
|
||||
exec nginx
|
||||
@@ -1 +0,0 @@
|
||||
longrun
|
||||
7
docker/test_configs/bk72xx-arduino.yaml
Normal file
7
docker/test_configs/bk72xx-arduino.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
esphome:
|
||||
name: docker-test-bk72xx-arduino
|
||||
|
||||
bk72xx:
|
||||
board: generic-bk7231n-qfn32-tuya
|
||||
|
||||
logger:
|
||||
10
docker/test_configs/esp32-arduino-esp-idf.yaml
Normal file
10
docker/test_configs/esp32-arduino-esp-idf.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
esphome:
|
||||
name: docker-test-esp32-ard-idf
|
||||
|
||||
esp32:
|
||||
variant: esp32
|
||||
framework:
|
||||
type: arduino
|
||||
toolchain: esp-idf
|
||||
|
||||
logger:
|
||||
10
docker/test_configs/esp32-arduino-platformio.yaml
Normal file
10
docker/test_configs/esp32-arduino-platformio.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
esphome:
|
||||
name: docker-test-esp32-ard-pio
|
||||
|
||||
esp32:
|
||||
variant: esp32
|
||||
framework:
|
||||
type: arduino
|
||||
toolchain: platformio
|
||||
|
||||
logger:
|
||||
10
docker/test_configs/esp32-idf-esp-idf.yaml
Normal file
10
docker/test_configs/esp32-idf-esp-idf.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
esphome:
|
||||
name: docker-test-esp32-idf-idf
|
||||
|
||||
esp32:
|
||||
variant: esp32
|
||||
framework:
|
||||
type: esp-idf
|
||||
toolchain: esp-idf
|
||||
|
||||
logger:
|
||||
10
docker/test_configs/esp32-idf-platformio.yaml
Normal file
10
docker/test_configs/esp32-idf-platformio.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
esphome:
|
||||
name: docker-test-esp32-idf-pio
|
||||
|
||||
esp32:
|
||||
variant: esp32
|
||||
framework:
|
||||
type: esp-idf
|
||||
toolchain: platformio
|
||||
|
||||
logger:
|
||||
7
docker/test_configs/esp8266-arduino.yaml
Normal file
7
docker/test_configs/esp8266-arduino.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
esphome:
|
||||
name: docker-test-esp8266-arduino
|
||||
|
||||
esp8266:
|
||||
board: d1_mini
|
||||
|
||||
logger:
|
||||
6
docker/test_configs/host.yaml
Normal file
6
docker/test_configs/host.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
esphome:
|
||||
name: docker-test-host
|
||||
|
||||
host:
|
||||
|
||||
logger:
|
||||
7
docker/test_configs/ln882x-arduino.yaml
Normal file
7
docker/test_configs/ln882x-arduino.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
esphome:
|
||||
name: docker-test-ln882x-arduino
|
||||
|
||||
ln882x:
|
||||
board: generic-ln882hki
|
||||
|
||||
logger:
|
||||
8
docker/test_configs/nrf52.yaml
Normal file
8
docker/test_configs/nrf52.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
esphome:
|
||||
name: docker-test-nrf52
|
||||
|
||||
nrf52:
|
||||
board: adafruit_itsybitsy_nrf52840
|
||||
bootloader: adafruit_nrf52_sd140_v6
|
||||
|
||||
logger:
|
||||
7
docker/test_configs/rp2040-arduino.yaml
Normal file
7
docker/test_configs/rp2040-arduino.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
esphome:
|
||||
name: docker-test-rp2040-arduino
|
||||
|
||||
rp2040:
|
||||
variant: rp2040
|
||||
|
||||
logger:
|
||||
7
docker/test_configs/rtl87xx-arduino.yaml
Normal file
7
docker/test_configs/rtl87xx-arduino.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
esphome:
|
||||
name: docker-test-rtl87xx-arduino
|
||||
|
||||
rtl87xx:
|
||||
board: generic-rtl8710bn-2mb-788k
|
||||
|
||||
logger:
|
||||
@@ -268,6 +268,36 @@ def _ota_hostnames_for_default(purpose: Purpose) -> list[str]:
|
||||
return _resolve_with_cache(CORE.address, purpose)
|
||||
|
||||
|
||||
def _unresolved_default_error(purpose: Purpose, defaults: list[str]) -> str:
|
||||
"""Build the error when a default device target produced no usable host.
|
||||
|
||||
When the OTA default was requested and the address resolves but the config
|
||||
lacks the transport the purpose needs (``api:`` for logs, an ``ota:``
|
||||
platform for uploads), name that gap instead of the misleading
|
||||
"could not be resolved" / set-use_address hint.
|
||||
"""
|
||||
if "OTA" in defaults and has_resolvable_address():
|
||||
if purpose == Purpose.LOGGING and not has_api():
|
||||
return (
|
||||
"Cannot view logs over the network: no 'api:' component is "
|
||||
"configured. Network log streaming requires the native API; add "
|
||||
"an 'api:' component, enable MQTT logging, or view logs over USB."
|
||||
)
|
||||
if purpose == Purpose.UPLOADING and not has_ota():
|
||||
return (
|
||||
"Cannot upload over the network: no 'ota:' platform is "
|
||||
"configured. Add an 'ota:' platform, or upload over USB."
|
||||
)
|
||||
if CORE.dashboard:
|
||||
hint = "If you know the IP, set 'use_address' in your network config."
|
||||
else:
|
||||
hint = "If you know the IP, try --device <IP>"
|
||||
return (
|
||||
f"All specified devices {defaults} could not be resolved. "
|
||||
f"Is the device connected to the network? {hint}"
|
||||
)
|
||||
|
||||
|
||||
def choose_upload_log_host(
|
||||
default: list[str] | str | None,
|
||||
check_default: str | None,
|
||||
@@ -317,14 +347,7 @@ def choose_upload_log_host(
|
||||
else:
|
||||
resolved.append(device)
|
||||
if not resolved:
|
||||
if CORE.dashboard:
|
||||
hint = "If you know the IP, set 'use_address' in your network config."
|
||||
else:
|
||||
hint = "If you know the IP, try --device <IP>"
|
||||
raise EsphomeError(
|
||||
f"All specified devices {defaults} could not be resolved. "
|
||||
f"Is the device connected to the network? {hint}"
|
||||
)
|
||||
raise EsphomeError(_unresolved_default_error(purpose, defaults))
|
||||
return resolved
|
||||
|
||||
# No devices specified, show interactive chooser
|
||||
@@ -504,6 +527,12 @@ def has_resolvable_address() -> bool:
|
||||
if has_ip_address():
|
||||
return True
|
||||
|
||||
# device-builder pre-resolves the device and passes the IPs via
|
||||
# --mdns-address-cache/--dns-address-cache; honor a cached address even when the
|
||||
# device has mDNS disabled (e.g. a .local host found via ping).
|
||||
if CORE.address_cache and CORE.address_cache.get_addresses(CORE.address):
|
||||
return True
|
||||
|
||||
if has_mdns():
|
||||
return True
|
||||
|
||||
@@ -1686,9 +1715,13 @@ def command_bundle(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
|
||||
|
||||
def command_dashboard(args: ArgsProtocol) -> int | None:
|
||||
from esphome.dashboard import dashboard
|
||||
|
||||
return dashboard.start_dashboard(args)
|
||||
raise EsphomeError(
|
||||
"The built-in dashboard has been removed from ESPHome. "
|
||||
"Install and run ESPHome Device Builder instead:\n"
|
||||
" pip install esphome-device-builder\n"
|
||||
" esphome-device-builder\n"
|
||||
"See https://github.com/esphome/device-builder for more information."
|
||||
)
|
||||
|
||||
|
||||
def run_multiple_configs(
|
||||
@@ -1765,6 +1798,21 @@ def command_update_all(args: ArgsProtocol) -> int | None:
|
||||
def command_idedata(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
import json
|
||||
|
||||
if CORE.using_toolchain_esp_idf:
|
||||
# Native ESP-IDF derives idedata from the build's compile_commands.json,
|
||||
# so the configuration must already be compiled.
|
||||
from esphome.espidf import toolchain as espidf_toolchain
|
||||
|
||||
idedata = espidf_toolchain.get_idedata()
|
||||
if idedata is None:
|
||||
_LOGGER.error(
|
||||
"No idedata available; compile the configuration first",
|
||||
)
|
||||
return 1
|
||||
|
||||
print(json.dumps(idedata, indent=2) + "\n")
|
||||
return 0
|
||||
|
||||
if not CORE.using_toolchain_platformio:
|
||||
_LOGGER.error(
|
||||
"The idedata command is not compatible with %s toolchain",
|
||||
@@ -2335,44 +2383,22 @@ def parse_args(argv):
|
||||
"configuration", help="Your YAML file or configuration directory.", nargs="*"
|
||||
)
|
||||
|
||||
parser_dashboard = subparsers.add_parser(
|
||||
"dashboard", help="Create a simple web server for a dashboard."
|
||||
# The dashboard moved to ESPHome Device Builder; the command is kept only to
|
||||
# print a redirect (see command_dashboard). Accept and ignore the old flags
|
||||
# so legacy invocations reach that message instead of failing on argparse
|
||||
# "unrecognized arguments".
|
||||
parser_dashboard = subparsers.add_parser("dashboard")
|
||||
parser_dashboard.add_argument("configuration", nargs="?", help=argparse.SUPPRESS)
|
||||
parser_dashboard.add_argument("--port", help=argparse.SUPPRESS)
|
||||
parser_dashboard.add_argument("--address", help=argparse.SUPPRESS)
|
||||
parser_dashboard.add_argument("--username", help=argparse.SUPPRESS)
|
||||
parser_dashboard.add_argument("--password", help=argparse.SUPPRESS)
|
||||
parser_dashboard.add_argument("--socket", help=argparse.SUPPRESS)
|
||||
parser_dashboard.add_argument(
|
||||
"--open-ui", action="store_true", help=argparse.SUPPRESS
|
||||
)
|
||||
parser_dashboard.add_argument(
|
||||
"configuration", help="Your YAML configuration file directory."
|
||||
)
|
||||
parser_dashboard.add_argument(
|
||||
"--port",
|
||||
help="The HTTP port to open connections on. Defaults to 6052.",
|
||||
type=int,
|
||||
default=6052,
|
||||
)
|
||||
parser_dashboard.add_argument(
|
||||
"--address",
|
||||
help="The address to bind to.",
|
||||
type=str,
|
||||
default="0.0.0.0",
|
||||
)
|
||||
parser_dashboard.add_argument(
|
||||
"--username",
|
||||
help="The optional username to require for authentication.",
|
||||
type=str,
|
||||
default="",
|
||||
)
|
||||
parser_dashboard.add_argument(
|
||||
"--password",
|
||||
help="The optional password to require for authentication.",
|
||||
type=str,
|
||||
default="",
|
||||
)
|
||||
parser_dashboard.add_argument(
|
||||
"--open-ui", help="Open the dashboard UI in a browser.", action="store_true"
|
||||
)
|
||||
parser_dashboard.add_argument(
|
||||
"--ha-addon", help=argparse.SUPPRESS, action="store_true"
|
||||
)
|
||||
parser_dashboard.add_argument(
|
||||
"--socket", help="Make the dashboard serve under a unix socket", type=str
|
||||
"--ha-addon", action="store_true", help=argparse.SUPPRESS
|
||||
)
|
||||
|
||||
parser_vscode = subparsers.add_parser("vscode")
|
||||
@@ -2467,11 +2493,7 @@ def run_esphome(argv):
|
||||
elif args.quiet:
|
||||
args.log_level = "CRITICAL"
|
||||
|
||||
setup_log(
|
||||
log_level=args.log_level,
|
||||
# Show timestamp for dashboard access logs
|
||||
include_timestamp=args.command == "dashboard",
|
||||
)
|
||||
setup_log(log_level=args.log_level)
|
||||
|
||||
if args.command in PRE_CONFIG_ACTIONS:
|
||||
try:
|
||||
|
||||
@@ -6,8 +6,20 @@ from pathlib import Path
|
||||
from esphome.components.esp32 import get_esp32_variant, idf_version
|
||||
import esphome.config_validation as cv
|
||||
from esphome.core import CORE
|
||||
from esphome.framework_helpers import get_project_compile_flags, get_project_link_flags
|
||||
from esphome.helpers import mkdir_p, write_file_if_changed
|
||||
|
||||
# Replaces the IDF default C++ standard (-std=gnu++2b appended to
|
||||
# CXX_COMPILE_OPTIONS by project.cmake's __build_init) with the one set via
|
||||
# cg.set_cpp_standard(). Emitted between include(project.cmake) and project(),
|
||||
# i.e. after IDF appends its default and before the options are consumed, and
|
||||
# applies project-wide like PlatformIO build_unflags.
|
||||
CPP_STANDARD_TEMPLATE = """\
|
||||
idf_build_get_property(esphome_cxx_compile_options CXX_COMPILE_OPTIONS)
|
||||
list(FILTER esphome_cxx_compile_options EXCLUDE REGEX "^-std=")
|
||||
list(APPEND esphome_cxx_compile_options "-std={standard}")
|
||||
idf_build_set_property(CXX_COMPILE_OPTIONS "${{esphome_cxx_compile_options}}")"""
|
||||
|
||||
|
||||
def get_available_components() -> list[str] | None:
|
||||
"""Get list of built-in ESP-IDF components from project_description.json.
|
||||
@@ -73,17 +85,18 @@ def get_project_cmakelists(minimal: bool = False) -> str:
|
||||
# esphome__micro-mp3) rather than just src/. Required so suppressions
|
||||
# like ``-Wno-error=maybe-uninitialized`` actually silence warnings in
|
||||
# third-party components we don't author.
|
||||
project_compile_opts = [
|
||||
flag
|
||||
for flag in sorted(CORE.build_flags)
|
||||
if flag.startswith("-D")
|
||||
or (flag.startswith("-W") and not flag.startswith("-Wl,"))
|
||||
]
|
||||
project_compile_opts = get_project_compile_flags()
|
||||
extra_compile_options = "\n".join(
|
||||
f'idf_build_set_property(COMPILE_OPTIONS "{flag}" APPEND)'
|
||||
for flag in project_compile_opts
|
||||
)
|
||||
|
||||
cpp_standard_options = (
|
||||
CPP_STANDARD_TEMPLATE.format(standard=CORE.cpp_standard)
|
||||
if CORE.cpp_standard
|
||||
else ""
|
||||
)
|
||||
|
||||
# Per-project list exposed as a CMake variable so converted PIO libs
|
||||
# can reference ${ESPHOME_PROJECT_MANAGED_COMPONENTS} without baking
|
||||
# project-specific names into their cached CMakeLists.
|
||||
@@ -140,6 +153,8 @@ set(EXTRA_COMPONENT_DIRS ${{CMAKE_SOURCE_DIR}}/src)
|
||||
|
||||
include($ENV{{IDF_PATH}}/tools/cmake/project.cmake)
|
||||
|
||||
{cpp_standard_options}
|
||||
|
||||
{extra_compile_options}
|
||||
|
||||
{managed_components_property}
|
||||
@@ -169,8 +184,8 @@ def get_component_cmakelists() -> str:
|
||||
# Extract linker options (-Wl, flags). Compile flags (-D, -W) are
|
||||
# emitted project-wide via idf_build_set_property in
|
||||
# get_project_cmakelists so they reach every component, not just src/.
|
||||
link_opts = [flag for flag in CORE.build_flags if flag.startswith("-Wl,")]
|
||||
link_opts_str = "\n ".join(sorted(link_opts)) if link_opts else ""
|
||||
link_opts = get_project_link_flags()
|
||||
link_opts_str = "\n ".join(link_opts) if link_opts else ""
|
||||
|
||||
return f"""\
|
||||
# Auto-generated by ESPHome
|
||||
@@ -200,9 +215,6 @@ idf_component_register(
|
||||
REQUIRES ${{ESPHOME_PROJECT_BUILTIN_COMPONENTS}}
|
||||
)
|
||||
|
||||
# Apply C++ standard
|
||||
target_compile_features(${{COMPONENT_LIB}} PUBLIC cxx_std_20)
|
||||
|
||||
# ESPHome linker options
|
||||
target_link_options(${{COMPONENT_LIB}} PUBLIC
|
||||
{link_opts_str}
|
||||
|
||||
@@ -33,12 +33,27 @@ def format_ini(data: dict[str, str | list[str]]) -> str:
|
||||
return content
|
||||
|
||||
|
||||
# All -std= variants a platform/framework may set by default, in both the GNU
|
||||
# and strict dialects; unflagged so the cg.set_cpp_standard() value is the
|
||||
# only standard left in the build.
|
||||
CPP_STD_VARIANTS = [
|
||||
f"{prefix}{year}"
|
||||
for year in ("11", "14", "17", "20", "23", "26", "2a", "2b", "2c")
|
||||
for prefix in ("gnu++", "c++")
|
||||
]
|
||||
|
||||
|
||||
def get_ini_content():
|
||||
CORE.add_platformio_option(
|
||||
"lib_deps",
|
||||
[x.as_lib_dep for x in CORE.platformio_libraries.values()]
|
||||
+ ["${common.lib_deps}"],
|
||||
)
|
||||
if CORE.cpp_standard:
|
||||
for variant in CPP_STD_VARIANTS:
|
||||
if variant != CORE.cpp_standard:
|
||||
CORE.add_build_unflag(f"-std={variant}")
|
||||
CORE.add_build_flag(f"-std={CORE.cpp_standard}")
|
||||
# Sort to avoid changing build flags order
|
||||
CORE.add_platformio_option("build_flags", sorted(CORE.build_flags))
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace esphome::a01nyub {
|
||||
|
||||
class A01nyubComponent : public sensor::Sensor, public Component, public uart::UARTDevice {
|
||||
class A01nyubComponent final : public sensor::Sensor, public Component, public uart::UARTDevice {
|
||||
public:
|
||||
// Nothing really public.
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace esphome::a02yyuw {
|
||||
|
||||
class A02yyuwComponent : public sensor::Sensor, public Component, public uart::UARTDevice {
|
||||
class A02yyuwComponent final : public sensor::Sensor, public Component, public uart::UARTDevice {
|
||||
public:
|
||||
// Nothing really public.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace esphome::a4988 {
|
||||
|
||||
class A4988 : public stepper::Stepper, public Component {
|
||||
class A4988 final : public stepper::Stepper, public Component {
|
||||
public:
|
||||
void set_step_pin(GPIOPin *step_pin) { step_pin_ = step_pin; }
|
||||
void set_dir_pin(GPIOPin *dir_pin) { dir_pin_ = dir_pin; }
|
||||
|
||||
@@ -13,7 +13,7 @@ enum SaturationVaporPressureEquation {
|
||||
};
|
||||
|
||||
/// This class implements calculation of absolute humidity from temperature and relative humidity.
|
||||
class AbsoluteHumidityComponent : public sensor::Sensor, public Component {
|
||||
class AbsoluteHumidityComponent final : public sensor::Sensor, public Component {
|
||||
public:
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
|
||||
|
||||
@@ -41,7 +41,7 @@ struct AcDimmerDataStore {
|
||||
#endif
|
||||
};
|
||||
|
||||
class AcDimmer : public output::FloatOutput, public Component {
|
||||
class AcDimmer final : public output::FloatOutput, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ template<typename T> class Aggregator {
|
||||
SamplingMode mode_{SamplingMode::AVG};
|
||||
};
|
||||
|
||||
class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
|
||||
class ADCSensor final : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
|
||||
public:
|
||||
/// Update the sensor's state by reading the current ADC value.
|
||||
/// This method is called periodically based on the update interval.
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
namespace esphome::adc128s102 {
|
||||
|
||||
class ADC128S102 : public Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_10MHZ> {
|
||||
class ADC128S102 final : public Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_10MHZ> {
|
||||
public:
|
||||
ADC128S102() = default;
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
|
||||
namespace esphome::adc128s102 {
|
||||
|
||||
class ADC128S102Sensor : public PollingComponent,
|
||||
public Parented<ADC128S102>,
|
||||
public sensor::Sensor,
|
||||
public voltage_sampler::VoltageSampler {
|
||||
class ADC128S102Sensor final : public PollingComponent,
|
||||
public Parented<ADC128S102>,
|
||||
public sensor::Sensor,
|
||||
public voltage_sampler::VoltageSampler {
|
||||
public:
|
||||
ADC128S102Sensor(uint8_t channel);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
namespace esphome::addressable_light {
|
||||
|
||||
class AddressableLightDisplay : public display::DisplayBuffer {
|
||||
class AddressableLightDisplay final : public display::DisplayBuffer {
|
||||
public:
|
||||
light::AddressableLight *get_light() const { return this->light_; }
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ struct ADE7880Store {
|
||||
static void gpio_intr(ADE7880Store *arg);
|
||||
};
|
||||
|
||||
class ADE7880 : public i2c::I2CDevice, public PollingComponent {
|
||||
class ADE7880 final : public i2c::I2CDevice, public PollingComponent {
|
||||
public:
|
||||
void set_irq0_pin(InternalGPIOPin *pin) { this->irq0_pin_ = pin; }
|
||||
void set_irq1_pin(InternalGPIOPin *pin) { this->irq1_pin_ = pin; }
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
namespace esphome::ade7953_i2c {
|
||||
|
||||
class AdE7953I2c : public ade7953_base::ADE7953, public i2c::I2CDevice {
|
||||
class AdE7953I2c final : public ade7953_base::ADE7953, public i2c::I2CDevice {
|
||||
public:
|
||||
void dump_config() override;
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ enum ADS1115Samplerate {
|
||||
ADS1115_860SPS = 0b111
|
||||
};
|
||||
|
||||
class ADS1115Component : public Component, public i2c::I2CDevice {
|
||||
class ADS1115Component final : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
namespace esphome::ads1115 {
|
||||
|
||||
/// Internal holder class that is in instance of Sensor so that the hub can create individual sensors.
|
||||
class ADS1115Sensor : public sensor::Sensor,
|
||||
public PollingComponent,
|
||||
public voltage_sampler::VoltageSampler,
|
||||
public Parented<ADS1115Component> {
|
||||
class ADS1115Sensor final : public sensor::Sensor,
|
||||
public PollingComponent,
|
||||
public voltage_sampler::VoltageSampler,
|
||||
public Parented<ADS1115Component> {
|
||||
public:
|
||||
void update() override;
|
||||
void set_multiplexer(ADS1115Multiplexer multiplexer) { this->multiplexer_ = multiplexer; }
|
||||
|
||||
@@ -26,9 +26,9 @@ enum ADS1118Gain {
|
||||
ADS1118_GAIN_0P256 = 0b101,
|
||||
};
|
||||
|
||||
class ADS1118 : public Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_TRAILING,
|
||||
spi::DATA_RATE_1MHZ> {
|
||||
class ADS1118 final : public Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_1MHZ> {
|
||||
public:
|
||||
ADS1118() = default;
|
||||
void setup() override;
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
|
||||
namespace esphome::ads1118 {
|
||||
|
||||
class ADS1118Sensor : public PollingComponent,
|
||||
public sensor::Sensor,
|
||||
public voltage_sampler::VoltageSampler,
|
||||
public Parented<ADS1118> {
|
||||
class ADS1118Sensor final : public PollingComponent,
|
||||
public sensor::Sensor,
|
||||
public voltage_sampler::VoltageSampler,
|
||||
public Parented<ADS1118> {
|
||||
public:
|
||||
void update() override;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace esphome::ags10 {
|
||||
|
||||
class AGS10Component : public PollingComponent, public i2c::I2CDevice {
|
||||
class AGS10Component final : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
/**
|
||||
* Sets TVOC sensor.
|
||||
@@ -100,7 +100,7 @@ class AGS10Component : public PollingComponent, public i2c::I2CDevice {
|
||||
template<size_t N> optional<std::array<uint8_t, N>> read_and_check_(uint8_t a_register);
|
||||
};
|
||||
|
||||
template<typename... Ts> class AGS10NewI2cAddressAction : public Action<Ts...>, public Parented<AGS10Component> {
|
||||
template<typename... Ts> class AGS10NewI2cAddressAction final : public Action<Ts...>, public Parented<AGS10Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, new_address)
|
||||
|
||||
@@ -116,7 +116,7 @@ enum AGS10SetZeroPointActionMode {
|
||||
CUSTOM_VALUE,
|
||||
};
|
||||
|
||||
template<typename... Ts> class AGS10SetZeroPointAction : public Action<Ts...>, public Parented<AGS10Component> {
|
||||
template<typename... Ts> class AGS10SetZeroPointAction final : public Action<Ts...>, public Parented<AGS10Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, value)
|
||||
TEMPLATABLE_VALUE(AGS10SetZeroPointActionMode, mode)
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace esphome::aht10 {
|
||||
|
||||
enum AHT10Variant { AHT10, AHT20 };
|
||||
|
||||
class AHT10Component : public PollingComponent, public i2c::I2CDevice {
|
||||
class AHT10Component final : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void update() override;
|
||||
|
||||
@@ -61,7 +61,7 @@ static const uint8_t AIC3204_ADC_PTM = 0x3D; // Register 61 - ADC Power Tu
|
||||
static const uint8_t AIC3204_AN_IN_CHRG = 0x47; // Register 71 - Analog Input Quick Charging Config
|
||||
static const uint8_t AIC3204_REF_STARTUP = 0x7B; // Register 123 - Reference Power Up Config
|
||||
|
||||
class AIC3204 : public audio_dac::AudioDac, public Component, public i2c::I2CDevice {
|
||||
class AIC3204 final : public audio_dac::AudioDac, public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace esphome::aic3204 {
|
||||
|
||||
template<typename... Ts> class SetAutoMuteAction : public Action<Ts...> {
|
||||
template<typename... Ts> class SetAutoMuteAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit SetAutoMuteAction(AIC3204 *aic3204) : aic3204_(aic3204) {}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace esphome::airthings_ble {
|
||||
|
||||
class AirthingsListener : public esp32_ble_tracker::ESPBTDeviceListener {
|
||||
class AirthingsListener final : public esp32_ble_tracker::ESPBTDeviceListener {
|
||||
public:
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ static const char *const SERVICE_UUID = "b42e3882-ade7-11e4-89d3-123b93f75cba";
|
||||
static const char *const CHARACTERISTIC_UUID = "b42e3b98-ade7-11e4-89d3-123b93f75cba";
|
||||
static const char *const ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID = "b42e3ef4-ade7-11e4-89d3-123b93f75cba";
|
||||
|
||||
class AirthingsWaveMini : public airthings_wave_base::AirthingsWaveBase {
|
||||
class AirthingsWaveMini final : public airthings_wave_base::AirthingsWaveBase {
|
||||
public:
|
||||
AirthingsWaveMini();
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ static const char *const CHARACTERISTIC_UUID_WAVE_RADON_GEN2 = "b42e4dcc-ade7-11
|
||||
static const char *const ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID_WAVE_RADON_GEN2 =
|
||||
"b42e50d8-ade7-11e4-89d3-123b93f75cba";
|
||||
|
||||
class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase {
|
||||
class AirthingsWavePlus final : public airthings_wave_base::AirthingsWaveBase {
|
||||
public:
|
||||
void setup() override;
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ static_assert(std::is_trivially_copyable_v<StateAnyForwarder>);
|
||||
static_assert(sizeof(StateEnterForwarder<ACP_STATE_TRIGGERED>) <= sizeof(void *));
|
||||
static_assert(std::is_trivially_copyable_v<StateEnterForwarder<ACP_STATE_TRIGGERED>>);
|
||||
|
||||
template<typename... Ts> class ArmAwayAction : public Action<Ts...> {
|
||||
template<typename... Ts> class ArmAwayAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit ArmAwayAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
|
||||
|
||||
@@ -39,7 +39,7 @@ template<typename... Ts> class ArmAwayAction : public Action<Ts...> {
|
||||
AlarmControlPanel *alarm_control_panel_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ArmHomeAction : public Action<Ts...> {
|
||||
template<typename... Ts> class ArmHomeAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit ArmHomeAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
|
||||
|
||||
@@ -51,7 +51,7 @@ template<typename... Ts> class ArmHomeAction : public Action<Ts...> {
|
||||
AlarmControlPanel *alarm_control_panel_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ArmNightAction : public Action<Ts...> {
|
||||
template<typename... Ts> class ArmNightAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit ArmNightAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
|
||||
|
||||
@@ -63,7 +63,7 @@ template<typename... Ts> class ArmNightAction : public Action<Ts...> {
|
||||
AlarmControlPanel *alarm_control_panel_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class DisarmAction : public Action<Ts...> {
|
||||
template<typename... Ts> class DisarmAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit DisarmAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
|
||||
|
||||
@@ -75,7 +75,7 @@ template<typename... Ts> class DisarmAction : public Action<Ts...> {
|
||||
AlarmControlPanel *alarm_control_panel_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class PendingAction : public Action<Ts...> {
|
||||
template<typename... Ts> class PendingAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit PendingAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
|
||||
|
||||
@@ -85,7 +85,7 @@ template<typename... Ts> class PendingAction : public Action<Ts...> {
|
||||
AlarmControlPanel *alarm_control_panel_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class TriggeredAction : public Action<Ts...> {
|
||||
template<typename... Ts> class TriggeredAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit TriggeredAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
|
||||
|
||||
@@ -95,7 +95,7 @@ template<typename... Ts> class TriggeredAction : public Action<Ts...> {
|
||||
AlarmControlPanel *alarm_control_panel_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class AlarmControlPanelCondition : public Condition<Ts...> {
|
||||
template<typename... Ts> class AlarmControlPanelCondition final : public Condition<Ts...> {
|
||||
public:
|
||||
AlarmControlPanelCondition(AlarmControlPanel *parent) : parent_(parent) {}
|
||||
bool check(const Ts &...x) override {
|
||||
|
||||
@@ -31,7 +31,7 @@ static const int16_t GENI_RESPONSE_POWER_OFFSET = 12;
|
||||
static const int16_t GENI_RESPONSE_MOTOR_POWER_OFFSET = 16; // not sure
|
||||
static const int16_t GENI_RESPONSE_MOTOR_SPEED_OFFSET = 20;
|
||||
|
||||
class Alpha3 : public esphome::ble_client::BLEClientNode, public PollingComponent {
|
||||
class Alpha3 final : public esphome::ble_client::BLEClientNode, public PollingComponent {
|
||||
public:
|
||||
void setup() override;
|
||||
void update() override;
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
namespace esphome::am2315c {
|
||||
|
||||
class AM2315C : public PollingComponent, public i2c::I2CDevice {
|
||||
class AM2315C final : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace esphome::am2320 {
|
||||
|
||||
class AM2320Component : public PollingComponent, public i2c::I2CDevice {
|
||||
class AM2320Component final : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace esphome::am43 {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
class Am43Component : public cover::Cover, public esphome::ble_client::BLEClientNode, public Component {
|
||||
class Am43Component final : public cover::Cover, public esphome::ble_client::BLEClientNode, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace esphome::am43 {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
class Am43 : public esphome::ble_client::BLEClientNode, public PollingComponent {
|
||||
class Am43 final : public esphome::ble_client::BLEClientNode, public PollingComponent {
|
||||
public:
|
||||
void setup() override;
|
||||
void update() override;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace esphome::analog_threshold {
|
||||
|
||||
class AnalogThresholdBinarySensor : public Component, public binary_sensor::BinarySensor {
|
||||
class AnalogThresholdBinarySensor final : public Component, public binary_sensor::BinarySensor {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void setup() override;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome::animation {
|
||||
|
||||
class Animation : public image::Image {
|
||||
class Animation final : public image::Image {
|
||||
public:
|
||||
Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, image::ImageType type,
|
||||
image::Transparency transparent);
|
||||
@@ -35,7 +35,7 @@ class Animation : public image::Image {
|
||||
int loop_current_iteration_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class AnimationNextFrameAction : public Action<Ts...> {
|
||||
template<typename... Ts> class AnimationNextFrameAction final : public Action<Ts...> {
|
||||
public:
|
||||
AnimationNextFrameAction(Animation *parent) : parent_(parent) {}
|
||||
void play(const Ts &...x) override { this->parent_->next_frame(); }
|
||||
@@ -44,7 +44,7 @@ template<typename... Ts> class AnimationNextFrameAction : public Action<Ts...> {
|
||||
Animation *parent_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class AnimationPrevFrameAction : public Action<Ts...> {
|
||||
template<typename... Ts> class AnimationPrevFrameAction final : public Action<Ts...> {
|
||||
public:
|
||||
AnimationPrevFrameAction(Animation *parent) : parent_(parent) {}
|
||||
void play(const Ts &...x) override { this->parent_->prev_frame(); }
|
||||
@@ -53,7 +53,7 @@ template<typename... Ts> class AnimationPrevFrameAction : public Action<Ts...> {
|
||||
Animation *parent_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class AnimationSetFrameAction : public Action<Ts...> {
|
||||
template<typename... Ts> class AnimationSetFrameAction final : public Action<Ts...> {
|
||||
public:
|
||||
AnimationSetFrameAction(Animation *parent) : parent_(parent) {}
|
||||
TEMPLATABLE_VALUE(uint16_t, frame)
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace espbt = esphome::esp32_ble_tracker;
|
||||
static const uint16_t ANOVA_SERVICE_UUID = 0xFFE0;
|
||||
static const uint16_t ANOVA_CHARACTERISTIC_UUID = 0xFFE1;
|
||||
|
||||
class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode, public PollingComponent {
|
||||
class Anova final : public climate::Climate, public esphome::ble_client::BLEClientNode, public PollingComponent {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
|
||||
@@ -39,7 +39,7 @@ enum AmbientLightGain : uint8_t {
|
||||
};
|
||||
static const uint8_t AMBIENT_LIGHT_GAIN_VALUES[] = {1, 3, 6, 9, 18};
|
||||
|
||||
class APDS9306 : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
|
||||
class APDS9306 final : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
float get_setup_priority() const override { return setup_priority::BUS; }
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
namespace esphome::apds9960 {
|
||||
|
||||
class APDS9960 : public PollingComponent, public i2c::I2CDevice {
|
||||
class APDS9960 final : public PollingComponent, public i2c::I2CDevice {
|
||||
#ifdef USE_SENSOR
|
||||
SUB_SENSOR(red)
|
||||
SUB_SENSOR(green)
|
||||
|
||||
@@ -375,7 +375,7 @@ void APIConnection::finalize_iterator_sync_() {
|
||||
|
||||
void APIConnection::process_iterator_batch_(ComponentIterator &iterator) {
|
||||
size_t initial_size = this->deferred_batch_.size();
|
||||
size_t max_batch = this->get_max_batch_size_();
|
||||
size_t max_batch = MAX_INITIAL_PER_BATCH;
|
||||
while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < max_batch) {
|
||||
iterator.advance();
|
||||
}
|
||||
@@ -418,16 +418,6 @@ uint16_t APIConnection::fill_and_encode_entity_info(EntityBase *entity, InfoResp
|
||||
// Set common fields that are shared by all entity types
|
||||
msg.key = entity->get_object_id_hash();
|
||||
|
||||
// API 1.14+ clients compute object_id client-side from the entity name
|
||||
// For older clients, we must send object_id for backward compatibility
|
||||
// See: https://github.com/esphome/backlog/issues/76
|
||||
// TODO: Remove this backward compat code before 2026.7.0 - all clients should support API 1.14 by then
|
||||
// Buffer must remain in scope until encode_to_buffer is called
|
||||
char object_id_buf[OBJECT_ID_MAX_LEN];
|
||||
if (!conn->client_supports_api_version(1, 14)) {
|
||||
msg.object_id = entity->get_object_id_to(object_id_buf);
|
||||
}
|
||||
|
||||
if (entity->has_own_name()) {
|
||||
msg.name = entity->get_name();
|
||||
}
|
||||
|
||||
@@ -43,10 +43,7 @@ class APIServer;
|
||||
// Keepalive timeout in milliseconds
|
||||
static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000;
|
||||
// Maximum number of entities to process in a single batch during initial state/info sending
|
||||
// API 1.14+ clients compute object_id client-side, so messages are smaller and we can fit more per batch
|
||||
// TODO: Remove MAX_INITIAL_PER_BATCH_LEGACY before 2026.7.0 - all clients should support API 1.14 by then
|
||||
static constexpr size_t MAX_INITIAL_PER_BATCH_LEGACY = 24; // For clients < API 1.14 (includes object_id)
|
||||
static constexpr size_t MAX_INITIAL_PER_BATCH = 34; // For clients >= API 1.14 (no object_id)
|
||||
static constexpr size_t MAX_INITIAL_PER_BATCH = 34;
|
||||
// Verify MAX_MESSAGES_PER_BATCH (defined in api_frame_helper.h) can hold the initial batch
|
||||
static_assert(MAX_MESSAGES_PER_BATCH >= MAX_INITIAL_PER_BATCH,
|
||||
"MAX_MESSAGES_PER_BATCH must be >= MAX_INITIAL_PER_BATCH");
|
||||
@@ -481,13 +478,6 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
inline bool check_voice_assistant_api_connection_() const;
|
||||
#endif
|
||||
|
||||
// Get the max batch size based on client API version
|
||||
// API 1.14+ clients don't receive object_id, so messages are smaller and more fit per batch
|
||||
// TODO: Remove this method before 2026.7.0 and use MAX_INITIAL_PER_BATCH directly
|
||||
size_t get_max_batch_size_() const {
|
||||
return this->client_supports_api_version(1, 14) ? MAX_INITIAL_PER_BATCH : MAX_INITIAL_PER_BATCH_LEGACY;
|
||||
}
|
||||
|
||||
// Send keepalive ping or disconnect unresponsive client.
|
||||
// Cold path — extracted from loop() to reduce instruction cache pressure.
|
||||
void __attribute__((noinline)) check_keepalive_(uint32_t now);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user