mirror of
https://github.com/esphome/esphome.git
synced 2026-09-20 03:28:41 +00:00
Merge branch 'analyze-memory-dedup-aliased-ram-symbols' into integration
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
ARG BUILD_BASE_VERSION=2025.04.0
|
||||
ARG BUILD_BASE_VERSION=2026.06.1
|
||||
|
||||
|
||||
FROM ghcr.io/esphome/docker-base:debian-${BUILD_BASE_VERSION} AS base
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
// uncomment and edit the path in order to pass through local USB serial to the container
|
||||
// , "--device=/dev/ttyACM0"
|
||||
],
|
||||
"appPort": 6052,
|
||||
// if you are using avahi in the host device, uncomment these to allow the
|
||||
// devcontainer to find devices via mdns
|
||||
//"mounts": [
|
||||
@@ -41,7 +40,11 @@
|
||||
],
|
||||
"settings": {
|
||||
"python.languageServer": "Pylance",
|
||||
"python.pythonPath": "/usr/bin/python3",
|
||||
// Use the container's pre-provisioned venv (built by the Dockerfile, outside the
|
||||
// bind-mounted workspace) rather than a ./venv that may leak in from the host and
|
||||
// mismatch the container's Python. See .devcontainer/Dockerfile (esphome-venv).
|
||||
"python.defaultInterpreterPath": "/home/esphome/.local/esphome-venv/bin/python",
|
||||
"python.terminal.activateEnvironment": true,
|
||||
"pylint.args": [
|
||||
"--rcfile=${workspaceFolder}/pyproject.toml"
|
||||
],
|
||||
|
||||
@@ -42,7 +42,7 @@ runs:
|
||||
|
||||
- name: Build and push to ghcr by digest
|
||||
id: build-ghcr
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
@@ -67,7 +67,7 @@ runs:
|
||||
|
||||
- name: Build and push to dockerhub by digest
|
||||
id: build-dockerhub
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
name: Cache nRF Connect SDK
|
||||
description: >
|
||||
Resolve the pinned sdk-nrf version and cache the native sdk-nrf install
|
||||
(west workspace, Zephyr SDK toolchain, python env) at ~/.esphome-sdk-nrf.
|
||||
Every job that installs sdk-nrf natively (the nrf52 clang-tidy job and,
|
||||
once the component tests build natively, their batches) shares one cache.
|
||||
Callers must set env ESPHOME_SDK_NRF_PREFIX: ~/.esphome-sdk-nrf and have
|
||||
the Python venv already restored.
|
||||
inputs:
|
||||
restore-only:
|
||||
description: >
|
||||
When "true", only restore -- never save the cache, even on dev. Use from
|
||||
jobs that may not produce a complete install (e.g. a component batch
|
||||
that fails mid-install), so a partial install is never written.
|
||||
default: "false"
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Resolve sdk-nrf and toolchain versions for cache key
|
||||
# Both versions are pinned in code, not in any file that feeds the
|
||||
# other cache keys, so resolve them explicitly. Keying on them means
|
||||
# the cache invalidates when either is bumped (actions/cache never
|
||||
# overwrites a key).
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
version=$(python -c '
|
||||
from esphome.components.nrf52 import RECOMMENDED_SDK_NRF_VERSION
|
||||
from esphome.components.nrf52.framework import TOOLCHAIN_VERSION
|
||||
print(f"{RECOMMENDED_SDK_NRF_VERSION}-{TOOLCHAIN_VERSION}")')
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
# Mirror cache-esp-idf: only dev-branch runs write the shared cache (so it
|
||||
# lives in the default-branch scope readable by all PRs); PRs are
|
||||
# restore-only and never push multi-GB artifacts into their own scope.
|
||||
- name: Cache nRF Connect SDK install (write on dev)
|
||||
if: github.ref == 'refs/heads/dev' && inputs.restore-only != 'true'
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ~/.esphome-sdk-nrf
|
||||
# yamllint disable-line rule:line-length
|
||||
key: ${{ runner.os }}-esphome-sdk-nrf-${{ steps.version.outputs.version }}-${{ hashFiles('esphome/components/nrf52/requirements.txt') }}
|
||||
- name: Cache nRF Connect SDK install (restore-only off dev)
|
||||
if: github.ref != 'refs/heads/dev' || inputs.restore-only == 'true'
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ~/.esphome-sdk-nrf
|
||||
# yamllint disable-line rule:line-length
|
||||
key: ${{ runner.os }}-esphome-sdk-nrf-${{ steps.version.outputs.version }}-${{ hashFiles('esphome/components/nrf52/requirements.txt') }}
|
||||
@@ -17,12 +17,12 @@ runs:
|
||||
steps:
|
||||
- name: Set up Python ${{ inputs.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: venv
|
||||
# yamllint disable-line rule:line-length
|
||||
|
||||
@@ -147,19 +147,9 @@ async function detectCoreChanges(changedFiles) {
|
||||
}
|
||||
|
||||
// Strategy: PR size detection
|
||||
async function detectPRSize(prFiles, totalAdditions, totalDeletions, totalChanges, isMegaPR, SMALL_PR_THRESHOLD, MEDIUM_PR_THRESHOLD, TOO_BIG_THRESHOLD) {
|
||||
async function detectPRSize(prFiles, totalAdditions, totalDeletions, isMegaPR, SMALL_PR_THRESHOLD, MEDIUM_PR_THRESHOLD, TOO_BIG_THRESHOLD) {
|
||||
const labels = new Set();
|
||||
|
||||
if (totalChanges <= SMALL_PR_THRESHOLD) {
|
||||
labels.add('small-pr');
|
||||
return labels;
|
||||
}
|
||||
|
||||
if (totalChanges <= MEDIUM_PR_THRESHOLD) {
|
||||
labels.add('medium-pr');
|
||||
return labels;
|
||||
}
|
||||
|
||||
const testAdditions = prFiles
|
||||
.filter(file => file.filename.startsWith('tests/'))
|
||||
.reduce((sum, file) => sum + (file.additions || 0), 0);
|
||||
@@ -167,7 +157,24 @@ async function detectPRSize(prFiles, totalAdditions, totalDeletions, totalChange
|
||||
.filter(file => file.filename.startsWith('tests/'))
|
||||
.reduce((sum, file) => sum + (file.deletions || 0), 0);
|
||||
|
||||
const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
|
||||
const nonTestAdditions = totalAdditions - testAdditions;
|
||||
const nonTestDeletions = totalDeletions - testDeletions;
|
||||
|
||||
// small/medium count churn (additions + deletions) so a balanced refactor isn't undersized.
|
||||
const nonTestChurn = nonTestAdditions + nonTestDeletions;
|
||||
|
||||
if (nonTestChurn <= SMALL_PR_THRESHOLD) {
|
||||
labels.add('small-pr');
|
||||
return labels;
|
||||
}
|
||||
|
||||
if (nonTestChurn <= MEDIUM_PR_THRESHOLD) {
|
||||
labels.add('medium-pr');
|
||||
return labels;
|
||||
}
|
||||
|
||||
// too-big uses net line delta (additions - deletions), matching the review message in reviews.js.
|
||||
const nonTestChanges = nonTestAdditions - nonTestDeletions;
|
||||
|
||||
// Don't add too-big if mega-pr label is already present
|
||||
if (nonTestChanges > TOO_BIG_THRESHOLD && !isMegaPR) {
|
||||
|
||||
@@ -123,7 +123,7 @@ module.exports = async ({ github, context }) => {
|
||||
detectNewComponents(github, context, prFiles),
|
||||
detectNewPlatforms(github, context, prFiles, apiData),
|
||||
detectCoreChanges(changedFiles),
|
||||
detectPRSize(prFiles, totalAdditions, totalDeletions, totalChanges, isMegaPR, SMALL_PR_THRESHOLD, MEDIUM_PR_THRESHOLD, TOO_BIG_THRESHOLD),
|
||||
detectPRSize(prFiles, totalAdditions, totalDeletions, isMegaPR, SMALL_PR_THRESHOLD, MEDIUM_PR_THRESHOLD, TOO_BIG_THRESHOLD),
|
||||
detectDashboardChanges(changedFiles),
|
||||
detectGitHubActionsChanges(changedFiles),
|
||||
detectCodeOwner(github, context, changedFiles),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const { describe, it } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const { detectNewPlatforms, detectNewComponents } = require('../detectors');
|
||||
const { detectNewPlatforms, detectNewComponents, detectPRSize } = require('../detectors');
|
||||
|
||||
// Minimal GitHub API mock — only repos.getContent is called by detectNewPlatforms/detectNewComponents
|
||||
// to check for CONFIG_SCHEMA in newly added files.
|
||||
@@ -145,3 +145,79 @@ describe('detectNewComponents', () => {
|
||||
assert.equal(result.labels.size, 0);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// detectPRSize
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('detectPRSize', () => {
|
||||
const SMALL = 30;
|
||||
const MEDIUM = 100;
|
||||
const TOO_BIG = 1000;
|
||||
|
||||
function size(prFiles, isMegaPR = false) {
|
||||
const totalAdditions = prFiles.reduce((sum, file) => sum + (file.additions || 0), 0);
|
||||
const totalDeletions = prFiles.reduce((sum, file) => sum + (file.deletions || 0), 0);
|
||||
return detectPRSize(prFiles, totalAdditions, totalDeletions, isMegaPR, SMALL, MEDIUM, TOO_BIG);
|
||||
}
|
||||
|
||||
it('counts only non-test changes toward small-pr', async () => {
|
||||
// 10 source + 5000 test lines -> non-test churn of 10 is still small.
|
||||
const labels = await size([
|
||||
{ filename: 'esphome/components/foo/foo.cpp', additions: 10, deletions: 0 },
|
||||
{ filename: 'tests/components/foo/test.esp32-idf.yaml', additions: 5000, deletions: 0 },
|
||||
]);
|
||||
assert.ok(labels.has('small-pr'));
|
||||
assert.equal(labels.size, 1);
|
||||
});
|
||||
|
||||
it('counts additions and deletions as churn (not net delta)', async () => {
|
||||
// A balanced refactor (40 added, 40 removed) is 80 lines of churn -> medium, not small.
|
||||
const labels = await size([
|
||||
{ filename: 'esphome/components/foo/foo.cpp', additions: 40, deletions: 40 },
|
||||
]);
|
||||
assert.ok(labels.has('medium-pr'));
|
||||
assert.equal(labels.size, 1);
|
||||
});
|
||||
|
||||
it('labels medium-pr when non-test changes exceed small threshold', async () => {
|
||||
const labels = await size([
|
||||
{ filename: 'esphome/components/foo/foo.cpp', additions: 60, deletions: 0 },
|
||||
{ filename: 'tests/components/foo/test.esp32-idf.yaml', additions: 5000, deletions: 0 },
|
||||
]);
|
||||
assert.ok(labels.has('medium-pr'));
|
||||
assert.equal(labels.size, 1);
|
||||
});
|
||||
|
||||
it('uses net delta (not churn) for too-big', async () => {
|
||||
// 600 added + 600 removed: 1200 churn (above too-big) but 0 net delta -> not too-big.
|
||||
const labels = await size([
|
||||
{ filename: 'esphome/components/foo/foo.cpp', additions: 600, deletions: 600 },
|
||||
]);
|
||||
assert.equal(labels.size, 0);
|
||||
});
|
||||
|
||||
it('labels too-big when non-test changes exceed the big threshold', async () => {
|
||||
const labels = await size([
|
||||
{ filename: 'esphome/components/foo/foo.cpp', additions: 2000, deletions: 0 },
|
||||
{ filename: 'tests/components/foo/test.esp32-idf.yaml', additions: 5000, deletions: 0 },
|
||||
]);
|
||||
assert.ok(labels.has('too-big'));
|
||||
assert.equal(labels.size, 1);
|
||||
});
|
||||
|
||||
it('does not label too-big when mega-pr is set', async () => {
|
||||
const labels = await size([
|
||||
{ filename: 'esphome/components/foo/foo.cpp', additions: 2000, deletions: 0 },
|
||||
], true);
|
||||
assert.equal(labels.size, 0);
|
||||
});
|
||||
|
||||
it('produces no size label for a large mega-pr in the gap above medium', async () => {
|
||||
// Non-test changes land between MEDIUM and TOO_BIG: not small/medium, and mega-pr suppresses too-big.
|
||||
const labels = await size([
|
||||
{ filename: 'esphome/components/foo/foo.cpp', additions: 500, deletions: 0 },
|
||||
], true);
|
||||
assert.equal(labels.size, 0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,7 +41,6 @@ function hasCoreChanges(changedFiles) {
|
||||
*/
|
||||
function hasDashboardChanges(changedFiles) {
|
||||
return changedFiles.some(file =>
|
||||
file.startsWith('esphome/dashboard/') ||
|
||||
file.startsWith('esphome/components/dashboard_import/')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
python-version: "3.12"
|
||||
- name: Set up uv
|
||||
# ``--system`` (below) installs into the setup-python interpreter;
|
||||
# no venv is created or restored by this workflow.
|
||||
|
||||
@@ -63,11 +63,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
python-version: "3.12"
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: Determine tag and whether to push
|
||||
id: tag
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
|
||||
- name: Log in to the GitHub container registry
|
||||
if: steps.tag.outputs.push == 'true'
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -147,14 +147,14 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
python-version: "3.12"
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
if: steps.pr.outputs.skip != 'true'
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
python-version: "3.11"
|
||||
python-version: "3.12"
|
||||
cache-key: ${{ hashFiles('.cache-key') }}
|
||||
|
||||
- name: Download memory analysis artifacts
|
||||
|
||||
+52
-28
@@ -12,8 +12,8 @@ permissions:
|
||||
contents: read # actions/checkout for all jobs; individual jobs add their own scopes when they need to write
|
||||
|
||||
env:
|
||||
DEFAULT_PYTHON: "3.11"
|
||||
PYUPGRADE_TARGET: "--py311-plus"
|
||||
DEFAULT_PYTHON: "3.12"
|
||||
PYUPGRADE_TARGET: "--py312-plus"
|
||||
|
||||
concurrency:
|
||||
# yamllint disable-line rule:line-length
|
||||
@@ -34,12 +34,12 @@ jobs:
|
||||
run: echo key="${{ hashFiles('requirements.txt', 'requirements_dev.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: venv
|
||||
# yamllint disable-line rule:line-length
|
||||
@@ -162,7 +162,7 @@ jobs:
|
||||
ref: main
|
||||
path: device-builder
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- name: Set up uv
|
||||
@@ -203,7 +203,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
- "3.13"
|
||||
- "3.14"
|
||||
os:
|
||||
@@ -250,7 +250,7 @@ jobs:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
- name: Save Python virtual environment cache
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||
@@ -295,7 +295,7 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Restore components graph cache
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: .temp/components_graph.json
|
||||
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
||||
@@ -339,7 +339,7 @@ jobs:
|
||||
echo "benchmarks=$(echo "$output" | jq -r '.benchmarks')" >> $GITHUB_OUTPUT
|
||||
- name: Save components graph cache
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: .temp/components_graph.json
|
||||
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
||||
@@ -360,12 +360,12 @@ jobs:
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Set up Python 3.13
|
||||
id: python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||
@@ -456,7 +456,7 @@ jobs:
|
||||
echo "binary=$BINARY" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Run CodSpeed benchmarks
|
||||
uses: CodSpeedHQ/action@63f3e98b61959fe67f146a3ff022e4136fe9bb9c # v4.17.6
|
||||
uses: CodSpeedHQ/action@a4a36bb07c0638b0b4ca52bf1f3dad1b4289e52f # v4.18.1
|
||||
with:
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
@@ -475,6 +475,8 @@ jobs:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
# esp32-arduino-tidy installs ESP-IDF natively; share the native IDF cache.
|
||||
ESPHOME_ESP_IDF_PREFIX: ~/.esphome-idf
|
||||
# nrf52-tidy installs sdk-nrf natively; pin it to a cacheable path.
|
||||
ESPHOME_SDK_NRF_PREFIX: ~/.esphome-sdk-nrf
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 2
|
||||
@@ -491,7 +493,7 @@ jobs:
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ZEPHYR
|
||||
options: --environment nrf52-tidy --grep USE_ZEPHYR --grep USE_NRF52
|
||||
pio_cache_key: tidy-zephyr
|
||||
cache_sdk_nrf: true
|
||||
ignore_errors: false
|
||||
|
||||
steps:
|
||||
@@ -509,14 +511,14 @@ jobs:
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref == 'refs/heads/dev' && matrix.pio_cache_key
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref != 'refs/heads/dev' && matrix.pio_cache_key
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||
@@ -527,6 +529,10 @@ jobs:
|
||||
with:
|
||||
framework: arduino
|
||||
|
||||
- name: Cache nRF Connect SDK install
|
||||
if: matrix.cache_sdk_nrf
|
||||
uses: ./.github/actions/cache-sdk-nrf
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
@@ -795,7 +801,7 @@ jobs:
|
||||
if: always()
|
||||
|
||||
test-build-components-split:
|
||||
name: Test components batch (${{ matrix.components }})
|
||||
name: Test components batch (${{ matrix.batch.components }})
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- common
|
||||
@@ -805,11 +811,14 @@ jobs:
|
||||
# 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
|
||||
# nrf52 component builds install sdk-nrf natively; pin it to the shared
|
||||
# cacheable path so the restore below lands where the build looks.
|
||||
ESPHOME_SDK_NRF_PREFIX: ~/.esphome-sdk-nrf
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: ${{ (startsWith(github.base_ref, 'beta') || startsWith(github.base_ref, 'release')) && 8 || 4 }}
|
||||
matrix:
|
||||
components: ${{ fromJson(needs.determine-jobs.outputs.component-test-batches) }}
|
||||
batch: ${{ fromJson(needs.determine-jobs.outputs.component-test-batches) }}
|
||||
steps:
|
||||
- name: Show disk space
|
||||
run: |
|
||||
@@ -817,13 +826,13 @@ jobs:
|
||||
df -h
|
||||
|
||||
- name: List components
|
||||
run: echo ${{ matrix.components }}
|
||||
run: echo ${{ matrix.batch.components }}
|
||||
|
||||
- name: Cache apt packages
|
||||
uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.5.3
|
||||
uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # v1.6.3
|
||||
with:
|
||||
packages: libsdl2-dev
|
||||
version: 1.0
|
||||
packages: libsdl2-dev ccache
|
||||
version: 1.1
|
||||
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
@@ -833,11 +842,21 @@ jobs:
|
||||
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.
|
||||
# Only batches whose test platforms include esp32 need the native
|
||||
# ESP-IDF install; never save -- just reuse the shared install the
|
||||
# dev tidy jobs already cached when present.
|
||||
if: matrix.batch.needs_idf
|
||||
uses: ./.github/actions/cache-esp-idf
|
||||
with:
|
||||
restore-only: true
|
||||
- name: Cache nRF Connect SDK install (restore-only)
|
||||
# Only batches whose test platforms include nrf52 need the native
|
||||
# sdk-nrf install; never save -- just reuse the shared install the
|
||||
# dev nrf52 tidy job cached when present.
|
||||
if: matrix.batch.needs_nrf
|
||||
uses: ./.github/actions/cache-sdk-nrf
|
||||
with:
|
||||
restore-only: true
|
||||
- name: Validate and compile components with intelligent grouping
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
@@ -868,7 +887,7 @@ jobs:
|
||||
fi
|
||||
|
||||
# Convert space-separated components to comma-separated for Python script
|
||||
components_csv=$(echo "${{ matrix.components }}" | tr ' ' ',')
|
||||
components_csv=$(echo "${{ matrix.batch.components }}" | tr ' ' ',')
|
||||
|
||||
# Only isolate directly changed components when targeting dev branch
|
||||
# For beta/release branches, group everything for faster CI
|
||||
@@ -941,6 +960,11 @@ jobs:
|
||||
echo "All components in this batch are validate-only -- skipping compile stage."
|
||||
fi
|
||||
|
||||
- name: Print ccache statistics
|
||||
# esphome stores the cache under the IDF tools path; expand the leading
|
||||
# ~ in ESPHOME_ESP_IDF_PREFIX so ccache reads the dir the build used.
|
||||
run: CCACHE_DIR="${ESPHOME_ESP_IDF_PREFIX/#\~/$HOME}/ccache" ccache -s
|
||||
|
||||
test-esp32-platformio:
|
||||
name: Test esp32 components with PlatformIO
|
||||
runs-on: ubuntu-24.04
|
||||
@@ -1093,7 +1117,7 @@ jobs:
|
||||
- name: Restore cached memory analysis
|
||||
id: cache-memory-analysis
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true'
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: memory-analysis-target.json
|
||||
key: ${{ steps.cache-key.outputs.cache-key }}
|
||||
@@ -1117,7 +1141,7 @@ jobs:
|
||||
|
||||
- name: Cache platformio
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
|
||||
@@ -1159,7 +1183,7 @@ jobs:
|
||||
|
||||
- name: Save memory analysis to cache
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success'
|
||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: memory-analysis-target.json
|
||||
key: ${{ steps.cache-key.outputs.cache-key }}
|
||||
@@ -1206,7 +1230,7 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Cache platformio
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
|
||||
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
@@ -84,6 +84,6 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Build
|
||||
@@ -94,20 +94,20 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: Log in to docker hub
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -178,17 +178,17 @@ jobs:
|
||||
merge-multiple: true
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: Log in to docker hub
|
||||
if: matrix.registry == 'dockerhub'
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
if: matrix.registry == 'ghcr'
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
path: lib/home-assistant
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "3.14"
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ repos:
|
||||
rev: v3.21.2
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py311-plus]
|
||||
args: [--py312-plus]
|
||||
- repo: https://github.com/adrienverge/yamllint.git
|
||||
rev: v1.37.1
|
||||
hooks:
|
||||
|
||||
@@ -9,12 +9,12 @@ This document provides essential context for AI models interacting with this pro
|
||||
|
||||
## 2. Core Technologies & Stack
|
||||
|
||||
* **Languages:** Python (>=3.11), C++ (gnu++20)
|
||||
* **Languages:** Python (>=3.12), C++ (gnu++20)
|
||||
* **Frameworks & Runtimes:** PlatformIO, Arduino, ESP-IDF.
|
||||
* **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).
|
||||
@@ -456,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`.
|
||||
@@ -658,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
|
||||
@@ -711,3 +709,9 @@ This document provides essential context for AI models interacting with this pro
|
||||
_LOGGER.warning(f"'{CONF_OLD_KEY}' deprecated, use '{CONF_NEW_KEY}'. Removed in 2026.6.0")
|
||||
config[CONF_NEW_KEY] = config.pop(CONF_OLD_KEY) # Auto-migrate
|
||||
```
|
||||
## 9. English Language
|
||||
|
||||
The project uses English for non-code content. When drafting documentation, code comments, commit messages,
|
||||
PR descriptions, and similar text, avoid technical jargon. Instead, express concepts in plain English,
|
||||
using standard technical terms only when required. Ensure the text is readily comprehensible to a wide
|
||||
audience, including non-native English speakers.
|
||||
|
||||
+8
-1
@@ -122,7 +122,9 @@ esphome/components/cover/* @esphome/core
|
||||
esphome/components/cs5460a/* @balrog-kun
|
||||
esphome/components/cse7761/* @berfenger
|
||||
esphome/components/cst226/* @clydebarrow
|
||||
esphome/components/cst328/* @latonita
|
||||
esphome/components/cst816/* @clydebarrow
|
||||
esphome/components/cst9220/* @clydebarrow
|
||||
esphome/components/ct_clamp/* @jesserockz
|
||||
esphome/components/current_based/* @djwmarcx
|
||||
esphome/components/dac7678/* @NickB1
|
||||
@@ -266,6 +268,7 @@ esphome/components/integration/* @OttoWinter
|
||||
esphome/components/internal_temperature/* @Mat931
|
||||
esphome/components/interval/* @esphome/core
|
||||
esphome/components/ir_rf_proxy/* @kbx81
|
||||
esphome/components/it8951/* @koosoli @limengdu @Passific
|
||||
esphome/components/jsn_sr04t/* @Mafus1
|
||||
esphome/components/json/* @esphome/core
|
||||
esphome/components/kamstrup_kmp/* @cfeenstra1024
|
||||
@@ -385,6 +388,7 @@ esphome/components/pcm5122/* @remcom
|
||||
esphome/components/pi4ioe5v6408/* @jesserockz
|
||||
esphome/components/pid/* @OttoWinter
|
||||
esphome/components/pipsolar/* @andreashergert1984
|
||||
esphome/components/pixoo/* @jesserockz
|
||||
esphome/components/pm1006/* @habbie
|
||||
esphome/components/pm2005/* @andrewjswan
|
||||
esphome/components/pmsa003i/* @sjtrny
|
||||
@@ -404,6 +408,7 @@ esphome/components/psram/* @esphome/core
|
||||
esphome/components/pulse_meter/* @cstaahl @stevebaxter @TrentHouliston
|
||||
esphome/components/pvvx_mithermometer/* @pasiz
|
||||
esphome/components/pylontech/* @functionpointer
|
||||
esphome/components/qmi8658/* @clydebarrow
|
||||
esphome/components/qmp6988/* @andrewpc
|
||||
esphome/components/qr_code/* @wjtje
|
||||
esphome/components/qspi_dbi/* @clydebarrow
|
||||
@@ -445,7 +450,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
|
||||
@@ -497,6 +502,7 @@ esphome/components/ssd1331_base/* @kbx81
|
||||
esphome/components/ssd1331_spi/* @kbx81
|
||||
esphome/components/ssd1351_base/* @kbx81
|
||||
esphome/components/ssd1351_spi/* @kbx81
|
||||
esphome/components/st7123/* @miniskipper
|
||||
esphome/components/st7567_base/* @latonita
|
||||
esphome/components/st7567_i2c/* @latonita
|
||||
esphome/components/st7567_spi/* @latonita
|
||||
@@ -578,6 +584,7 @@ esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
|
||||
esphome/components/watchdog/* @oarcher
|
||||
esphome/components/water_heater/* @dhoeben
|
||||
esphome/components/waveshare_epaper/* @clydebarrow
|
||||
esphome/components/waveshare_io_ch32v003/* @latonita
|
||||
esphome/components/web_server/ota/* @esphome/core
|
||||
esphome/components/web_server_base/* @esphome/core
|
||||
esphome/components/web_server_idf/* @dentra
|
||||
|
||||
+102
@@ -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).
|
||||
+2
-12
@@ -1,5 +1,5 @@
|
||||
ARG BUILD_VERSION=dev
|
||||
ARG BUILD_BASE_VERSION=2026.06.0
|
||||
ARG BUILD_BASE_VERSION=2026.06.1
|
||||
ARG BUILD_TYPE=docker
|
||||
|
||||
FROM ghcr.io/esphome/docker-base:debian-${BUILD_BASE_VERSION} AS base-source-docker
|
||||
@@ -11,16 +11,6 @@ FROM base-source-${BUILD_TYPE} AS base
|
||||
RUN git config --system --add safe.directory "*" \
|
||||
&& git config --system advice.detachedHead false
|
||||
|
||||
# Install build tools for Python packages that require compilation
|
||||
# (e.g., ruamel.yaml.clib used by ESP-IDF's idf-component-manager).
|
||||
# Also install libusb-1.0 at runtime so the ESP-IDF tools installer can
|
||||
# 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 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
|
||||
|
||||
RUN pip install --no-cache-dir -U pip uv==0.10.1
|
||||
@@ -32,7 +22,7 @@ RUN \
|
||||
-r /requirements.txt
|
||||
|
||||
# Install the ESPHome Device Builder dashboard.
|
||||
RUN uv pip install --no-cache-dir esphome-device-builder==1.0.9
|
||||
RUN uv pip install --no-cache-dir esphome-device-builder==1.0.29
|
||||
|
||||
RUN \
|
||||
platformio settings set enable_telemetry No \
|
||||
|
||||
@@ -21,6 +21,11 @@ export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
|
||||
export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
|
||||
export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
|
||||
|
||||
# Keep the native toolchain installs on the persistent cache root, not the
|
||||
# container's ephemeral user cache dir (re-downloaded on every restart).
|
||||
export ESPHOME_ESP_IDF_PREFIX="$(dirname "${pio_cache_base}")/idf"
|
||||
export ESPHOME_SDK_NRF_PREFIX="$(dirname "${pio_cache_base}")/sdk-nrf"
|
||||
|
||||
# If /build is mounted, use that as the build path
|
||||
# otherwise use path in /config (so that builds aren't lost on container restart)
|
||||
if [[ -d /build ]]; then
|
||||
|
||||
@@ -15,18 +15,15 @@ export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
|
||||
export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
|
||||
export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
|
||||
|
||||
# Keep the native toolchain installs on the persistent /data volume, not the
|
||||
# container's ephemeral user cache dir (wiped on every add-on update/restart).
|
||||
export ESPHOME_ESP_IDF_PREFIX=/data/cache/idf
|
||||
export ESPHOME_SDK_NRF_PREFIX=/data/cache/sdk-nrf
|
||||
|
||||
if bashio::config.true 'leave_front_door_open'; then
|
||||
export DISABLE_HA_AUTHENTICATION=true
|
||||
fi
|
||||
|
||||
if bashio::config.true 'streamer_mode'; then
|
||||
export ESPHOME_STREAMER_MODE=true
|
||||
fi
|
||||
|
||||
if bashio::config.has_value 'relative_url'; then
|
||||
export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url')
|
||||
fi
|
||||
|
||||
if bashio::config.has_value 'default_compile_process_limit'; then
|
||||
export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=$(bashio::config 'default_compile_process_limit')
|
||||
else
|
||||
@@ -49,7 +46,21 @@ if bashio::fs.directory_exists '/config/esphome/.esphome'; then
|
||||
rm -rf /config/esphome/.esphome
|
||||
fi
|
||||
|
||||
# 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 Device Builder..."
|
||||
exec esphome-device-builder /config/esphome \
|
||||
--ha-addon \
|
||||
--ingress-port "$(bashio::addon.ingress_port)"
|
||||
--ingress-port "$(bashio::addon.ingress_port)" \
|
||||
"$@"
|
||||
|
||||
@@ -2,6 +2,6 @@ esphome:
|
||||
name: docker-test-ln882x-arduino
|
||||
|
||||
ln882x:
|
||||
board: generic-ln882hki
|
||||
board: generic-ln882h
|
||||
|
||||
logger:
|
||||
|
||||
+99
-80
@@ -52,11 +52,6 @@ from esphome.const import (
|
||||
CONF_WEB_SERVER,
|
||||
CONF_WIFI,
|
||||
ENV_NOGITIGNORE,
|
||||
KEY_CORE,
|
||||
KEY_TARGET_PLATFORM,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_RP2040,
|
||||
SECRETS_FILES,
|
||||
Toolchain,
|
||||
)
|
||||
@@ -230,8 +225,9 @@ def _discover_mac_suffix_devices() -> list[str] | None:
|
||||
|
||||
Returns:
|
||||
- ``None`` when discovery isn't applicable (``name_add_mac_suffix`` off,
|
||||
mDNS disabled, or ``CORE.address`` is already an IP). Callers should
|
||||
then fall back to whatever default OTA address they normally use.
|
||||
mDNS disabled, or ``CORE.address`` isn't a ``.local`` mDNS address).
|
||||
Callers should then fall back to whatever default OTA address they
|
||||
normally use.
|
||||
- ``[]`` when discovery ran but found nothing. Callers should NOT fall
|
||||
back to the base name: with ``name_add_mac_suffix`` enabled, the base
|
||||
name by definition doesn't exist on the network.
|
||||
@@ -241,7 +237,7 @@ def _discover_mac_suffix_devices() -> list[str] | None:
|
||||
``aioesphomeapi`` via :func:`_resolve_network_devices`) reuses the IPs we
|
||||
already have without opening a second Zeroconf client.
|
||||
"""
|
||||
if not (has_name_add_mac_suffix() and has_mdns() and has_non_ip_address()):
|
||||
if not (has_name_add_mac_suffix() and has_mdns() and has_mdns_address()):
|
||||
return None
|
||||
from esphome.zeroconf import discover_mdns_devices
|
||||
|
||||
@@ -268,6 +264,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 +343,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
|
||||
@@ -336,7 +355,7 @@ def choose_upload_log_host(
|
||||
bootsel_permission_error = False
|
||||
if (
|
||||
purpose == Purpose.UPLOADING
|
||||
and CORE.data.get(KEY_CORE, {}).get(KEY_TARGET_PLATFORM) == PLATFORM_RP2040
|
||||
and CORE.is_rp2040
|
||||
and (picotool := _find_picotool()) is not None
|
||||
):
|
||||
bootsel = detect_rp2040_bootsel(picotool)
|
||||
@@ -383,7 +402,7 @@ def choose_upload_log_host(
|
||||
# Show helpful BOOTSEL instructions for RP2040 when no BOOTSEL device is found
|
||||
if (
|
||||
purpose == Purpose.UPLOADING
|
||||
and CORE.data.get(KEY_CORE, {}).get(KEY_TARGET_PLATFORM) == PLATFORM_RP2040
|
||||
and CORE.is_rp2040
|
||||
and not any(get_port_type(opt[1]) == PortType.BOOTSEL for opt in options)
|
||||
):
|
||||
if bootsel_permission_error:
|
||||
@@ -485,17 +504,22 @@ def has_mdns() -> bool:
|
||||
|
||||
|
||||
def has_non_ip_address() -> bool:
|
||||
"""Check if CORE.address is set and is not an IP address."""
|
||||
"""Check if ``CORE.address`` is set and is not an IP address."""
|
||||
return CORE.address is not None and not is_ip_address(CORE.address)
|
||||
|
||||
|
||||
def has_mdns_address() -> bool:
|
||||
"""Check if ``CORE.address`` is a ``.local`` mDNS hostname."""
|
||||
return CORE.address is not None and CORE.address.endswith(".local")
|
||||
|
||||
|
||||
def has_ip_address() -> bool:
|
||||
"""Check if CORE.address is a valid IP address."""
|
||||
"""Check if ``CORE.address`` is a valid IP address."""
|
||||
return CORE.address is not None and is_ip_address(CORE.address)
|
||||
|
||||
|
||||
def has_resolvable_address() -> bool:
|
||||
"""Check if CORE.address is resolvable (via mDNS, DNS, or is an IP address)."""
|
||||
"""Check if ``CORE.address`` is resolvable (via mDNS, DNS, or is an IP address)."""
|
||||
# Any address (IP, mDNS hostname, or regular DNS hostname) is resolvable
|
||||
# The resolve_ip_address() function in helpers.py handles all types via AsyncResolver
|
||||
if CORE.address is None:
|
||||
@@ -504,7 +528,7 @@ def has_resolvable_address() -> bool:
|
||||
if has_ip_address():
|
||||
return True
|
||||
|
||||
# The dashboard pre-resolves the device and passes the IPs via
|
||||
# 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):
|
||||
@@ -514,7 +538,7 @@ def has_resolvable_address() -> bool:
|
||||
return True
|
||||
|
||||
# .local mDNS hostnames are only resolvable if mDNS is enabled
|
||||
return not CORE.address.endswith(".local")
|
||||
return not has_mdns_address()
|
||||
|
||||
|
||||
def has_name_add_mac_suffix() -> bool:
|
||||
@@ -962,7 +986,7 @@ def upload_using_platformio(config: ConfigType, port: str) -> int:
|
||||
# RP2040 platform-raspberrypi build recipe expects firmware.bin.signed for
|
||||
# the upload target, but 'nobuild' skips the build phase that creates it.
|
||||
# Create it here so the upload doesn't fail.
|
||||
if CORE.data.get(KEY_CORE, {}).get(KEY_TARGET_PLATFORM) == PLATFORM_RP2040:
|
||||
if CORE.is_rp2040:
|
||||
idedata = toolchain.get_idedata(config)
|
||||
build_dir = Path(idedata.firmware_elf_path).parent
|
||||
firmware_bin = build_dir / "firmware.bin"
|
||||
@@ -1147,10 +1171,10 @@ def upload_program(
|
||||
check_permissions(host)
|
||||
|
||||
exit_code = 1
|
||||
if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
|
||||
if CORE.is_esp32 or CORE.is_esp8266:
|
||||
file = getattr(args, "file", None)
|
||||
exit_code = upload_using_esptool(config, host, file, args.upload_speed)
|
||||
elif CORE.target_platform == PLATFORM_RP2040 or CORE.is_libretiny:
|
||||
elif CORE.is_rp2040 or CORE.is_libretiny:
|
||||
exit_code = upload_using_platformio(config, host)
|
||||
# else: Unknown target platform, exit_code remains 1
|
||||
|
||||
@@ -1471,12 +1495,29 @@ _LEGACY_REDACTION_REMOVAL = "2026.12.0"
|
||||
|
||||
def _redact_with_legacy_fallback(output: str) -> str:
|
||||
unmarked: set[str] = set()
|
||||
# Track the top-level ``substitutions:`` block. Its keys are arbitrary
|
||||
# user-chosen names with no schema validator, so the ``cv.sensitive(...)``
|
||||
# migration named in the warning can't be applied to them. Their values are
|
||||
# still redacted, but emitting the (unactionable) deprecation warning would
|
||||
# only confuse users.
|
||||
in_substitutions = False
|
||||
|
||||
def _replace(m: re.Match[str]) -> str:
|
||||
unmarked.add(m.group("key"))
|
||||
return f"{m.group('key')}: \\033[8m{m.group('val')}\\033[28m"
|
||||
|
||||
output = _LEGACY_REDACTION_RE.sub(_replace, output)
|
||||
lines = output.split("\n")
|
||||
for i, line in enumerate(lines):
|
||||
# A non-indented, non-blank line is a top-level key that opens or
|
||||
# closes the substitutions block.
|
||||
if line and not line[0].isspace():
|
||||
in_substitutions = line.startswith(f"{CONF_SUBSTITUTIONS}:")
|
||||
m = _LEGACY_REDACTION_RE.search(line)
|
||||
if m is None:
|
||||
continue
|
||||
if not in_substitutions:
|
||||
unmarked.add(m.group("key"))
|
||||
lines[i] = (
|
||||
f"{line[: m.start()]}{m.group('key')}: "
|
||||
f"\\033[8m{m.group('val')}\\033[28m{line[m.end() :]}"
|
||||
)
|
||||
output = "\n".join(lines)
|
||||
for key in sorted(unmarked):
|
||||
_LOGGER.warning(
|
||||
"Field '%s' is being redacted by a legacy substring heuristic. "
|
||||
@@ -1607,10 +1648,7 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
|
||||
# After BOOTSEL upload, wait for a new serial port to appear
|
||||
# so it shows up in the log chooser
|
||||
if (
|
||||
successful_device is None
|
||||
and CORE.data.get(KEY_CORE, {}).get(KEY_TARGET_PLATFORM) == PLATFORM_RP2040
|
||||
):
|
||||
if successful_device is None and CORE.is_rp2040:
|
||||
_wait_for_serial_port(known_ports=pre_upload_ports)
|
||||
# If exactly one new serial port appeared, use it directly
|
||||
serial_ports = get_serial_ports()
|
||||
@@ -1693,9 +1731,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(
|
||||
@@ -2351,50 +2393,31 @@ def parse_args(argv):
|
||||
)
|
||||
|
||||
parser_clean_all = subparsers.add_parser(
|
||||
"clean-all", help="Clean all build and platform files."
|
||||
"clean-all",
|
||||
help="Clean all build and platform files, including machine-global "
|
||||
"toolchain caches shared by all configurations, so other projects will "
|
||||
"re-download them on next build.",
|
||||
)
|
||||
parser_clean_all.add_argument(
|
||||
"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")
|
||||
@@ -2489,11 +2512,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:
|
||||
|
||||
@@ -8,7 +8,7 @@ memory-constrained platforms like ESP8266.
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import re
|
||||
@@ -65,6 +65,7 @@ class RamSymbol:
|
||||
size: int
|
||||
section: str
|
||||
demangled: str = "" # Demangled name, set after batch demangling
|
||||
aliases: list[str] = field(default_factory=list) # Other names at same address
|
||||
|
||||
|
||||
class RamStringsAnalyzer:
|
||||
@@ -235,6 +236,11 @@ class RamStringsAnalyzer:
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
return
|
||||
|
||||
# Track symbols by address so aliases (multiple names for the same
|
||||
# object, e.g. the newlib __lock___* mutexes that all alias one
|
||||
# StaticSemaphore_t) are reported once instead of once per name.
|
||||
symbols_by_addr: dict[int, RamSymbol] = {}
|
||||
|
||||
for line in output.split("\n"):
|
||||
parts = line.split()
|
||||
if len(parts) < 4:
|
||||
@@ -253,6 +259,18 @@ class RamStringsAnalyzer:
|
||||
if sym_type not in DATA_SYMBOL_TYPES:
|
||||
continue
|
||||
|
||||
if (existing := symbols_by_addr.get(addr)) is not None:
|
||||
# Prefer a global (uppercase type) name as the primary so
|
||||
# nm output order can't hide it behind a local alias.
|
||||
if sym_type.isupper() and existing.sym_type.islower():
|
||||
existing.aliases.append(existing.name)
|
||||
existing.name = name
|
||||
existing.sym_type = sym_type
|
||||
else:
|
||||
existing.aliases.append(name)
|
||||
existing.size = max(existing.size, size)
|
||||
continue
|
||||
|
||||
# Check if symbol is in a RAM section
|
||||
for section_name in self.ram_sections:
|
||||
if section_name not in self.sections:
|
||||
@@ -260,15 +278,15 @@ class RamStringsAnalyzer:
|
||||
|
||||
section = self.sections[section_name]
|
||||
if section.address <= addr < section.address + section.size:
|
||||
self.ram_symbols.append(
|
||||
RamSymbol(
|
||||
name=name,
|
||||
sym_type=sym_type,
|
||||
address=addr,
|
||||
size=size,
|
||||
section=section_name,
|
||||
)
|
||||
symbol = RamSymbol(
|
||||
name=name,
|
||||
sym_type=sym_type,
|
||||
address=addr,
|
||||
size=size,
|
||||
section=section_name,
|
||||
)
|
||||
symbols_by_addr[addr] = symbol
|
||||
self.ram_symbols.append(symbol)
|
||||
break
|
||||
|
||||
def _demangle_symbols(self) -> None:
|
||||
@@ -436,7 +454,13 @@ class RamStringsAnalyzer:
|
||||
for symbol in largest_symbols:
|
||||
# Use demangled name if available, otherwise raw name
|
||||
display_name = symbol.demangled or symbol.name
|
||||
name_display = display_name[:49] if len(display_name) > 49 else display_name
|
||||
# Truncate the name, not the alias note, so merged aliases stay
|
||||
# visible even for long demangled C++ names.
|
||||
alias_note = f" (+{len(symbol.aliases)} aliases)" if symbol.aliases else ""
|
||||
max_name_len = 49 - len(alias_note)
|
||||
if len(display_name) > max_name_len:
|
||||
display_name = display_name[:max_name_len]
|
||||
name_display = display_name + alias_note
|
||||
lines.append(
|
||||
f"{name_display:<50} {symbol.sym_type:<6} {symbol.size:>8} B {symbol.section}"
|
||||
)
|
||||
|
||||
@@ -12,12 +12,9 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
from collections.abc import Awaitable, Callable
|
||||
import threading
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
class AsyncThreadRunner(threading.Thread, Generic[_T]):
|
||||
class AsyncThreadRunner[T](threading.Thread):
|
||||
"""Run an async coroutine in a daemon thread and expose its result.
|
||||
|
||||
The runner catches all exceptions from the coroutine and stores them in
|
||||
@@ -35,10 +32,10 @@ class AsyncThreadRunner(threading.Thread, Generic[_T]):
|
||||
result = runner.result
|
||||
"""
|
||||
|
||||
def __init__(self, coro_factory: Callable[[], Awaitable[_T]]) -> None:
|
||||
def __init__(self, coro_factory: Callable[[], Awaitable[T]]) -> None:
|
||||
super().__init__(daemon=True)
|
||||
self._coro_factory = coro_factory
|
||||
self.result: _T | None = None
|
||||
self.result: T | None = None
|
||||
self.exception: BaseException | None = None
|
||||
self.event = threading.Event()
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ void A01nyubComponent::check_buffer_() {
|
||||
if (this->buffer_[3] == checksum) {
|
||||
float distance = (this->buffer_[1] << 8) + this->buffer_[2];
|
||||
if (distance > 280) {
|
||||
float meters = distance / 1000.0;
|
||||
float meters = distance / 1000.0f;
|
||||
ESP_LOGV(TAG, "Distance from sensor: %f mm, %f m", distance, meters);
|
||||
this->publish_state(meters);
|
||||
} else {
|
||||
|
||||
@@ -216,7 +216,7 @@ void AcDimmer::setup() {
|
||||
}
|
||||
|
||||
void AcDimmer::write_state(float state) {
|
||||
state = std::acos(1 - (2 * state)) / std::numbers::pi; // RMS power compensation
|
||||
state = std::acos(1 - (2 * state)) / std::numbers::pi_v<float>; // RMS power compensation
|
||||
auto new_value = static_cast<uint16_t>(roundf(state * 65535));
|
||||
if (new_value != 0 && this->store_.value == 0)
|
||||
this->store_.init_cycle = this->init_with_half_cycle_;
|
||||
|
||||
@@ -66,15 +66,18 @@ float ADCSensor::sample() {
|
||||
}
|
||||
|
||||
uint8_t pin = this->pin_->get_pin();
|
||||
#ifdef CYW43_USES_VSYS_PIN
|
||||
#if defined(CYW43_USES_VSYS_PIN) && defined(USE_WIFI)
|
||||
if (pin == PICO_VSYS_PIN) {
|
||||
// Measuring VSYS on Raspberry Pico W needs to be wrapped with
|
||||
// `cyw43_thread_enter()`/`cyw43_thread_exit()` as discussed in
|
||||
// https://github.com/raspberrypi/pico-sdk/issues/1222, since Wifi chip and
|
||||
// VSYS ADC both share GPIO29
|
||||
// VSYS ADC both share GPIO29.
|
||||
// The USE_WIFI guard is required because CYW43_USES_VSYS_PIN can be defined
|
||||
// transitively (e.g. via lwip_wrap.h) even on non-WiFi boards where the CYW43
|
||||
// driver is never initialized; calling cyw43_thread_enter() there hard-faults.
|
||||
cyw43_thread_enter();
|
||||
}
|
||||
#endif // CYW43_USES_VSYS_PIN
|
||||
#endif // defined(CYW43_USES_VSYS_PIN) && defined(USE_WIFI)
|
||||
|
||||
adc_gpio_init(pin);
|
||||
adc_select_input(pin - 26);
|
||||
@@ -84,11 +87,11 @@ float ADCSensor::sample() {
|
||||
aggr.add_sample(raw);
|
||||
}
|
||||
|
||||
#ifdef CYW43_USES_VSYS_PIN
|
||||
#if defined(CYW43_USES_VSYS_PIN) && defined(USE_WIFI)
|
||||
if (pin == PICO_VSYS_PIN) {
|
||||
cyw43_thread_exit();
|
||||
}
|
||||
#endif // CYW43_USES_VSYS_PIN
|
||||
#endif // defined(CYW43_USES_VSYS_PIN) && defined(USE_WIFI)
|
||||
|
||||
if (this->output_raw_) {
|
||||
return aggr.aggregate();
|
||||
|
||||
@@ -114,13 +114,13 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
this->decoder_->decode(param->notify.value, param->notify.value_len);
|
||||
|
||||
if (this->decoder_->has_position()) {
|
||||
this->position = ((float) this->decoder_->position_ / 100.0);
|
||||
this->position = ((float) this->decoder_->position_ / 100.0f);
|
||||
if (!this->invert_position_)
|
||||
this->position = 1 - this->position;
|
||||
if (this->position > 0.97)
|
||||
this->position = 1.0;
|
||||
if (this->position < 0.02)
|
||||
this->position = 0.0;
|
||||
if (this->position > 0.97f)
|
||||
this->position = 1.0f;
|
||||
if (this->position < 0.02f)
|
||||
this->position = 0.0f;
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
namespace esphome::anova {
|
||||
|
||||
float ftoc(float f) { return (f - 32.0) * (5.0f / 9.0f); }
|
||||
float ftoc(float f) { return (f - 32.0f) * (5.0f / 9.0f); }
|
||||
|
||||
float ctof(float c) { return (c * 9.0f / 5.0f) + 32.0; }
|
||||
float ctof(float c) { return (c * 9.0f / 5.0f) + 32.0f; }
|
||||
|
||||
AnovaPacket *AnovaCodec::clean_packet_() {
|
||||
this->packet_.length = strlen((char *) this->packet_.data);
|
||||
|
||||
@@ -305,6 +305,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
rtl87xx=4, # Moderate RAM, BSD-style sockets
|
||||
host=4, # Abundant resources
|
||||
ln882x=4, # Moderate RAM
|
||||
nrf52=4, # ~256KB RAM, BSD sockets
|
||||
): cv.int_range(min=1, max=10),
|
||||
cv.SplitDefault(
|
||||
CONF_MAX_CONNECTIONS,
|
||||
@@ -315,6 +316,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
rtl87xx=5, # Moderate RAM
|
||||
host=8, # Abundant resources
|
||||
ln882x=5, # Moderate RAM
|
||||
nrf52=4, # ~256KB RAM, BSD sockets, Thread (single HA controller)
|
||||
): cv.int_range(min=1, max=20),
|
||||
# Maximum queued send buffers per connection before dropping connection
|
||||
# Each buffer uses ~8-12 bytes overhead plus actual message size
|
||||
@@ -538,17 +540,20 @@ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
# synchronous=False: when on_success/on_error is configured, play() stores the
|
||||
# trigger args until the HomeassistantActionResponse arrives, so non-owning args
|
||||
# (StringRef into the API receive buffer) must not be used.
|
||||
@automation.register_action(
|
||||
"homeassistant.action",
|
||||
HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
synchronous=False,
|
||||
)
|
||||
@automation.register_action(
|
||||
"homeassistant.service",
|
||||
HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
synchronous=False,
|
||||
)
|
||||
async def homeassistant_service_to_code(
|
||||
config: ConfigType,
|
||||
@@ -642,6 +647,8 @@ HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema(
|
||||
)
|
||||
|
||||
|
||||
# synchronous=True is safe here: the event schema has no on_success/on_error,
|
||||
# so play() never stores the trigger args.
|
||||
@automation.register_action(
|
||||
"homeassistant.event",
|
||||
HomeAssistantServiceCallAction,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -31,6 +31,13 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#if defined(LOG_LEVEL_NONE)
|
||||
// Zephyr defines LOG_LEVEL_NONE as a logging macro that collides with the LogLevel enum value of
|
||||
// the same name in the generated api_pb2.h. Undefine it for the rest of this translation unit so
|
||||
// the enum parses; nothing below needs Zephyr's logging macro.
|
||||
#undef LOG_LEVEL_NONE
|
||||
#endif
|
||||
|
||||
namespace esphome::api {
|
||||
|
||||
// This file only provides includes, no actual code
|
||||
|
||||
@@ -395,7 +395,7 @@ async def to_code(config):
|
||||
)
|
||||
if data.mp3_support:
|
||||
cg.add_define("USE_AUDIO_MP3_SUPPORT")
|
||||
add_idf_component(name="esphome/micro-mp3", ref="0.3.0")
|
||||
add_idf_component(name="esphome/micro-mp3", ref="0.4.0")
|
||||
_emit_memory_pair(
|
||||
data.mp3.buffer_memory,
|
||||
"CONFIG_MICRO_MP3_PREFER_PSRAM",
|
||||
|
||||
@@ -326,14 +326,8 @@ FileDecoderState AudioDecoder::decode_mp3_() {
|
||||
} else if (result == micro_mp3::MP3_NEED_MORE_DATA) {
|
||||
return FileDecoderState::MORE_TO_PROCESS;
|
||||
} else if (result == micro_mp3::MP3_OUTPUT_BUFFER_TOO_SMALL) {
|
||||
// Reallocate to decode the frame on the next call
|
||||
if (this->mp3_decoder_->get_channels() > 0) {
|
||||
this->free_buffer_required_ =
|
||||
this->mp3_decoder_->get_samples_per_frame() * this->mp3_decoder_->get_channels() * sizeof(int16_t);
|
||||
} else {
|
||||
// Fallback to worst-case size if channel info isn't available
|
||||
this->free_buffer_required_ = this->mp3_decoder_->get_min_output_buffer_bytes();
|
||||
}
|
||||
// Fallback to worst-case size
|
||||
this->free_buffer_required_ = this->mp3_decoder_->get_min_output_buffer_bytes();
|
||||
if (!this->output_transfer_buffer_->reallocate(this->free_buffer_required_)) {
|
||||
return FileDecoderState::FAILED;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace esphome::b_parasite {
|
||||
|
||||
class BParasite : public Component, public esp32_ble_tracker::ESPBTDeviceListener {
|
||||
class BParasite final : public Component, public esp32_ble_tracker::ESPBTDeviceListener {
|
||||
public:
|
||||
void set_address(uint64_t address) { address_ = address; };
|
||||
void set_bindkey(const std::string &bindkey);
|
||||
|
||||
@@ -112,7 +112,7 @@ float BinarySensorMap::bayesian_predicate_(bool sensor_state, float prior, float
|
||||
prob_state_source_false = 1 - prob_given_false;
|
||||
}
|
||||
|
||||
return prob_state_source_true / (prior * prob_state_source_true + (1.0 - prior) * prob_state_source_false);
|
||||
return prob_state_source_true / (prior * prob_state_source_true + (1.0f - prior) * prob_state_source_false);
|
||||
}
|
||||
|
||||
void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float value) {
|
||||
|
||||
+1127
-1051
File diff suppressed because it is too large
Load Diff
@@ -205,7 +205,7 @@ void BL0906::read_data_(const uint8_t address, const float reference, sensor::Se
|
||||
// Chip temperature
|
||||
if (reference == BL0906_TREF) {
|
||||
value = (float) to_int32_t(data_s24);
|
||||
value = (value - 64) * 12.5 / 59 - 40;
|
||||
value = (value - 64) * 12.5f / 59 - 40;
|
||||
}
|
||||
sensor->publish_state(value);
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ float BL0940::calculate_power_reference_() {
|
||||
float BL0940::calculate_energy_reference_() {
|
||||
// formula: 3600000 * 4046 * RL * R1 * 1000 / (1638.4 * 256) / Vref² / (R1 + R2)
|
||||
// or: power_reference_ * 3600000 / (1638.4 * 256)
|
||||
return this->power_reference_cal_ * 3600000 / (1638.4 * 256);
|
||||
return this->power_reference_cal_ * 3600000 / (1638.4f * 256);
|
||||
}
|
||||
|
||||
float BL0940::calculate_calibration_value_(float state) { return (100 + state) / 100; }
|
||||
|
||||
@@ -211,6 +211,17 @@ CONFIG_SCHEMA = (
|
||||
.add_extra(set_reference_values)
|
||||
)
|
||||
|
||||
# BL0940 datasheet: 4800 baud, 8 data bits, no parity (stop bits are 1.5 -- not
|
||||
# representable in the uart schema, so it isn't asserted).
|
||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||
"bl0940",
|
||||
baud_rate=4800,
|
||||
data_bits=8,
|
||||
parity="NONE",
|
||||
require_rx=True,
|
||||
require_tx=True,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
|
||||
@@ -124,14 +124,14 @@ void BL0942::setup() {
|
||||
// If either current or voltage references are set explicitly by the user,
|
||||
// calculate the power reference from it unless that is also explicitly set.
|
||||
if ((this->current_reference_set_ || this->voltage_reference_set_) && !this->power_reference_set_) {
|
||||
this->power_reference_ = (this->voltage_reference_ * this->current_reference_ * 3537.0 / 305978.0) / 73989.0;
|
||||
this->power_reference_ = (this->voltage_reference_ * this->current_reference_ * 3537.0f / 305978.0f) / 73989.0f;
|
||||
this->power_reference_set_ = true;
|
||||
}
|
||||
|
||||
// Similarly for energy reference, if the power reference was set by the user
|
||||
// either implicitly or explicitly.
|
||||
if (this->power_reference_set_ && !this->energy_reference_set_) {
|
||||
this->energy_reference_ = this->power_reference_ * 3600000 / 419430.4;
|
||||
this->energy_reference_ = this->power_reference_ * 3600000 / 419430.4f;
|
||||
this->energy_reference_set_ = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,11 +25,14 @@ void BLENUS::write_array(const uint8_t *data, size_t len) {
|
||||
if (atomic_get(&this->tx_status_) == TX_DISABLED) {
|
||||
return;
|
||||
}
|
||||
auto sent = ring_buf_put(&global_ble_tx_ring_buf, data, len);
|
||||
if (sent < len) {
|
||||
ESP_LOGE(TAG, "TX dropping %u bytes", len - sent);
|
||||
// ring_buf_put() performs a partial write when the buffer is nearly full, which would commit a
|
||||
// truncated fragment and corrupt the stream. Only write when the whole payload fits, so the byte
|
||||
// stream never contains a partial message.
|
||||
if (ring_buf_space_get(&global_ble_tx_ring_buf) < len) {
|
||||
ESP_LOGE(TAG, "TX dropping %u bytes", len);
|
||||
return;
|
||||
}
|
||||
ring_buf_put(&global_ble_tx_ring_buf, data, len);
|
||||
#ifdef USE_UART_DEBUGGER
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
this->debug_callback_.call(uart::UART_DIRECTION_TX, data[i]);
|
||||
@@ -197,6 +200,10 @@ void BLENUS::setup() {
|
||||
void BLENUS::on_log(uint8_t level, const char *tag, const char *message, size_t message_len) {
|
||||
(void) level;
|
||||
(void) tag;
|
||||
// make sure there is space for '\n' or entire message is dropped
|
||||
if (ring_buf_space_get(&global_ble_tx_ring_buf) < message_len + 1) {
|
||||
return;
|
||||
}
|
||||
this->write_array(reinterpret_cast<const uint8_t *>(message), message_len);
|
||||
const char c = '\n';
|
||||
this->write_array(reinterpret_cast<const uint8_t *>(&c), 1);
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
|
||||
namespace esphome::ble_scanner {
|
||||
|
||||
class BLEScanner : public text_sensor::TextSensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||
class BLEScanner final : public text_sensor::TextSensor,
|
||||
public esp32_ble_tracker::ESPBTDeviceListener,
|
||||
public Component {
|
||||
public:
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
|
||||
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
|
||||
@@ -68,11 +68,15 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener,
|
||||
void loop() override;
|
||||
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
|
||||
|
||||
void register_connection(BluetoothConnection *connection) {
|
||||
// maybe_unused: in a passive proxy (active: false) MAX is 0, the body below is removed, and connection is unused.
|
||||
void register_connection([[maybe_unused]] BluetoothConnection *connection) {
|
||||
// Guard the always-false comparison (-Wtype-limits) in a passive proxy (active: false), where MAX is 0.
|
||||
#if BLUETOOTH_PROXY_MAX_CONNECTIONS > 0
|
||||
if (this->connection_count_ < BLUETOOTH_PROXY_MAX_CONNECTIONS) {
|
||||
this->connections_[this->connection_count_++] = connection;
|
||||
connection->proxy_ = this;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void bluetooth_device_request(const api::BluetoothDeviceRequest &msg);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome::bm8563 {
|
||||
|
||||
class BM8563 : public time::RealTimeClock, public i2c::I2CDevice {
|
||||
class BM8563 final : public time::RealTimeClock, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void update() override;
|
||||
@@ -34,17 +34,17 @@ class BM8563 : public time::RealTimeClock, public i2c::I2CDevice {
|
||||
uint8_t byte_to_bcd2_(uint8_t value);
|
||||
};
|
||||
|
||||
template<typename... Ts> class WriteAction : public Action<Ts...>, public Parented<BM8563> {
|
||||
template<typename... Ts> class WriteAction final : public Action<Ts...>, public Parented<BM8563> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->write_time(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class ReadAction : public Action<Ts...>, public Parented<BM8563> {
|
||||
template<typename... Ts> class ReadAction final : public Action<Ts...>, public Parented<BM8563> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->read_time(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class TimerAction : public Action<Ts...>, public Parented<BM8563> {
|
||||
template<typename... Ts> class TimerAction final : public Action<Ts...>, public Parented<BM8563> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint32_t, duration)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace esphome::bme280_i2c {
|
||||
|
||||
static const char *const TAG = "bme280_i2c.sensor";
|
||||
|
||||
class BME280I2CComponent : public esphome::bme280_base::BME280Component, public i2c::I2CDevice {
|
||||
class BME280I2CComponent final : public esphome::bme280_base::BME280Component, public i2c::I2CDevice {
|
||||
bool read_byte(uint8_t a_register, uint8_t *data) override;
|
||||
bool write_byte(uint8_t a_register, uint8_t data) override;
|
||||
bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
namespace esphome::bme280_spi {
|
||||
|
||||
class BME280SPIComponent : public esphome::bme280_base::BME280Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_200KHZ> {
|
||||
class BME280SPIComponent final : public esphome::bme280_base::BME280Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_200KHZ> {
|
||||
void setup() override;
|
||||
bool read_byte(uint8_t a_register, uint8_t *data) override;
|
||||
bool write_byte(uint8_t a_register, uint8_t data) override;
|
||||
|
||||
@@ -65,7 +65,7 @@ struct BME680CalibrationData {
|
||||
int8_t ambient_temperature;
|
||||
};
|
||||
|
||||
class BME680Component : public PollingComponent, public i2c::I2CDevice {
|
||||
class BME680Component final : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
/// Set the temperature oversampling value. Defaults to 16X.
|
||||
void set_temperature_oversampling(BME680Oversampling temperature_oversampling);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32, i2c
|
||||
from esphome.components.const import CONF_STATE_SAVE_INTERVAL
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_SAMPLE_RATE, CONF_TEMPERATURE_OFFSET, Framework
|
||||
|
||||
@@ -12,7 +13,6 @@ MULTI_CONF = True
|
||||
CONF_BME680_BSEC_ID = "bme680_bsec_id"
|
||||
CONF_IAQ_MODE = "iaq_mode"
|
||||
CONF_SUPPLY_VOLTAGE = "supply_voltage"
|
||||
CONF_STATE_SAVE_INTERVAL = "state_save_interval"
|
||||
|
||||
bme680_bsec_ns = cg.esphome_ns.namespace("bme680_bsec")
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ enum SampleRate {
|
||||
|
||||
#define BME680_BSEC_SAMPLE_RATE_LOG(r) (r == SAMPLE_RATE_DEFAULT ? "Default" : (r == SAMPLE_RATE_ULP ? "ULP" : "LP"))
|
||||
|
||||
class BME680BSECComponent : public Component, public i2c::I2CDevice {
|
||||
class BME680BSECComponent final : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_device_id(const std::string &devid) { this->device_id_.assign(devid); }
|
||||
void set_temperature_offset(float offset) { this->temperature_offset_ = offset; }
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
from esphome.components.const import (
|
||||
CONF_BREATH_VOC_EQUIVALENT,
|
||||
CONF_CO2_EQUIVALENT,
|
||||
CONF_IAQ,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_GAS_RESISTANCE,
|
||||
@@ -29,9 +34,6 @@ from . import CONF_BME680_BSEC_ID, SAMPLE_RATE_OPTIONS, BME680BSECComponent
|
||||
|
||||
DEPENDENCIES = ["bme680_bsec"]
|
||||
|
||||
CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent"
|
||||
CONF_CO2_EQUIVALENT = "co2_equivalent"
|
||||
CONF_IAQ = "iaq"
|
||||
ICON_ACCURACY = "mdi:checkbox-marked-circle-outline"
|
||||
UNIT_IAQ = "IAQ"
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ from pathlib import Path
|
||||
|
||||
from esphome import core, external_files
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_STATE_SAVE_INTERVAL
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
@@ -24,7 +25,6 @@ CONF_ALGORITHM_OUTPUT = "algorithm_output"
|
||||
CONF_BME68X_BSEC2_ID = "bme68x_bsec2_id"
|
||||
CONF_IAQ_MODE = "iaq_mode"
|
||||
CONF_OPERATING_AGE = "operating_age"
|
||||
CONF_STATE_SAVE_INTERVAL = "state_save_interval"
|
||||
CONF_SUPPLY_VOLTAGE = "supply_voltage"
|
||||
|
||||
bme68x_bsec2_ns = cg.esphome_ns.namespace("bme68x_bsec2")
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
from esphome.components.const import (
|
||||
CONF_BREATH_VOC_EQUIVALENT,
|
||||
CONF_CO2_EQUIVALENT,
|
||||
CONF_IAQ,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_GAS_RESISTANCE,
|
||||
@@ -29,9 +34,6 @@ from . import CONF_BME68X_BSEC2_ID, SAMPLE_RATE_OPTIONS, BME68xBSEC2Component
|
||||
|
||||
DEPENDENCIES = ["bme68x_bsec2"]
|
||||
|
||||
CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent"
|
||||
CONF_CO2_EQUIVALENT = "co2_equivalent"
|
||||
CONF_IAQ = "iaq"
|
||||
CONF_IAQ_STATIC = "iaq_static"
|
||||
ICON_ACCURACY = "mdi:checkbox-marked-circle-outline"
|
||||
UNIT_IAQ = "IAQ"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
namespace esphome::bme68x_bsec2_i2c {
|
||||
|
||||
class BME68xBSEC2I2CComponent : public bme68x_bsec2::BME68xBSEC2Component, public i2c::I2CDevice {
|
||||
class BME68xBSEC2I2CComponent final : public bme68x_bsec2::BME68xBSEC2Component, public i2c::I2CDevice {
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace esphome::bmi160 {
|
||||
|
||||
class BMI160Component : public PollingComponent, public i2c::I2CDevice {
|
||||
class BMI160Component final : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
@@ -78,7 +78,7 @@ enum BMI270GyroODR : uint8_t {
|
||||
// ---Data class
|
||||
|
||||
// Main component class
|
||||
class BMI270Component : public motion::MotionComponent, public i2c::I2CDevice {
|
||||
class BMI270Component final : public motion::MotionComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
// Lifecycle
|
||||
void setup() override;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace esphome::bmp085 {
|
||||
|
||||
class BMP085Component : public PollingComponent, public i2c::I2CDevice {
|
||||
class BMP085Component final : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
|
||||
void set_pressure(sensor::Sensor *pressure) { pressure_ = pressure; }
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace esphome::bmp280_i2c {
|
||||
static const char *const TAG = "bmp280_i2c.sensor";
|
||||
|
||||
/// This class implements support for the BMP280 Temperature+Pressure i2c sensor.
|
||||
class BMP280I2CComponent : public esphome::bmp280_base::BMP280Component, public i2c::I2CDevice {
|
||||
class BMP280I2CComponent final : public esphome::bmp280_base::BMP280Component, public i2c::I2CDevice {
|
||||
public:
|
||||
bool bmp_read_byte(uint8_t a_register, uint8_t *data) override { return read_byte(a_register, data); }
|
||||
bool bmp_write_byte(uint8_t a_register, uint8_t data) override { return write_byte(a_register, data); }
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
namespace esphome::bmp280_spi {
|
||||
|
||||
class BMP280SPIComponent : public esphome::bmp280_base::BMP280Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_200KHZ> {
|
||||
class BMP280SPIComponent final : public esphome::bmp280_base::BMP280Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_200KHZ> {
|
||||
void setup() override;
|
||||
bool bmp_read_byte(uint8_t a_register, uint8_t *data) override;
|
||||
bool bmp_write_byte(uint8_t a_register, uint8_t data) override;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
namespace esphome::bmp3xx_i2c {
|
||||
|
||||
class BMP3XXI2CComponent : public bmp3xx_base::BMP3XXComponent, public i2c::I2CDevice {
|
||||
class BMP3XXI2CComponent final : public bmp3xx_base::BMP3XXComponent, public i2c::I2CDevice {
|
||||
bool read_byte(uint8_t a_register, uint8_t *data) override;
|
||||
bool write_byte(uint8_t a_register, uint8_t data) override;
|
||||
bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
namespace esphome::bmp3xx_spi {
|
||||
|
||||
class BMP3XXSPIComponent : public bmp3xx_base::BMP3XXComponent,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> {
|
||||
class BMP3XXSPIComponent final : public bmp3xx_base::BMP3XXComponent,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> {
|
||||
void setup() override;
|
||||
bool read_byte(uint8_t a_register, uint8_t *data) override;
|
||||
bool write_byte(uint8_t a_register, uint8_t data) override;
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace esphome::bmp581_i2c {
|
||||
static const char *const TAG = "bmp581_i2c.sensor";
|
||||
|
||||
/// This class implements support for the BMP581 Temperature+Pressure i2c sensor.
|
||||
class BMP581I2CComponent : public esphome::bmp581_base::BMP581Component, public i2c::I2CDevice {
|
||||
class BMP581I2CComponent final : public esphome::bmp581_base::BMP581Component, public i2c::I2CDevice {
|
||||
public:
|
||||
bool bmp_read_byte(uint8_t a_register, uint8_t *data) override { return read_byte(a_register, data); }
|
||||
bool bmp_write_byte(uint8_t a_register, uint8_t data) override { return write_byte(a_register, data); }
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
namespace esphome::bmp581_spi {
|
||||
|
||||
// BMP581 is technically compatible with SPI Mode0 and Mode3. Default to Mode3.
|
||||
class BMP581SPIComponent : public esphome::bmp581_base::BMP581Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
|
||||
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_200KHZ> {
|
||||
class BMP581SPIComponent final : public esphome::bmp581_base::BMP581Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
|
||||
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_200KHZ> {
|
||||
public:
|
||||
void setup() override;
|
||||
bool bmp_read_byte(uint8_t a_register, uint8_t *data) override;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace esphome::bp1658cj {
|
||||
|
||||
class BP1658CJ : public Component {
|
||||
class BP1658CJ final : public Component {
|
||||
public:
|
||||
class Channel;
|
||||
|
||||
@@ -29,7 +29,7 @@ class BP1658CJ : public Component {
|
||||
/// Send new values if they were updated.
|
||||
void loop() override;
|
||||
|
||||
class Channel : public output::FloatOutput {
|
||||
class Channel final : public output::FloatOutput {
|
||||
public:
|
||||
void set_parent(BP1658CJ *parent) { parent_ = parent; }
|
||||
void set_channel(uint8_t channel) { channel_ = channel; }
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace esphome::bp5758d {
|
||||
|
||||
class BP5758D : public Component {
|
||||
class BP5758D final : public Component {
|
||||
public:
|
||||
class Channel;
|
||||
|
||||
@@ -23,7 +23,7 @@ class BP5758D : public Component {
|
||||
/// Send new values if they were updated.
|
||||
void loop() override;
|
||||
|
||||
class Channel : public output::FloatOutput {
|
||||
class Channel final : public output::FloatOutput {
|
||||
public:
|
||||
void set_parent(BP5758D *parent) { parent_ = parent; }
|
||||
void set_channel(uint8_t channel) { channel_ = channel; }
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
namespace esphome::bthome_mithermometer {
|
||||
|
||||
class BTHomeMiThermometer : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||
class BTHomeMiThermometer final : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||
public:
|
||||
void set_address(uint64_t address) { this->address_ = address; }
|
||||
void set_bindkey(std::initializer_list<uint8_t> bindkey);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace esphome::button {
|
||||
|
||||
template<typename... Ts> class PressAction : public Action<Ts...> {
|
||||
template<typename... Ts> class PressAction final : public Action<Ts...> {
|
||||
public:
|
||||
explicit PressAction(Button *button) : button_(button) {}
|
||||
|
||||
@@ -16,7 +16,7 @@ template<typename... Ts> class PressAction : public Action<Ts...> {
|
||||
Button *button_;
|
||||
};
|
||||
|
||||
class ButtonPressTrigger : public Trigger<> {
|
||||
class ButtonPressTrigger final : public Trigger<> {
|
||||
public:
|
||||
ButtonPressTrigger(Button *button) {
|
||||
button->add_on_press_callback([this]() { this->trigger(); });
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome::camera_encoder {
|
||||
|
||||
class EncoderBufferImpl : public camera::EncoderBuffer {
|
||||
class EncoderBufferImpl final : public camera::EncoderBuffer {
|
||||
public:
|
||||
// --- EncoderBuffer ---
|
||||
bool set_buffer_size(size_t size) override;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace esphome::camera_encoder {
|
||||
|
||||
/// Encoder that uses the software-based JPEG implementation from Espressif's esp32-camera component.
|
||||
class ESP32CameraJPEGEncoder : public camera::Encoder {
|
||||
class ESP32CameraJPEGEncoder final : public camera::Encoder {
|
||||
public:
|
||||
/// Constructs a ESP32CameraJPEGEncoder instance.
|
||||
/// @param quality Sets the quality of the encoded image (1-100).
|
||||
|
||||
@@ -106,7 +106,7 @@ class Canbus : public Component {
|
||||
virtual Error read_message(struct CanFrame *frame) = 0;
|
||||
};
|
||||
|
||||
template<typename... Ts> class CanbusSendAction : public Action<Ts...>, public Parented<Canbus> {
|
||||
template<typename... Ts> class CanbusSendAction final : public Action<Ts...>, public Parented<Canbus> {
|
||||
public:
|
||||
void set_data_template(std::vector<uint8_t> (*func)(Ts...)) {
|
||||
// Stateless lambdas (generated by ESPHome) implicitly convert to function pointers
|
||||
@@ -154,7 +154,7 @@ template<typename... Ts> class CanbusSendAction : public Action<Ts...>, public P
|
||||
} data_;
|
||||
};
|
||||
|
||||
class CanbusTrigger : public Trigger<std::vector<uint8_t>, uint32_t, bool>, public Component {
|
||||
class CanbusTrigger final : public Trigger<std::vector<uint8_t>, uint32_t, bool>, public Component {
|
||||
friend class Canbus;
|
||||
|
||||
public:
|
||||
|
||||
@@ -26,7 +26,7 @@ enum {
|
||||
CAP1188_SENSITVITY = 0x1f,
|
||||
};
|
||||
|
||||
class CAP1188Channel : public binary_sensor::BinarySensor {
|
||||
class CAP1188Channel final : public binary_sensor::BinarySensor {
|
||||
public:
|
||||
void set_channel(uint8_t channel) { channel_ = channel; }
|
||||
void process(uint8_t data) { this->publish_state(static_cast<bool>(data & (1 << this->channel_))); }
|
||||
@@ -35,7 +35,7 @@ class CAP1188Channel : public binary_sensor::BinarySensor {
|
||||
uint8_t channel_{0};
|
||||
};
|
||||
|
||||
class CAP1188Component : public Component, public i2c::I2CDevice {
|
||||
class CAP1188Component final : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
void register_channel(CAP1188Channel *channel) { this->channels_.push_back(channel); }
|
||||
void set_touch_threshold(uint8_t touch_threshold) { this->touch_threshold_ = touch_threshold; };
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
namespace esphome::captive_portal {
|
||||
|
||||
class CaptivePortal : public AsyncWebHandler, public Component {
|
||||
class CaptivePortal final : public AsyncWebHandler, public Component {
|
||||
public:
|
||||
CaptivePortal(web_server_base::WebServerBase *base);
|
||||
void setup() override;
|
||||
|
||||
@@ -16,9 +16,9 @@ class CC1101Listener {
|
||||
virtual void on_packet(const std::vector<uint8_t> &packet, float freq_offset, float rssi, uint8_t lqi) = 0;
|
||||
};
|
||||
|
||||
class CC1101Component : public Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> {
|
||||
class CC1101Component final : public Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> {
|
||||
public:
|
||||
CC1101Component();
|
||||
|
||||
@@ -119,27 +119,27 @@ class CC1101Component : public Component,
|
||||
};
|
||||
|
||||
// Action Wrappers
|
||||
template<typename... Ts> class BeginTxAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
template<typename... Ts> class BeginTxAction final : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->begin_tx(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class BeginRxAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
template<typename... Ts> class BeginRxAction final : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->begin_rx(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class ResetAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
template<typename... Ts> class ResetAction final : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->reset(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetIdleAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
template<typename... Ts> class SetIdleAction final : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->set_idle(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SendPacketAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
template<typename... Ts> class SendPacketAction final : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
void set_data_template(std::function<std::vector<uint8_t>(Ts...)> func) { this->data_func_ = func; }
|
||||
void set_data_static(const uint8_t *data, size_t len) {
|
||||
@@ -163,79 +163,80 @@ template<typename... Ts> class SendPacketAction : public Action<Ts...>, public P
|
||||
size_t data_static_len_{0};
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetSymbolRateAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
template<typename... Ts> class SetSymbolRateAction final : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, symbol_rate)
|
||||
void play(const Ts &...x) override { this->parent_->set_symbol_rate(this->symbol_rate_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetFrequencyAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
template<typename... Ts> class SetFrequencyAction final : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, frequency)
|
||||
void play(const Ts &...x) override { this->parent_->set_frequency(this->frequency_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetOutputPowerAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
template<typename... Ts> class SetOutputPowerAction final : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, output_power)
|
||||
void play(const Ts &...x) override { this->parent_->set_output_power(this->output_power_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetModulationTypeAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
template<typename... Ts> class SetModulationTypeAction final : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(Modulation, modulation_type)
|
||||
void play(const Ts &...x) override { this->parent_->set_modulation_type(this->modulation_type_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetRxAttenuationAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
template<typename... Ts> class SetRxAttenuationAction final : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(RxAttenuation, rx_attenuation)
|
||||
void play(const Ts &...x) override { this->parent_->set_rx_attenuation(this->rx_attenuation_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetDcBlockingFilterAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
template<typename... Ts>
|
||||
class SetDcBlockingFilterAction final : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(bool, dc_blocking_filter)
|
||||
void play(const Ts &...x) override { this->parent_->set_dc_blocking_filter(this->dc_blocking_filter_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetManchesterAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
template<typename... Ts> class SetManchesterAction final : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(bool, manchester)
|
||||
void play(const Ts &...x) override { this->parent_->set_manchester(this->manchester_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetFilterBandwidthAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
template<typename... Ts> class SetFilterBandwidthAction final : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, filter_bandwidth)
|
||||
void play(const Ts &...x) override { this->parent_->set_filter_bandwidth(this->filter_bandwidth_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetFskDeviationAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
template<typename... Ts> class SetFskDeviationAction final : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, fsk_deviation)
|
||||
void play(const Ts &...x) override { this->parent_->set_fsk_deviation(this->fsk_deviation_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetMskDeviationAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
template<typename... Ts> class SetMskDeviationAction final : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, msk_deviation)
|
||||
void play(const Ts &...x) override { this->parent_->set_msk_deviation(this->msk_deviation_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetChannelAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
template<typename... Ts> class SetChannelAction final : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, channel)
|
||||
void play(const Ts &...x) override { this->parent_->set_channel(this->channel_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetChannelSpacingAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
template<typename... Ts> class SetChannelSpacingAction final : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, channel_spacing)
|
||||
void play(const Ts &...x) override { this->parent_->set_channel_spacing(this->channel_spacing_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetIfFrequencyAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
template<typename... Ts> class SetIfFrequencyAction final : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, if_frequency)
|
||||
void play(const Ts &...x) override { this->parent_->set_if_frequency(this->if_frequency_.value(x...)); }
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace esphome::ccs811 {
|
||||
|
||||
class CCS811Component : public PollingComponent, public i2c::I2CDevice {
|
||||
class CCS811Component final : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_co2(sensor::Sensor *co2) { co2_ = co2; }
|
||||
void set_tvoc(sensor::Sensor *tvoc) { tvoc_ = tvoc; }
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace esphome::cd74hc4067 {
|
||||
|
||||
class CD74HC4067Component : public Component {
|
||||
class CD74HC4067Component final : public Component {
|
||||
public:
|
||||
/// Set up the internal sensor array.
|
||||
void setup() override;
|
||||
@@ -38,7 +38,7 @@ class CD74HC4067Component : public Component {
|
||||
uint32_t switch_delay_;
|
||||
};
|
||||
|
||||
class CD74HC4067Sensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
|
||||
class CD74HC4067Sensor final : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
|
||||
public:
|
||||
CD74HC4067Sensor(CD74HC4067Component *parent);
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace esphome::ch422g {
|
||||
|
||||
class CH422GComponent : public Component, public i2c::I2CDevice {
|
||||
class CH422GComponent final : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
CH422GComponent() = default;
|
||||
|
||||
@@ -42,7 +42,7 @@ class CH422GComponent : public Component, public i2c::I2CDevice {
|
||||
};
|
||||
|
||||
/// Helper class to expose a CH422G pin as a GPIO pin.
|
||||
class CH422GGPIOPin : public GPIOPin {
|
||||
class CH422GGPIOPin final : public GPIOPin {
|
||||
public:
|
||||
void setup() override{};
|
||||
void pin_mode(gpio::Flags flags) override;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace esphome::ch423 {
|
||||
|
||||
class CH423Component : public Component, public i2c::I2CDevice {
|
||||
class CH423Component final : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
CH423Component() = default;
|
||||
|
||||
@@ -41,7 +41,7 @@ class CH423Component : public Component, public i2c::I2CDevice {
|
||||
};
|
||||
|
||||
/// Helper class to expose a CH423 pin as a GPIO pin.
|
||||
class CH423GPIOPin : public GPIOPin {
|
||||
class CH423GPIOPin final : public GPIOPin {
|
||||
public:
|
||||
void setup() override{};
|
||||
void pin_mode(gpio::Flags flags) override;
|
||||
|
||||
@@ -17,7 +17,7 @@ static const uint8_t CHSC6X_REG_STATUS_Y_COR = 0x04;
|
||||
static const uint8_t CHSC6X_REG_STATUS_LEN = 0x05;
|
||||
static const uint8_t CHSC6X_CHIP_ID = 0x2e;
|
||||
|
||||
class CHSC6XTouchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice {
|
||||
class CHSC6XTouchscreen final : public touchscreen::Touchscreen, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void update_touches() override;
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace esphome::climate {
|
||||
// (e.g. `const T & &` if Ts already carries a reference, or `const const
|
||||
// T &` if Ts already carries a const). This keeps trigger args no-copy
|
||||
// regardless of whether the trigger supplies `T`, `T &`, or `const T &`.
|
||||
template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||
template<typename... Ts> class ControlAction final : public Action<Ts...> {
|
||||
public:
|
||||
using ApplyFn = void (*)(ClimateCall &, const std::remove_cvref_t<Ts> &...);
|
||||
ControlAction(Climate *climate, ApplyFn apply) : climate_(climate), apply_(apply) {}
|
||||
@@ -33,14 +33,14 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||
ApplyFn apply_;
|
||||
};
|
||||
|
||||
class ControlTrigger : public Trigger<ClimateCall &> {
|
||||
class ControlTrigger final : public Trigger<ClimateCall &> {
|
||||
public:
|
||||
ControlTrigger(Climate *climate) {
|
||||
climate->add_on_control_callback([this](ClimateCall &x) { this->trigger(x); });
|
||||
}
|
||||
};
|
||||
|
||||
class StateTrigger : public Trigger<Climate &> {
|
||||
class StateTrigger final : public Trigger<Climate &> {
|
||||
public:
|
||||
StateTrigger(Climate *climate) {
|
||||
climate->add_on_state_callback([this](Climate &x) { this->trigger(x); });
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace esphome::climate_ir_lg {
|
||||
const uint8_t TEMP_MIN = 18; // Celsius
|
||||
const uint8_t TEMP_MAX = 30; // Celsius
|
||||
|
||||
class LgIrClimate : public climate_ir::ClimateIR {
|
||||
class LgIrClimate final : public climate_ir::ClimateIR {
|
||||
public:
|
||||
LgIrClimate()
|
||||
: climate_ir::ClimateIR(TEMP_MIN, TEMP_MAX, 1.0f, true, true,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace esphome::cm1106 {
|
||||
|
||||
class CM1106Component : public PollingComponent, public uart::UARTDevice {
|
||||
class CM1106Component final : public PollingComponent, public uart::UARTDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void update() override;
|
||||
@@ -23,7 +23,7 @@ class CM1106Component : public PollingComponent, public uart::UARTDevice {
|
||||
bool cm1106_write_command_(const uint8_t *command, size_t command_len, uint8_t *response, size_t response_len);
|
||||
};
|
||||
|
||||
template<typename... Ts> class CM1106CalibrateZeroAction : public Action<Ts...> {
|
||||
template<typename... Ts> class CM1106CalibrateZeroAction final : public Action<Ts...> {
|
||||
public:
|
||||
CM1106CalibrateZeroAction(CM1106Component *cm1106) : cm1106_(cm1106) {}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace esphome::color_temperature {
|
||||
|
||||
class CTLightOutput : public light::LightOutput {
|
||||
class CTLightOutput final : public light::LightOutput {
|
||||
public:
|
||||
void set_color_temperature(output::FloatOutput *color_temperature) { color_temperature_ = color_temperature; }
|
||||
void set_brightness(output::FloatOutput *brightness) { brightness_ = brightness; }
|
||||
|
||||
@@ -204,7 +204,7 @@ void MedianCombinationComponent::handle_new_value(float value) {
|
||||
median = sensor_states[sensor_states_size / 2];
|
||||
} else {
|
||||
// Even number of measurements, use the average of the two middle measurements
|
||||
median = (sensor_states[sensor_states_size / 2] + sensor_states[sensor_states_size / 2 - 1]) / 2.0;
|
||||
median = (sensor_states[sensor_states_size / 2] + sensor_states[sensor_states_size / 2 - 1]) / 2.0f;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ class CombinationOneParameterComponent : public CombinationComponent {
|
||||
FixedVector<SensorSource> sensor_sources_;
|
||||
};
|
||||
|
||||
class KalmanCombinationComponent : public CombinationOneParameterComponent {
|
||||
class KalmanCombinationComponent final : public CombinationOneParameterComponent {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void setup() override;
|
||||
@@ -85,7 +85,7 @@ class KalmanCombinationComponent : public CombinationOneParameterComponent {
|
||||
float variance_{INFINITY};
|
||||
};
|
||||
|
||||
class LinearCombinationComponent : public CombinationOneParameterComponent {
|
||||
class LinearCombinationComponent final : public CombinationOneParameterComponent {
|
||||
public:
|
||||
void dump_config() override { this->log_config_(LOG_STR("linear")); }
|
||||
void setup() override;
|
||||
@@ -93,49 +93,49 @@ class LinearCombinationComponent : public CombinationOneParameterComponent {
|
||||
void handle_new_value(float value);
|
||||
};
|
||||
|
||||
class MaximumCombinationComponent : public CombinationNoParameterComponent {
|
||||
class MaximumCombinationComponent final : public CombinationNoParameterComponent {
|
||||
public:
|
||||
void dump_config() override { this->log_config_(LOG_STR("max")); }
|
||||
|
||||
void handle_new_value(float value) override;
|
||||
};
|
||||
|
||||
class MeanCombinationComponent : public CombinationNoParameterComponent {
|
||||
class MeanCombinationComponent final : public CombinationNoParameterComponent {
|
||||
public:
|
||||
void dump_config() override { this->log_config_(LOG_STR("mean")); }
|
||||
|
||||
void handle_new_value(float value) override;
|
||||
};
|
||||
|
||||
class MedianCombinationComponent : public CombinationNoParameterComponent {
|
||||
class MedianCombinationComponent final : public CombinationNoParameterComponent {
|
||||
public:
|
||||
void dump_config() override { this->log_config_(LOG_STR("median")); }
|
||||
|
||||
void handle_new_value(float value) override;
|
||||
};
|
||||
|
||||
class MinimumCombinationComponent : public CombinationNoParameterComponent {
|
||||
class MinimumCombinationComponent final : public CombinationNoParameterComponent {
|
||||
public:
|
||||
void dump_config() override { this->log_config_(LOG_STR("min")); }
|
||||
|
||||
void handle_new_value(float value) override;
|
||||
};
|
||||
|
||||
class MostRecentCombinationComponent : public CombinationNoParameterComponent {
|
||||
class MostRecentCombinationComponent final : public CombinationNoParameterComponent {
|
||||
public:
|
||||
void dump_config() override { this->log_config_(LOG_STR("most_recently_updated")); }
|
||||
|
||||
void handle_new_value(float value) override;
|
||||
};
|
||||
|
||||
class RangeCombinationComponent : public CombinationNoParameterComponent {
|
||||
class RangeCombinationComponent final : public CombinationNoParameterComponent {
|
||||
public:
|
||||
void dump_config() override { this->log_config_(LOG_STR("range")); }
|
||||
|
||||
void handle_new_value(float value) override;
|
||||
};
|
||||
|
||||
class SumCombinationComponent : public CombinationNoParameterComponent {
|
||||
class SumCombinationComponent final : public CombinationNoParameterComponent {
|
||||
public:
|
||||
void dump_config() override { this->log_config_(LOG_STR("sum")); }
|
||||
|
||||
|
||||
@@ -8,8 +8,10 @@ BYTE_ORDER_BIG = "big_endian"
|
||||
CONF_ACCELEROMETER_ODR = "accelerometer_odr"
|
||||
CONF_ACCELEROMETER_RANGE = "accelerometer_range"
|
||||
CONF_B_CONSTANT = "b_constant"
|
||||
CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent"
|
||||
CONF_BYTE_ORDER = "byte_order"
|
||||
CONF_CLIMATE_ID = "climate_id"
|
||||
CONF_CO2_EQUIVALENT = "co2_equivalent"
|
||||
CONF_COLOR_DEPTH = "color_depth"
|
||||
CONF_CRC_ENABLE = "crc_enable"
|
||||
CONF_DATA_BITS = "data_bits"
|
||||
@@ -17,6 +19,7 @@ CONF_DRAW_ROUNDING = "draw_rounding"
|
||||
CONF_ENABLED = "enabled"
|
||||
CONF_GYROSCOPE_ODR = "gyroscope_odr"
|
||||
CONF_GYROSCOPE_RANGE = "gyroscope_range"
|
||||
CONF_IAQ = "iaq"
|
||||
CONF_IGNORE_NOT_FOUND = "ignore_not_found"
|
||||
CONF_LIBRETINY = "libretiny"
|
||||
CONF_LOOP = "loop"
|
||||
@@ -28,6 +31,7 @@ CONF_RECEIVER_FREQUENCY = "receiver_frequency"
|
||||
CONF_REQUEST_HEADERS = "request_headers"
|
||||
CONF_ROWS = "rows"
|
||||
CONF_SHA256 = "sha256"
|
||||
CONF_STATE_SAVE_INTERVAL = "state_save_interval"
|
||||
CONF_STOP_BITS = "stop_bits"
|
||||
CONF_USE_PSRAM = "use_psram"
|
||||
CONF_VOLUME_INCREMENT = "volume_increment"
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace esphome::coolix {
|
||||
const uint8_t COOLIX_TEMP_MIN = 17; // Celsius
|
||||
const uint8_t COOLIX_TEMP_MAX = 30; // Celsius
|
||||
|
||||
class CoolixClimate : public climate_ir::ClimateIR {
|
||||
class CoolixClimate final : public climate_ir::ClimateIR {
|
||||
public:
|
||||
CoolixClimate()
|
||||
: climate_ir::ClimateIR(COOLIX_TEMP_MIN, COOLIX_TEMP_MAX, 1.0f, true, true,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome::copy {
|
||||
|
||||
class CopyBinarySensor : public binary_sensor::BinarySensor, public Component {
|
||||
class CopyBinarySensor final : public binary_sensor::BinarySensor, public Component {
|
||||
public:
|
||||
void set_source(binary_sensor::BinarySensor *source) { source_ = source; }
|
||||
void setup() override;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome::copy {
|
||||
|
||||
class CopyButton : public button::Button, public Component {
|
||||
class CopyButton final : public button::Button, public Component {
|
||||
public:
|
||||
void set_source(button::Button *source) { source_ = source; }
|
||||
void dump_config() override;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome::copy {
|
||||
|
||||
class CopyCover : public cover::Cover, public Component {
|
||||
class CopyCover final : public cover::Cover, public Component {
|
||||
public:
|
||||
void set_source(cover::Cover *source) { source_ = source; }
|
||||
void setup() override;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome::copy {
|
||||
|
||||
class CopyFan : public fan::Fan, public Component {
|
||||
class CopyFan final : public fan::Fan, public Component {
|
||||
public:
|
||||
void set_source(fan::Fan *source) { source_ = source; }
|
||||
void setup() override;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome::copy {
|
||||
|
||||
class CopyLock : public lock::Lock, public Component {
|
||||
class CopyLock final : public lock::Lock, public Component {
|
||||
public:
|
||||
void set_source(lock::Lock *source) { source_ = source; }
|
||||
void setup() override;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome::copy {
|
||||
|
||||
class CopyNumber : public number::Number, public Component {
|
||||
class CopyNumber final : public number::Number, public Component {
|
||||
public:
|
||||
void set_source(number::Number *source) { source_ = source; }
|
||||
void setup() override;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome::copy {
|
||||
|
||||
class CopySelect : public select::Select, public Component {
|
||||
class CopySelect final : public select::Select, public Component {
|
||||
public:
|
||||
void set_source(select::Select *source) { source_ = source; }
|
||||
void setup() override;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome::copy {
|
||||
|
||||
class CopySensor : public sensor::Sensor, public Component {
|
||||
class CopySensor final : public sensor::Sensor, public Component {
|
||||
public:
|
||||
void set_source(sensor::Sensor *source) { source_ = source; }
|
||||
void setup() override;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome::copy {
|
||||
|
||||
class CopySwitch : public switch_::Switch, public Component {
|
||||
class CopySwitch final : public switch_::Switch, public Component {
|
||||
public:
|
||||
void set_source(switch_::Switch *source) { source_ = source; }
|
||||
void setup() override;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user