diff --git a/.ai/instructions.md b/.ai/instructions.md index 240a47a52f..86f554e9ce 100644 --- a/.ai/instructions.md +++ b/.ai/instructions.md @@ -124,6 +124,28 @@ This document provides essential context for AI models interacting with this pro * **Indentation:** Use spaces (two per indentation level), not tabs * **Type aliases:** Prefer `using type_t = int;` over `typedef int type_t;` * **Line length:** Wrap lines at no more than 120 characters + * **Constructor parameters vs setters:** Component properties that are both **required** and **invariant** + (never change after construction) should be constructor parameters rather than set via setter methods. + This makes the dependency explicit and prevents use of the object in an incompletely-initialized state. + In code generation, when calling `cg.new_Pvariable()` or the relevant helper function to create the component, pass these as arguments. + ```cpp + // Good - required invariant dependency as constructor parameter + class SourceTextSensor : public text_sensor::TextSensor, public Component { + public: + explicit SourceTextSensor(text::Text *source) : source_(source) {} + protected: + text::Text *source_; + }; + ``` + ```cpp + // Bad - required invariant dependency as setter + class SourceTextSensor : public text_sensor::TextSensor, public Component { + public: + void set_source(text::Text *source) { this->source_ = source; } + protected: + text::Text *source_{nullptr}; + }; + ``` * **Component Structure:** * **Standard Files:** @@ -217,6 +239,123 @@ This document provides essential context for AI models interacting with this pro var = await switch.new_switch(config) ``` +* **Automations (Triggers, Actions, Conditions):** + + Automations have three building blocks: **Triggers** (fire when something happens), **Actions** (do something), and **Conditions** (check if something is true). + + * **Triggers -- Callback method (preferred):** + + Use `build_callback_automation()` for simple triggers. This eliminates the need for a C++ Trigger class by using a lightweight pointer-sized forwarder struct registered directly as a callback. No `CONF_TRIGGER_ID` in the schema. + + **Python:** + ```python + from esphome import automation + + CONFIG_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.declare_id(MyComponent), + cv.Optional(CONF_ON_STATE): automation.validate_automation({}), + }).extend(cv.COMPONENT_SCHEMA) + + async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + for conf in config.get(CONF_ON_STATE, []): + await automation.build_callback_automation( + var, "add_on_state_callback", [(bool, "x")], conf + ) + ``` + + `build_callback_automation` arguments: `parent`, `callback_method` (C++ method name), `args` (template args as `[(type, name)]` tuples), `config`, and optional `forwarder` (defaults to `TriggerForwarder`). + + For boolean filtering (e.g. `on_press`/`on_release`), use built-in forwarders with `args=[]`: + ```python + for conf_key, forwarder in ( + (CONF_ON_PRESS, automation.TriggerOnTrueForwarder), + (CONF_ON_RELEASE, automation.TriggerOnFalseForwarder), + ): + for conf in config.get(conf_key, []): + await automation.build_callback_automation( + var, "add_on_state_callback", [], conf, forwarder=forwarder + ) + ``` + + **C++ -- no trigger class needed.** The callback registration method must be templatized to accept both `std::function` and lightweight forwarder structs (which avoid heap allocation): + ```cpp + class MyComponent : public Component { + public: + // Must be a template -- accepts both std::function and pointer-sized forwarder structs + template void add_on_state_callback(F &&callback) { + this->state_callback_.add(std::forward(callback)); + } + protected: + // Use CallbackManager when callbacks are always registered (e.g. core components) + CallbackManager state_callback_; + // Use LazyCallbackManager when callbacks are often not registered -- saves 8 bytes + // (nullptr vs empty std::vector) per instance when no callbacks are added + // LazyCallbackManager state_callback_; + }; + ``` + + * **Triggers -- Trigger class method:** + + Use `build_automation()` with a `Trigger` subclass only when the forwarder needs **mutable state beyond a single `Automation*` pointer** (e.g. edge detection tracking previous state, timing logic). + + **Python:** + ```python + TurnOnTrigger = my_ns.class_("TurnOnTrigger", automation.Trigger.template()) + + CONFIG_SCHEMA = cv.Schema({ + cv.Optional(CONF_ON_TURN_ON): automation.validate_automation( + {cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TurnOnTrigger)} + ), + }) + + async def to_code(config): + for conf in config.get(CONF_ON_TURN_ON, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + ``` + + **C++:** + ```cpp + class TurnOnTrigger : public Trigger<> { + public: + explicit TurnOnTrigger(MyComponent *parent) : last_on_{false} { + parent->add_on_state_callback([this](bool state) { + if (state && !this->last_on_) + this->trigger(); + this->last_on_ = state; + }); + } + protected: + bool last_on_; + }; + ``` + + * **Actions:** + ```cpp + template class MyAction : public Action { + public: + explicit MyAction(MyComponent *parent) : parent_(parent) {} + void play(const Ts &...) override { this->parent_->do_something(); } + protected: + MyComponent *parent_; + }; + ``` + Register with `@automation.register_action("my_component.do_something", MyAction, schema, synchronous=True)`. Use `synchronous=True` for actions that run to completion inside `play()` without deferring. Use `synchronous=False` if the action may suspend/defer execution (e.g. `delay`, `wait_until`, `script.wait`) or store trigger arguments for later use. + + * **Conditions:** + ```cpp + template class MyCondition : public Condition { + public: + explicit MyCondition(MyComponent *parent) : parent_(parent) {} + bool check(const Ts &...) override { return this->parent_->is_active(); } + protected: + MyComponent *parent_; + }; + ``` + Register with `@automation.register_condition("my_component.is_active", MyCondition, schema)`. + * **Configuration Validation:** * **Common Validators:** `cv.int_`, `cv.float_`, `cv.string`, `cv.boolean`, `cv.int_range(min=0, max=100)`, `cv.positive_int`, `cv.percentage`. * **Complex Validation:** `cv.All(cv.string, cv.Length(min=1, max=50))`, `cv.Any(cv.int_, cv.string)`. @@ -252,10 +391,39 @@ This document provides essential context for AI models interacting with this pro * **Component Tests:** YAML-based compilation tests are located in `tests/`. The structure is as follows: ``` tests/ - ├── test_build_components/ # Base test configurations - └── components/[component]/ # Component-specific tests + ├── test_build_components/ + │ └── common/ # Shared bus packages (uart, i2c, spi, etc.) + │ ├── uart/ # UART at default baud rate + │ ├── uart_115200/ # UART at 115200 baud + │ ├── i2c/ # I2C bus + │ └── spi/ # SPI bus + └── components/[component]/ + ├── common.yaml # Component-only config (no bus definitions) + ├── test.esp32-idf.yaml + ├── test.esp8266-ard.yaml + └── test.rp2040-ard.yaml ``` Run them using `script/test_build_components`. Use `-c ` to test specific components and `-t ` for specific platforms. + + * **Test Grouping with Packages:** Components that use shared bus packages can be grouped together in CI to reduce build count. **Never define buses (uart, i2c, spi, modbus) directly in test YAML files** — always use packages from `test_build_components/common/`: + ```yaml + # test.esp32-idf.yaml — use packages for buses + packages: + uart: !include ../../test_build_components/common/uart_115200/esp32-idf.yaml + + <<: !include common.yaml + ``` + ```yaml + # common.yaml — component config only, NO bus definitions + my_component: + id: my_instance + + sensor: + - platform: my_component + name: My Sensor + ``` + Components that define buses directly are flagged as "NEEDS MIGRATION" and cannot be grouped, increasing CI build time. + * **Testing All Components Together:** To verify that all components can be tested together without ID conflicts or configuration issues, use: ```bash ./script/test_component_grouping.py -e config --all @@ -395,6 +563,30 @@ This document provides essential context for AI models interacting with this pro Note: Avoiding heap allocation after `setup()` is always required regardless of component type. The prioritization above is about the effort spent on container optimization (e.g., migrating from `std::vector` to `StaticVector`). + **Callback Managers:** + + ESPHome provides two callback manager types in `esphome/core/helpers.h` for the observer pattern. Both support `std::function`, lambdas, and lightweight forwarder structs via their templatized `add()` method. + + | Type | Idle overhead (32-bit) | When to use | + |------|----------------------|-------------| + | `CallbackManager` | 12 bytes (empty `std::vector`) | Callbacks are always or almost always registered | + | `LazyCallbackManager` | 4 bytes (`nullptr`) | Callbacks are often not registered (common case) | + + `LazyCallbackManager` is a drop-in replacement for `CallbackManager` that defers allocation until the first callback is added. Prefer it for entity-level callbacks where most instances have no subscribers. + + **Important:** Registration methods that add to a callback manager **must always be templatized** to accept both `std::function` and pointer-sized forwarder structs (used by `build_callback_automation`). Never use `std::function` in the method signature: + ```cpp + // Bad -- forces heap allocation for forwarder structs + void add_on_state_callback(std::function &&callback) { + this->state_callback_.add(std::move(callback)); + } + + // Good -- accepts any callable without forcing std::function wrapping + template void add_on_state_callback(F &&callback) { + this->state_callback_.add(std::forward(callback)); + } + ``` + * **State Management:** Use `CORE.data` for component state that needs to persist during configuration generation. Avoid module-level mutable globals. **Bad Pattern (Module-Level Globals):** diff --git a/.clang-tidy.hash b/.clang-tidy.hash index 87b4ebb2c6..5c7eab517b 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -8e48e836c6fc196d3da000d46eb09db243b87fe33518a74e49c8e009d756074a +f31f13994768b5b07e29624406c9b053bf4bb26e1623ac2bc1e9d4a9477502d6 diff --git a/.github/actions/restore-python/action.yml b/.github/actions/restore-python/action.yml index 6d7d4f8c12..af54175c01 100644 --- a/.github/actions/restore-python/action.yml +++ b/.github/actions/restore-python/action.yml @@ -22,7 +22,7 @@ runs: python-version: ${{ inputs.python-version }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: venv # yamllint disable-line rule:line-length diff --git a/.github/scripts/auto-label-pr/detectors.js b/.github/scripts/auto-label-pr/detectors.js index fc63198019..fb9dadc6a0 100644 --- a/.github/scripts/auto-label-pr/detectors.js +++ b/.github/scripts/auto-label-pr/detectors.js @@ -235,19 +235,20 @@ async function detectDeprecatedComponents(github, context, changedFiles) { } } - // Get PR head to fetch files from the PR branch - const prNumber = context.payload.pull_request.number; + // Get base branch ref to check if deprecation already exists for the component + // This prevents flagging a PR that simply adds deprecation + const baseRef = context.payload.pull_request.base.ref; // Check each component's __init__.py for DEPRECATED_COMPONENT constant for (const component of components) { const initFile = `esphome/components/${component}/__init__.py`; try { - // Fetch file content from PR head using GitHub API + // Fetch file content from base branch using GitHub API const { data: fileData } = await github.rest.repos.getContent({ owner, repo, path: initFile, - ref: `refs/pull/${prNumber}/head` + ref: baseRef }); // Decode base64 content diff --git a/.github/workflows/auto-label-pr.yml b/.github/workflows/auto-label-pr.yml index 6376cf877e..3b5e9f0d15 100644 --- a/.github/workflows/auto-label-pr.yml +++ b/.github/workflows/auto-label-pr.yml @@ -27,7 +27,7 @@ jobs: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v2 with: app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }} private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }} diff --git a/.github/workflows/ci-clang-tidy-hash.yml b/.github/workflows/ci-clang-tidy-hash.yml index 5054a62207..7905739b15 100644 --- a/.github/workflows/ci-clang-tidy-hash.yml +++ b/.github/workflows/ci-clang-tidy-hash.yml @@ -40,7 +40,7 @@ jobs: echo "You have modified clang-tidy configuration but have not updated the hash." | tee -a $GITHUB_STEP_SUMMARY echo "Please run 'script/clang_tidy_hash.py --update' and commit the changes." | tee -a $GITHUB_STEP_SUMMARY - - if: failure() + - if: failure() && github.event.pull_request.head.repo.full_name == github.repository name: Request changes uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: @@ -53,7 +53,7 @@ jobs: body: 'You have modified clang-tidy configuration but have not updated the hash.\nPlease run `script/clang_tidy_hash.py --update` and commit the changes.' }) - - if: success() + - if: success() && github.event.pull_request.head.repo.full_name == github.repository name: Dismiss review uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4c9e8c58bc..dddf21f57e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: venv # yamllint disable-line rule:line-length @@ -106,6 +106,7 @@ jobs: script/build_codeowners.py --check script/build_language_schema.py --check script/generate-esp32-boards.py --check + script/generate-rp2040-boards.py --check pytest: name: Run pytest @@ -153,12 +154,12 @@ jobs: . venv/bin/activate pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/ - name: Upload coverage to Codecov - uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 + uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 with: token: ${{ secrets.CODECOV_TOKEN }} - name: Save Python virtual environment cache if: github.ref == 'refs/heads/dev' - uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: venv key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }} @@ -170,6 +171,8 @@ jobs: - common outputs: integration-tests: ${{ steps.determine.outputs.integration-tests }} + integration-tests-run-all: ${{ steps.determine.outputs.integration-tests-run-all }} + integration-test-files: ${{ steps.determine.outputs.integration-test-files }} clang-tidy: ${{ steps.determine.outputs.clang-tidy }} clang-tidy-mode: ${{ steps.determine.outputs.clang-tidy-mode }} python-linters: ${{ steps.determine.outputs.python-linters }} @@ -182,6 +185,7 @@ jobs: cpp-unit-tests-run-all: ${{ steps.determine.outputs.cpp-unit-tests-run-all }} cpp-unit-tests-components: ${{ steps.determine.outputs.cpp-unit-tests-components }} component-test-batches: ${{ steps.determine.outputs.component-test-batches }} + benchmarks: ${{ steps.determine.outputs.benchmarks }} steps: - name: Check out code from GitHub uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -194,7 +198,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} cache-key: ${{ needs.common.outputs.cache-key }} - name: Restore components graph cache - uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: .temp/components_graph.json key: components-graph-${{ hashFiles('esphome/components/**/*.py') }} @@ -210,6 +214,8 @@ jobs: # Extract individual fields echo "integration-tests=$(echo "$output" | jq -r '.integration_tests')" >> $GITHUB_OUTPUT + echo "integration-tests-run-all=$(echo "$output" | jq -r '.integration_tests_run_all')" >> $GITHUB_OUTPUT + echo "integration-test-files=$(echo "$output" | jq -c '.integration_test_files')" >> $GITHUB_OUTPUT echo "clang-tidy=$(echo "$output" | jq -r '.clang_tidy')" >> $GITHUB_OUTPUT echo "clang-tidy-mode=$(echo "$output" | jq -r '.clang_tidy_mode')" >> $GITHUB_OUTPUT echo "python-linters=$(echo "$output" | jq -r '.python_linters')" >> $GITHUB_OUTPUT @@ -222,9 +228,10 @@ jobs: echo "cpp-unit-tests-run-all=$(echo "$output" | jq -r '.cpp_unit_tests_run_all')" >> $GITHUB_OUTPUT echo "cpp-unit-tests-components=$(echo "$output" | jq -c '.cpp_unit_tests_components')" >> $GITHUB_OUTPUT echo "component-test-batches=$(echo "$output" | jq -c '.component_test_batches')" >> $GITHUB_OUTPUT + echo "benchmarks=$(echo "$output" | jq -r '.benchmarks')" >> $GITHUB_OUTPUT - name: Save components graph cache if: github.ref == 'refs/heads/dev' - uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: .temp/components_graph.json key: components-graph-${{ hashFiles('esphome/components/**/*.py') }} @@ -246,7 +253,7 @@ jobs: python-version: "3.13" - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: venv key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }} @@ -261,9 +268,20 @@ jobs: - name: Register matcher run: echo "::add-matcher::.github/workflows/matchers/pytest.json" - name: Run integration tests + env: + INTEGRATION_TEST_FILES: ${{ needs.determine-jobs.outputs.integration-test-files }} + INTEGRATION_TESTS_RUN_ALL: ${{ needs.determine-jobs.outputs.integration-tests-run-all }} run: | . venv/bin/activate - pytest -vv --no-cov --tb=native -n auto tests/integration/ + if [[ "$INTEGRATION_TESTS_RUN_ALL" == "true" ]]; then + echo "Running all integration tests" + pytest -vv --no-cov --tb=native -n auto tests/integration/ + else + # Parse JSON array into bash array to avoid shell expansion issues + mapfile -t test_files < <(echo "$INTEGRATION_TEST_FILES" | jq -r '.[]') + echo "Running ${#test_files[@]} specific integration tests" + pytest -vv --no-cov --tb=native -n auto "${test_files[@]}" + fi cpp-unit-tests: name: Run C++ unit tests @@ -292,6 +310,40 @@ jobs: script/cpp_unit_test.py $ARGS fi + benchmarks: + name: Run CodSpeed benchmarks + runs-on: ubuntu-24.04 + needs: + - common + - determine-jobs + if: >- + (github.event_name == 'push' && github.ref_name == 'dev') || + (github.event_name == 'pull_request' && needs.determine-jobs.outputs.benchmarks == 'true') + steps: + - name: Check out code from GitHub + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Restore Python + uses: ./.github/actions/restore-python + with: + python-version: ${{ env.DEFAULT_PYTHON }} + cache-key: ${{ needs.common.outputs.cache-key }} + + - name: Build benchmarks + id: build + run: | + . venv/bin/activate + export BENCHMARK_LIB_CONFIG=$(python script/setup_codspeed_lib.py) + # --build-only prints BUILD_BINARY= to stdout + BINARY=$(script/cpp_benchmark.py --all --build-only | grep '^BUILD_BINARY=' | tail -1 | cut -d= -f2-) + echo "binary=$BINARY" >> $GITHUB_OUTPUT + + - name: Run CodSpeed benchmarks + uses: CodSpeedHQ/action@db35df748deb45fdef0960669f57d627c1956c30 # v4 + with: + run: ${{ steps.build.outputs.binary }} + mode: simulation + clang-tidy-single: name: ${{ matrix.name }} runs-on: ubuntu-24.04 @@ -335,14 +387,14 @@ jobs: - name: Cache platformio if: github.ref == 'refs/heads/dev' - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: ~/.platformio key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} - name: Cache platformio if: github.ref != 'refs/heads/dev' - uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: ~/.platformio key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} @@ -414,14 +466,14 @@ jobs: - name: Cache platformio if: github.ref == 'refs/heads/dev' - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: ~/.platformio key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }} - name: Cache platformio if: github.ref != 'refs/heads/dev' - uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: ~/.platformio key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }} @@ -503,14 +555,14 @@ jobs: - name: Cache platformio if: github.ref == 'refs/heads/dev' - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: ~/.platformio key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }} - name: Cache platformio if: github.ref != 'refs/heads/dev' - uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: ~/.platformio key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }} @@ -671,7 +723,7 @@ jobs: cache-key: ${{ needs.common.outputs.cache-key }} - uses: esphome/pre-commit-action@43cd1109c09c544d97196f7730ee5b2e0cc6d81e # v3.0.1 fork with pinned actions/cache env: - SKIP: pylint,clang-tidy-hash + SKIP: pylint,clang-tidy-hash,ci-custom - uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0 if: always() @@ -765,7 +817,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@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: memory-analysis-target.json key: ${{ steps.cache-key.outputs.cache-key }} @@ -789,7 +841,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@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: ~/.platformio key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }} @@ -830,7 +882,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@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: memory-analysis-target.json key: ${{ steps.cache-key.outputs.cache-key }} @@ -877,7 +929,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} cache-key: ${{ needs.common.outputs.cache-key }} - name: Cache platformio - uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: ~/.platformio key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }} @@ -945,13 +997,13 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} cache-key: ${{ needs.common.outputs.cache-key }} - name: Download target analysis JSON - uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: memory-analysis-target path: ./memory-analysis continue-on-error: true - name: Download PR analysis JSON - uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: memory-analysis-pr path: ./memory-analysis diff --git a/.github/workflows/codeowner-approved-label-update.yml b/.github/workflows/codeowner-approved-label-update.yml index 0bce33ebe2..34ff934b77 100644 --- a/.github/workflows/codeowner-approved-label-update.yml +++ b/.github/workflows/codeowner-approved-label-update.yml @@ -10,6 +10,9 @@ name: Codeowner Approved Label on: pull_request_target: types: [opened, synchronize, reopened, ready_for_review] + branches-ignore: + - release + - beta permissions: issues: write diff --git a/.github/workflows/codeowner-review-request.yml b/.github/workflows/codeowner-review-request.yml index 02bf0e4a29..a89c03ba04 100644 --- a/.github/workflows/codeowner-review-request.yml +++ b/.github/workflows/codeowner-review-request.yml @@ -13,6 +13,9 @@ on: # Needs to be pull_request_target to get write permissions pull_request_target: types: [opened, reopened, synchronize, ready_for_review] + branches-ignore: + - release + - beta permissions: pull-requests: write diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 1a0c54da6d..67f4690ac9 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -58,7 +58,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 + uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -86,6 +86,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 + uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/pr-title-check.yml b/.github/workflows/pr-title-check.yml index 198b9a6b25..0021654def 100644 --- a/.github/workflows/pr-title-check.yml +++ b/.github/workflows/pr-title-check.yml @@ -3,6 +3,9 @@ name: PR Title Check on: pull_request: types: [opened, edited, synchronize, reopened] + branches-ignore: + - release + - beta permissions: contents: read @@ -65,6 +68,18 @@ jobs: return; } + // Check for angle brackets not wrapped in backticks. + // Astro docs MDX treats bare < as JSX component opening tags. + const stripped = title.replace(/`[^`]*`/g, ''); + if (/[<>]/.test(stripped)) { + core.setFailed( + 'PR title contains `<` or `>` not wrapped in backticks.\n' + + 'Astro docs MDX interprets bare `<` as JSX components.\n' + + 'Please wrap angle brackets with backticks, e.g.: [component] Add `` support' + ); + return; + } + // Check title starts with [tag] prefix const bracketPattern = /^\[\w+\]/; if (!bracketPattern.test(title)) { diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8f68e9c873..9e8a040888 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -70,7 +70,7 @@ jobs: pip3 install build python3 -m build - name: Publish - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 with: skip-existing: true @@ -102,12 +102,12 @@ jobs: uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Log in to docker hub - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: username: ${{ secrets.DOCKER_USER }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Log in to the GitHub container registry - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ghcr.io username: ${{ github.actor }} @@ -171,7 +171,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Download digests - uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: digests-* path: /tmp/digests @@ -182,13 +182,13 @@ jobs: - name: Log in to docker hub if: matrix.registry == 'dockerhub' - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.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@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ghcr.io username: ${{ github.actor }} @@ -221,7 +221,7 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }} private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }} @@ -256,7 +256,7 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }} private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }} @@ -287,7 +287,7 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }} private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }} diff --git a/.github/workflows/sync-device-classes.yml b/.github/workflows/sync-device-classes.yml index b0d966555b..a71e5ef4ca 100644 --- a/.github/workflows/sync-device-classes.yml +++ b/.github/workflows/sync-device-classes.yml @@ -24,7 +24,7 @@ jobs: - name: Setup Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: - python-version: 3.13 + python-version: "3.14" - name: Install Home Assistant run: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2d8f698395..f8c21aad36 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ ci: repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.15.5 + rev: v0.15.9 hooks: # Run the linter. - id: ruff @@ -65,3 +65,7 @@ repos: files: ^(\.clang-tidy|platformio\.ini|requirements_dev\.txt)$ pass_filenames: false additional_dependencies: [] + - id: ci-custom + name: ci-custom + entry: python3 script/run-in-env.py script/ci-custom.py + language: system diff --git a/CODEOWNERS b/CODEOWNERS index e72b164761..5b1ae65f1b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -92,6 +92,7 @@ esphome/components/bmp3xx_i2c/* @latonita esphome/components/bmp3xx_spi/* @latonita esphome/components/bmp581_base/* @danielkent-net @kahrendt esphome/components/bmp581_i2c/* @danielkent-net @kahrendt +esphome/components/bmp581_spi/* @danielkent-net @kahrendt esphome/components/bp1658cj/* @Cossid esphome/components/bp5758d/* @Cossid esphome/components/bthome_mithermometer/* @nagyrobi @@ -141,12 +142,13 @@ esphome/components/dlms_meter/* @SimonFischer04 esphome/components/dps310/* @kbx81 esphome/components/ds1307/* @badbadc0ffee esphome/components/ds2484/* @mrk-its -esphome/components/dsmr/* @glmnet @PolarGoose @zuidwijk +esphome/components/dsmr/* @glmnet @PolarGoose esphome/components/duty_time/* @dudanov esphome/components/ee895/* @Stock-M esphome/components/ektf2232/touchscreen/* @jesserockz esphome/components/emc2101/* @ellull esphome/components/emmeti/* @E440QF +esphome/components/emontx/* @FredM67 @glynhudson @TrystanLea esphome/components/ens160/* @latonita esphome/components/ens160_base/* @latonita @vincentscode esphome/components/ens160_i2c/* @latonita @@ -216,6 +218,7 @@ esphome/components/hbridge/light/* @DotNetDann esphome/components/hbridge/switch/* @dwmw2 esphome/components/hc8/* @omartijn esphome/components/hdc2010/* @optimusprimespace @ssieb +esphome/components/hdc2080/* @G-Pereira @jesserockz esphome/components/hdc302x/* @joshuasing esphome/components/he60r/* @clydebarrow esphome/components/heatpumpir/* @rob-deutsch @@ -244,7 +247,6 @@ esphome/components/hyt271/* @Philippe12 esphome/components/i2c/* @esphome/core esphome/components/i2c_device/* @gabest11 esphome/components/i2s_audio/* @jesserockz -esphome/components/i2s_audio/media_player/* @jesserockz esphome/components/i2s_audio/microphone/* @jesserockz esphome/components/i2s_audio/speaker/* @jesserockz @kahrendt esphome/components/iaqcore/* @yozik04 @@ -330,6 +332,7 @@ esphome/components/mipi_dsi/* @clydebarrow esphome/components/mipi_rgb/* @clydebarrow esphome/components/mipi_spi/* @clydebarrow esphome/components/mitsubishi/* @RubyBailey +esphome/components/mitsubishi_cn105/* @crnjan esphome/components/mixer/speaker/* @kahrendt esphome/components/mlx90393/* @functionpointer esphome/components/mlx90614/* @jesserockz @@ -458,6 +461,9 @@ esphome/components/sn74hc165/* @jesserockz esphome/components/socket/* @esphome/core esphome/components/sonoff_d1/* @anatoly-savchenkov esphome/components/sound_level/* @kahrendt +esphome/components/spa06_base/* @danielkent-net +esphome/components/spa06_i2c/* @danielkent-net +esphome/components/spa06_spi/* @danielkent-net esphome/components/speaker/* @jesserockz @kahrendt esphome/components/speaker/media_player/* @kahrendt @synesthesiam esphome/components/speaker_source/* @kahrendt diff --git a/Doxyfile b/Doxyfile index 7d0fcb9a27..e60c994ea9 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2026.3.3 +PROJECT_NUMBER = 2026.4.0b1 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/README.md b/README.md index b8ce8d091d..16497ee0be 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ESPHome [![Discord Chat](https://img.shields.io/discord/429907082951524364.svg)](https://discord.gg/KhAMKrd) [![GitHub release](https://img.shields.io/github/release/esphome/esphome.svg)](https://GitHub.com/esphome/esphome/releases/) +# ESPHome [![Discord Chat](https://img.shields.io/discord/429907082951524364.svg)](https://discord.gg/KhAMKrd) [![GitHub release](https://img.shields.io/github/release/esphome/esphome.svg)](https://GitHub.com/esphome/esphome/releases/) [![CodSpeed](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/esphome/esphome) diff --git a/esphome/__main__.py b/esphome/__main__.py index 4b0fc2cec7..25b404ae45 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -1046,7 +1046,11 @@ def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int ): from esphome.components.api.client import run_logs - return run_logs(config, network_devices) + return run_logs( + config, + network_devices, + subscribe_states=not getattr(args, "no_states", False), + ) if port_type in (PortType.NETWORK, PortType.MQTT) and has_mqtt_logging(): from esphome import mqtt @@ -1079,7 +1083,7 @@ def command_config(args: ArgsProtocol, config: ConfigType) -> int | None: # add the console decoration so the front-end can hide the secrets if not args.show_secrets: output = re.sub( - r"(password|key|psk|ssid)\: (.+)", r"\1: \\033[5m\2\\033[6m", output + r"(password|key|psk|ssid)\: (.+)", r"\1: \\033[8m\2\\033[28m", output ) if not CORE.quiet: safe_print(output) @@ -1238,6 +1242,38 @@ def command_clean(args: ArgsProtocol, config: ConfigType) -> int | None: return 0 +def command_bundle(args: ArgsProtocol, config: ConfigType) -> int | None: + from esphome.bundle import BUNDLE_EXTENSION, ConfigBundleCreator + + creator = ConfigBundleCreator(config) + + if args.list_only: + files = creator.discover_files() + for bf in sorted(files, key=lambda f: f.path): + safe_print(f" {bf.path}") + _LOGGER.info("Found %d files", len(files)) + return 0 + + result = creator.create_bundle() + + if args.output: + output_path = Path(args.output) + else: + stem = CORE.config_path.stem + output_path = CORE.config_dir / f"{stem}{BUNDLE_EXTENSION}" + + output_path.parent.mkdir(parents=True, exist_ok=True) + output_path.write_bytes(result.data) + + _LOGGER.info( + "Bundle created: %s (%d files, %.1f KB)", + output_path, + len(result.files), + len(result.data) / 1024, + ) + return 0 + + def command_dashboard(args: ArgsProtocol) -> int | None: from esphome.dashboard import dashboard @@ -1513,6 +1549,7 @@ POST_CONFIG_ACTIONS = { "rename": command_rename, "discover": command_discover, "analyze-memory": command_analyze_memory, + "bundle": command_bundle, } SIMPLE_CONFIG_ACTIONS = [ @@ -1664,6 +1701,11 @@ def parse_args(argv): help="Reset the device before starting serial logs.", default=os.getenv("ESPHOME_SERIAL_LOGGING_RESET"), ) + parser_logs.add_argument( + "--no-states", + action="store_true", + help="Do not show entity state changes in log output.", + ) parser_discover = subparsers.add_parser( "discover", @@ -1809,6 +1851,24 @@ def parse_args(argv): "configuration", help="Your YAML configuration file(s).", nargs="+" ) + parser_bundle = subparsers.add_parser( + "bundle", + help="Create a self-contained config bundle for remote compilation.", + ) + parser_bundle.add_argument( + "configuration", help="Your YAML configuration file(s).", nargs="+" + ) + parser_bundle.add_argument( + "-o", + "--output", + help="Output path for the bundle archive.", + ) + parser_bundle.add_argument( + "--list-only", + help="List discovered files without creating the archive.", + action="store_true", + ) + # Keep backward compatibility with the old command line format of # esphome . # @@ -1887,6 +1947,16 @@ def run_esphome(argv): _LOGGER.warning("Skipping secrets file %s", conf_path) return 0 + # Bundle support: if the configuration is a .esphomebundle, extract it + # and rewrite conf_path to the extracted YAML config. + from esphome.bundle import is_bundle_path, prepare_bundle_for_compile + + if is_bundle_path(conf_path): + _LOGGER.info("Extracting config bundle %s...", conf_path) + conf_path = prepare_bundle_for_compile(conf_path) + # Update the argument so downstream code sees the extracted path + args.configuration[0] = str(conf_path) + CORE.config_path = conf_path CORE.dashboard = args.dashboard diff --git a/esphome/analyze_memory/__init__.py b/esphome/analyze_memory/__init__.py index bf1bcbfa05..f56d720ec2 100644 --- a/esphome/analyze_memory/__init__.py +++ b/esphome/analyze_memory/__init__.py @@ -1,6 +1,6 @@ """Memory usage analyzer for ESPHome compiled binaries.""" -from collections import defaultdict +from collections import Counter, defaultdict from dataclasses import dataclass, field import logging from pathlib import Path @@ -40,6 +40,15 @@ _READELF_SECTION_PATTERN = re.compile( r"\s*\[\s*\d+\]\s+([\.\w]+)\s+\w+\s+[\da-fA-F]+\s+[\da-fA-F]+\s+([\da-fA-F]+)" ) +# Regex for extracting call targets from objdump disassembly +# Matches direct call instructions across architectures: +# Xtensa: call0/call4/call8/call12/callx0/callx4/callx8/callx12 +# ARM: bl/blx +# Captures the mangled symbol name inside angle brackets. +_CALL_TARGET_PATTERN = re.compile( + r"\t(?:call(?:0|4|8|12)|callx(?:0|4|8|12)|blx?)\s+[\da-fA-F]+ <([^>]+)>" +) + # Component category prefixes _COMPONENT_PREFIX_ESPHOME = "[esphome]" _COMPONENT_PREFIX_EXTERNAL = "[external]" @@ -47,6 +56,10 @@ _COMPONENT_PREFIX_LIB = "[lib]" _COMPONENT_CORE = f"{_COMPONENT_PREFIX_ESPHOME}core" _COMPONENT_API = f"{_COMPONENT_PREFIX_ESPHOME}api" +# Placement new storage suffix (generated by codegen Pvariable) +_PSTORAGE_SUFFIX = "__pstorage" + + # C++ namespace prefixes _NAMESPACE_ESPHOME = "esphome::" _NAMESPACE_STD = "std::" @@ -192,20 +205,27 @@ class MemoryAnalyzer: self._cswtch_symbols: list[tuple[str, int, str, str]] = [] # Library symbol mapping: symbol_name -> library_name self._lib_symbol_map: dict[str, str] = {} + # Source file symbol mapping: symbol_name -> component_name + # Used for extern "C" and other symbols without C++ namespace + self._source_symbol_map: dict[str, str] = {} # Library dir to name mapping: "lib641" -> "espsoftwareserial", # "espressif__mdns" -> "mdns" self._lib_hash_to_name: dict[str, str] = {} # Heuristic category to library redirect: "mdns_lib" -> "[lib]mdns" self._heuristic_to_lib: dict[str, str] = {} + # Function call counts: mangled_name -> call_count + self._function_call_counts: Counter[str] = Counter() def analyze(self) -> dict[str, ComponentMemory]: """Analyze the ELF file and return component memory usage.""" self._parse_sections() self._parse_symbols() self._scan_libraries() + self._scan_source_symbols() self._categorize_symbols() self._analyze_cswtch_symbols() self._analyze_sdk_libraries() + self._analyze_function_calls() return dict(self.components) def _parse_sections(self) -> None: @@ -316,6 +336,13 @@ class MemoryAnalyzer: # Demangle C++ names if needed demangled = self._demangle_symbol(symbol_name) + # Check for placement new storage symbols (generated by codegen) + # Format: {component}__{id}__pstorage + if demangled.endswith(_PSTORAGE_SUFFIX) and ( + component := self._match_pstorage_component(demangled) + ): + return component + # Check for special component classes first (before namespace pattern) # This handles cases like esphome::ESPHomeOTAComponent which should map to ota if _NAMESPACE_ESPHOME in demangled: @@ -351,6 +378,11 @@ class MemoryAnalyzer: if lib_name := self._lib_symbol_map.get(symbol_name): return f"{_COMPONENT_PREFIX_LIB}{lib_name}" + # Check source file mapping (catches extern "C" functions in ESPHome sources) + # Must be before heuristic patterns since source attribution is authoritative + if component := self._source_symbol_map.get(symbol_name): + return component + # Check against symbol patterns for component, patterns in SYMBOL_PATTERNS.items(): if any(pattern in symbol_name for pattern in patterns): @@ -378,14 +410,33 @@ class MemoryAnalyzer: # Track uncategorized symbols for analysis return "other" + def _match_pstorage_component(self, symbol_name: str) -> str | None: + """Match a __pstorage symbol to its ESPHome component. + + Symbol format: {component}__{id}__pstorage + The component namespace is embedded by codegen before the double underscore. + """ + prefix = symbol_name[: -len(_PSTORAGE_SUFFIX)] + # Extract component namespace before the first double underscore + dunder_pos = prefix.find("__") + if dunder_pos == -1: + return None + component_name = prefix[:dunder_pos] + if component_name in get_esphome_components(): + return f"{_COMPONENT_PREFIX_ESPHOME}{component_name}" + if component_name in self.external_components: + return f"{_COMPONENT_PREFIX_EXTERNAL}{component_name}" + return None + def _batch_demangle_symbols(self, symbols: list[str]) -> None: """Batch demangle C++ symbol names for efficiency.""" if not symbols: return _LOGGER.info("Demangling %d symbols", len(symbols)) - self._demangle_cache = batch_demangle(symbols, objdump_path=self.objdump_path) - _LOGGER.info("Successfully demangled %d symbols", len(self._demangle_cache)) + demangled = batch_demangle(symbols, objdump_path=self.objdump_path) + self._demangle_cache.update(demangled) + _LOGGER.info("Successfully demangled %d symbols", len(demangled)) def _demangle_symbol(self, symbol: str) -> str: """Get demangled C++ symbol name from cache.""" @@ -640,6 +691,7 @@ class MemoryAnalyzer: return None symbol_map: dict[str, str] = {} + source_symbol_map: dict[str, str] = {} current_symbol: str | None = None section_prefixes = (".text.", ".rodata.", ".data.", ".bss.", ".literal.") @@ -675,9 +727,18 @@ class MemoryAnalyzer: if dir_key in source_path: symbol_map[current_symbol] = lib_name break + else: + # Map ESPHome source files to components for extern "C" + # and other symbols without C++ namespace + component = self._source_file_to_component(source_path) + if component.startswith( + (_COMPONENT_PREFIX_ESPHOME, _COMPONENT_PREFIX_EXTERNAL) + ): + source_symbol_map[current_symbol] = component current_symbol = None + self._source_symbol_map = source_symbol_map return symbol_map or None def _scan_libraries(self) -> None: @@ -728,6 +789,112 @@ class MemoryAnalyzer: len(libraries), ) + def _scan_source_symbols(self) -> None: + """Scan ESPHome source object files to map extern "C" symbols to components. + + When no linker map file is available, this uses ``nm`` to scan ``.o`` files + under ``src/esphome/`` and build a symbol-to-component mapping. This catches + ``extern "C"`` functions and other symbols that lack C++ namespace prefixes. + + Skips scanning if ``_source_symbol_map`` was already populated by + ``_parse_map_file()``. + """ + if self._source_symbol_map or not self.nm_path: + return + + obj_dir = self._find_object_files_dir() + if obj_dir is None: + return + + # Find ESPHome source object files + esphome_src_dir = obj_dir / "src" / "esphome" + if not esphome_src_dir.is_dir(): + return + + obj_files = sorted(esphome_src_dir.rglob("*.o")) + if not obj_files: + return + + # Run nm with --print-file-name to get file:symbol mapping + result = run_tool( + [self.nm_path, "--print-file-name", "-g", "--defined-only"] + + [str(f) for f in obj_files], + ) + if result is None or result.returncode != 0: + _LOGGER.debug("nm scan of source objects failed") + return + + self._source_symbol_map = self._parse_nm_source_output(result.stdout, obj_dir) + if self._source_symbol_map: + _LOGGER.info( + "Built source symbol map from nm: %d symbols", + len(self._source_symbol_map), + ) + + def _parse_nm_source_output(self, output: str, base_dir: Path) -> dict[str, str]: + """Parse nm output to map non-namespaced symbols to ESPHome components. + + Extracts global defined symbols from ESPHome source object files that + don't use C++ namespacing (e.g. ``extern "C"`` functions). + + Args: + output: Raw stdout from ``nm --print-file-name -g --defined-only`` + or ``nm --print-file-name -S``. + base_dir: Build directory for computing relative paths. + + Returns: + Dict mapping symbol names to component names. + """ + source_map: dict[str, str] = {} + for line in output.splitlines(): + # Format: /path/to/file.o: addr type name + # or: /path/to/file.o: addr size type name (with -S) + colon_idx = line.rfind(".o:") + if colon_idx == -1: + continue + + file_path = line[: colon_idx + 2] + fields = line[colon_idx + 3 :].split() + if len(fields) < 3: + continue + + # With -S flag, format is: addr size type name + # Without -S flag: addr type name + # type is a single char; size is hex digits + # Detect by checking if fields[1] is a single uppercase letter (type) + if len(fields[1]) == 1 and fields[1].isalpha(): + # addr type name + sym_type = fields[1] + symbol_name = fields[2] + elif len(fields) >= 4: + # addr size type name + sym_type = fields[2] + symbol_name = fields[3] + else: + continue + + # Only global defined symbols (uppercase type) + if not sym_type.isupper() or sym_type == "U": + continue + + # Skip symbols already in esphome:: namespace + if symbol_name.startswith("_ZN7esphome"): + continue + + # Make path relative to base_dir for _source_file_to_component + try: + rel_path = str(Path(file_path).relative_to(base_dir)) + except ValueError: + continue + + component = self._source_file_to_component(rel_path) + if component.startswith( + (_COMPONENT_PREFIX_ESPHOME, _COMPONENT_PREFIX_EXTERNAL) + ): + source_map[symbol_name] = component + + return source_map + def _find_object_files_dir(self) -> Path | None: """Find the directory containing object files for this build. @@ -1011,6 +1178,43 @@ class MemoryAnalyzer: total_size, ) + def _analyze_function_calls(self) -> None: + """Count function call sites by parsing disassembly output. + + Parses direct call instructions (call0/call8/bl/blx) from objdump -d + to count how many times each function is called. This helps identify + inlining candidates — frequently called small functions benefit most + from inlining. + """ + result = run_tool( + [self.objdump_path, "-d", str(self.elf_path)], + timeout=60, + ) + if result is None or result.returncode != 0: + _LOGGER.debug("Failed to disassemble ELF for function call analysis") + return + + self._function_call_counts = Counter( + match.group(1) + for line in result.stdout.splitlines() + if (match := _CALL_TARGET_PATTERN.search(line)) + ) + + # Demangle any call targets not already in the cache + missing = [ + name + for name in self._function_call_counts + if name not in self._demangle_cache + ] + if missing: + self._batch_demangle_symbols(missing) + + _LOGGER.debug( + "Function call analysis: %d unique targets, %d total calls", + len(self._function_call_counts), + sum(self._function_call_counts.values()), + ) + def get_unattributed_ram(self) -> tuple[int, int, int]: """Get unattributed RAM sizes (SDK/framework overhead). diff --git a/esphome/analyze_memory/cli.py b/esphome/analyze_memory/cli.py index dbc19c6b89..b7561e8ffc 100644 --- a/esphome/analyze_memory/cli.py +++ b/esphome/analyze_memory/cli.py @@ -15,6 +15,7 @@ from . import ( _COMPONENT_PREFIX_ESPHOME, _COMPONENT_PREFIX_EXTERNAL, _COMPONENT_PREFIX_LIB, + _PSTORAGE_SUFFIX, RAM_SECTIONS, MemoryAnalyzer, ) @@ -23,6 +24,17 @@ if TYPE_CHECKING: from . import ComponentMemory +def _format_pstorage_name(name: str) -> str: + """Format a __pstorage symbol as 'storage for {id}'.""" + if not name.endswith(_PSTORAGE_SUFFIX): + return name + prefix = name[: -len(_PSTORAGE_SUFFIX)] + # Strip component namespace prefix: {component}__{id} -> {id} + dunder_pos = prefix.find("__") + var_id = prefix[dunder_pos + 2 :] if dunder_pos != -1 else prefix + return f"storage for {var_id}" + + class MemoryAnalyzerCLI(MemoryAnalyzer): """Memory analyzer with CLI-specific report generation.""" @@ -148,11 +160,14 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): If section is one of the RAM sections (.data or .bss), a label like " [data]" or " [bss]" is appended. For non-RAM sections or when section is None, no section label is added. + + Placement new storage symbols are formatted as "storage for {id}". """ + display_name = _format_pstorage_name(demangled) section_label = "" if section in RAM_SECTIONS: section_label = f" [{section[1:]}]" # .data -> [data], .bss -> [bss] - return f"{demangled} ({size:,} B){section_label}" + return f"{display_name} ({size:,} B){section_label}" def _add_top_symbols(self, lines: list[str]) -> None: """Add a section showing the top largest symbols in the binary.""" @@ -175,11 +190,13 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): for i, (_, demangled, size, section, component) in enumerate(top_symbols): # Format section label section_label = f"[{section[1:]}]" if section else "" - # Truncate demangled name if too long + # Format storage symbols readably + display_name = _format_pstorage_name(demangled) + # Truncate if too long demangled_display = ( - f"{demangled[:truncate_limit]}..." - if len(demangled) > self.COL_TOP_SYMBOL_NAME - else demangled + f"{display_name[:truncate_limit]}..." + if len(display_name) > self.COL_TOP_SYMBOL_NAME + else display_name ) lines.append( f"{i + 1:>2}. {size:>7,} B {section_label:<8} {demangled_display:<{self.COL_TOP_SYMBOL_NAME}} {component}" @@ -231,6 +248,110 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): lines.append(f" {size:>6,} B {sym_name}") lines.append("") + # Number of top called functions to show + TOP_CALLS_LIMIT: int = 50 + # Number of inlining candidates to show + INLINE_CANDIDATES_LIMIT: int = 25 + # Maximum function size in bytes to consider for inlining + INLINE_SIZE_THRESHOLD: int = 16 + + def _build_symbol_sizes(self) -> dict[str, int]: + """Build a size lookup from all component symbols: mangled_name -> size.""" + return { + symbol: size + for symbols in self._component_symbols.values() + for symbol, _, size, _ in symbols + } + + def _format_call_row( + self, index: int, mangled: str, count: int, symbol_sizes: dict[str, int] + ) -> str: + """Format a single row for call frequency tables.""" + demangled = self._demangle_cache.get(mangled, mangled) + if len(demangled) > 80: + demangled = f"{demangled[:77]}..." + size = symbol_sizes.get(mangled) + size_str = f"{size:>5,} B" if size is not None else " ?" + return f"{index:>3} {count:>5} {size_str} {demangled}" + + def _add_call_table_header(self, lines: list[str]) -> None: + """Add the header row for call frequency tables.""" + lines.append(f"{'#':>3} {'Calls':>5} {'Size':>7} Function") + lines.append(f"{'---':>3} {'-----':>5} {'-------':>7} {'-' * 60}") + + def _add_function_call_analysis(self, lines: list[str]) -> None: + """Add function call frequency analysis section. + + Shows the most frequently called functions by call site count. + """ + self._add_section_header(lines, "Top Called Functions") + + symbol_sizes = self._build_symbol_sizes() + + # Sort by call count descending + sorted_calls = sorted( + self._function_call_counts.items(), key=lambda x: x[1], reverse=True + ) + + self._add_call_table_header(lines) + + for i, (mangled, count) in enumerate(sorted_calls[: self.TOP_CALLS_LIMIT]): + lines.append(self._format_call_row(i + 1, mangled, count, symbol_sizes)) + + total_calls = sum(self._function_call_counts.values()) + lines.append("") + lines.append( + f"Total: {len(self._function_call_counts)} unique targets, " + f"{total_calls:,} call sites" + ) + lines.append("") + + def _add_inline_candidates(self, lines: list[str]) -> None: + """Add inlining candidates section. + + Shows frequently called functions that are small enough to benefit + from inlining (< 16 bytes). These are the best candidates for + reducing call overhead. + """ + self._add_section_header( + lines, + f"Inlining Candidates (<{self.INLINE_SIZE_THRESHOLD} B, by call count)", + ) + + symbol_sizes = self._build_symbol_sizes() + + # Filter to small functions with known size, sort by call count + candidates = sorted( + ( + (mangled, count) + for mangled, count in self._function_call_counts.items() + if mangled in symbol_sizes + and symbol_sizes[mangled] < self.INLINE_SIZE_THRESHOLD + ), + key=lambda x: x[1], + reverse=True, + ) + + if not candidates: + lines.append("No candidates found.") + lines.append("") + return + + self._add_call_table_header(lines) + + for i, (mangled, count) in enumerate( + candidates[: self.INLINE_CANDIDATES_LIMIT] + ): + lines.append(self._format_call_row(i + 1, mangled, count, symbol_sizes)) + + lines.append("") + lines.append( + f"Showing top {min(len(candidates), self.INLINE_CANDIDATES_LIMIT)} " + f"of {len(candidates)} functions under " + f"{self.INLINE_SIZE_THRESHOLD} B" + ) + lines.append("") + def generate_report(self, detailed: bool = False) -> str: """Generate a formatted memory report.""" components = sorted( @@ -469,15 +590,16 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): lines.append(f"Total size: {comp_mem.flash_total:,} B") lines.append("") - # Show all symbols above threshold for better visibility + # Show symbols above threshold, always include storage symbols large_symbols = [ (sym, dem, size, sec) for sym, dem, size, sec in sorted_symbols if size > self.SYMBOL_SIZE_THRESHOLD + or dem.endswith(_PSTORAGE_SUFFIX) ] lines.append( - f"{comp_name} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_symbols)} symbols):" + f"{comp_name} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B & storage ({len(large_symbols)} symbols):" ) for i, (symbol, demangled, size, section) in enumerate(large_symbols): lines.append( @@ -500,7 +622,10 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): # Sort by size descending sorted_ram_syms = sorted(ram_syms, key=lambda x: x[2], reverse=True) large_ram_syms = [ - s for s in sorted_ram_syms if s[2] > self.RAM_SYMBOL_SIZE_THRESHOLD + s + for s in sorted_ram_syms + if s[2] > self.RAM_SYMBOL_SIZE_THRESHOLD + or s[1].endswith(_PSTORAGE_SUFFIX) ] lines.append(f"{name} ({mem.ram_total:,} B total RAM):") @@ -518,13 +643,14 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): for symbol, demangled, size, section in large_ram_syms[:10]: # Format section label consistently by stripping leading dot section_label = section.lstrip(".") if section else "" + display_name = _format_pstorage_name(demangled) # Add ellipsis if name is truncated - demangled_display = ( - f"{demangled[:70]}..." if len(demangled) > 70 else demangled - ) - lines.append( - f" {size:>6,} B [{section_label}] {demangled_display}" + display_name = ( + f"{display_name[:70]}..." + if len(display_name) > 70 + else display_name ) + lines.append(f" {size:>6,} B [{section_label}] {display_name}") if len(large_ram_syms) > 10: lines.append(f" ... and {len(large_ram_syms) - 10} more") lines.append("") @@ -533,6 +659,11 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): if self._cswtch_symbols: self._add_cswtch_analysis(lines) + # Function call frequency analysis + if self._function_call_counts: + self._add_function_call_analysis(lines) + self._add_inline_candidates(lines) + lines.append( "Note: This analysis covers symbols in the ELF file. Some runtime allocations may not be included." ) diff --git a/esphome/analyze_memory/const.py b/esphome/analyze_memory/const.py index 3bdf555ae3..0c871d0727 100644 --- a/esphome/analyze_memory/const.py +++ b/esphome/analyze_memory/const.py @@ -408,7 +408,6 @@ SYMBOL_PATTERNS = { ], "arduino_core": [ "pinMode", - "resetPins", "millis", "micros", "delay(", # More specific - Arduino delay function with parenthesis diff --git a/esphome/automation.py b/esphome/automation.py index 36ab30b654..b4dcc41995 100644 --- a/esphome/automation.py +++ b/esphome/automation.py @@ -1,3 +1,4 @@ +from dataclasses import dataclass, field import logging import esphome.codegen as cg @@ -137,6 +138,9 @@ UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action) SuspendComponentAction = cg.esphome_ns.class_("SuspendComponentAction", Action) ResumeComponentAction = cg.esphome_ns.class_("ResumeComponentAction", Action) Automation = cg.esphome_ns.class_("Automation") +TriggerForwarder = cg.esphome_ns.class_("TriggerForwarder") +TriggerOnTrueForwarder = cg.esphome_ns.class_("TriggerOnTrueForwarder") +TriggerOnFalseForwarder = cg.esphome_ns.class_("TriggerOnFalseForwarder") LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition) StatelessLambdaCondition = cg.esphome_ns.class_("StatelessLambdaCondition", Condition) @@ -247,7 +251,9 @@ async def and_condition_to_code( args: TemplateArgsType, ) -> MockObj: conditions = await build_condition_list(config, template_arg, args) - return cg.new_Pvariable(condition_id, template_arg, conditions) + return cg.new_Pvariable( + condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions + ) @register_condition("or", OrCondition, validate_condition_list) @@ -258,7 +264,9 @@ async def or_condition_to_code( args: TemplateArgsType, ) -> MockObj: conditions = await build_condition_list(config, template_arg, args) - return cg.new_Pvariable(condition_id, template_arg, conditions) + return cg.new_Pvariable( + condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions + ) @register_condition("all", AndCondition, validate_condition_list) @@ -269,7 +277,9 @@ async def all_condition_to_code( args: TemplateArgsType, ) -> MockObj: conditions = await build_condition_list(config, template_arg, args) - return cg.new_Pvariable(condition_id, template_arg, conditions) + return cg.new_Pvariable( + condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions + ) @register_condition("any", OrCondition, validate_condition_list) @@ -280,7 +290,9 @@ async def any_condition_to_code( args: TemplateArgsType, ) -> MockObj: conditions = await build_condition_list(config, template_arg, args) - return cg.new_Pvariable(condition_id, template_arg, conditions) + return cg.new_Pvariable( + condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions + ) @register_condition("not", NotCondition, validate_potentially_and_condition) @@ -302,7 +314,9 @@ async def xor_condition_to_code( args: TemplateArgsType, ) -> MockObj: conditions = await build_condition_list(config, template_arg, args) - return cg.new_Pvariable(condition_id, template_arg, conditions) + return cg.new_Pvariable( + condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions + ) @register_condition("lambda", LambdaCondition, cv.returning_lambda) @@ -413,13 +427,16 @@ async def if_action_to_code( template_arg: cg.TemplateArguments, args: TemplateArgsType, ) -> MockObj: + has_else = CONF_ELSE in config + # Prepend HasElse bool to template arguments: IfAction + if_template_arg = cg.TemplateArguments(has_else, *template_arg) cond_conf = next(el for el in config if el in (CONF_ANY, CONF_ALL, CONF_CONDITION)) condition = await build_condition(config[cond_conf], template_arg, args) - var = cg.new_Pvariable(action_id, template_arg, condition) + var = cg.new_Pvariable(action_id, if_template_arg, condition) if CONF_THEN in config: actions = await build_action_list(config[CONF_THEN], template_arg, args) cg.add(var.add_then(actions)) - if CONF_ELSE in config: + if has_else: actions = await build_action_list(config[CONF_ELSE], template_arg, args) cg.add(var.add_else(actions)) return var @@ -658,3 +675,76 @@ async def build_automation( actions = await build_action_list(config[CONF_THEN], templ, args) cg.add(obj.add_actions(actions)) return obj + + +async def build_callback_automation( + parent: MockObj, + callback_method: str, + args: TemplateArgsType, + config: ConfigType, + forwarder: MockObj | MockObjClass | None = None, +) -> None: + """Build an Automation and register it as a callback on the parent. + + Eliminates the need for a Trigger wrapper object by registering the + automation's trigger() directly as a callback on the parent component. + + Uses template forwarder structs so the compiler deduplicates the operator() + body across all call sites with the same signature. The forwarder must be + pointer-sized (single Automation* field) to fit inline in Callback::ctx_ + and avoid heap allocation. + + :param parent: The component object (e.g., button, sensor). + :param callback_method: Name of the callback method (e.g., "add_on_press_callback"). + :param args: Automation template args as list of (type, name) tuples. + :param config: The automation config dict. + :param forwarder: Optional forwarder type to use instead of the default + TriggerForwarder. Pass any struct type whose aggregate init takes + a single Automation pointer (e.g., TriggerOnTrueForwarder). + """ + arg_types = [arg[0] for arg in args] + templ = cg.TemplateArguments(*arg_types) + obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ) + actions = await build_action_list(config[CONF_THEN], templ, args) + cg.add(obj.add_actions(actions)) + # Use template forwarder structs for deduplication. The compiler generates + # one operator() per forwarder type; different automation pointers are just + # data in the struct. + if forwarder is None: + forwarder = TriggerForwarder.template(templ) + # RawExpression for aggregate init — both forwarder and obj are codegen + # MockObjs (not user input), and there's no Expression type for positional + # aggregate initialization (StructInitializer uses named fields). + cg.add(getattr(parent, callback_method)(cg.RawExpression(f"{forwarder}{{{obj}}}"))) + + +@dataclass(frozen=True, slots=True) +class CallbackAutomation: + """A single callback automation entry for build_callback_automations.""" + + conf_key: str + callback_method: str + args: TemplateArgsType = field(default_factory=list) + forwarder: MockObj | MockObjClass | None = None + + +async def build_callback_automations( + parent: MockObj, + config: ConfigType, + entries: tuple[CallbackAutomation, ...], +) -> None: + """Build multiple callback automations from a tuple of entries. + + :param parent: The component object (e.g., button, sensor). + :param config: The full component config dict. + :param entries: Tuple of CallbackAutomation entries to process. + """ + for entry in entries: + for conf in config.get(entry.conf_key, []): + await build_callback_automation( + parent, + entry.callback_method, + entry.args, + conf, + forwarder=entry.forwarder, + ) diff --git a/esphome/build_gen/espidf.py b/esphome/build_gen/espidf.py index 9df9b1069c..01923baaac 100644 --- a/esphome/build_gen/espidf.py +++ b/esphome/build_gen/espidf.py @@ -53,6 +53,13 @@ def get_project_cmakelists() -> str: variant = get_esp32_variant() idf_target = variant.lower().replace("-", "") + # Extract compile definitions from build flags (-DXXX -> XXX) + compile_defs = [flag for flag in CORE.build_flags if flag.startswith("-D")] + extra_compile_options = "\n".join( + f'idf_build_set_property(COMPILE_OPTIONS "{compile_def}" APPEND)' + for compile_def in compile_defs + ) + return f"""\ # Auto-generated by ESPHome cmake_minimum_required(VERSION 3.16) @@ -61,6 +68,9 @@ set(IDF_TARGET {idf_target}) set(EXTRA_COMPONENT_DIRS ${{CMAKE_SOURCE_DIR}}/src) include($ENV{{IDF_PATH}}/tools/cmake/project.cmake) + +{extra_compile_options} + project({CORE.name}) """ @@ -70,10 +80,6 @@ def get_component_cmakelists(minimal: bool = False) -> str: idf_requires = [] if minimal else (get_available_components() or []) requires_str = " ".join(idf_requires) - # Extract compile definitions from build flags (-DXXX -> XXX) - compile_defs = [flag[2:] for flag in CORE.build_flags if flag.startswith("-D")] - compile_defs_str = "\n ".join(sorted(compile_defs)) if compile_defs else "" - # Extract compile options (-W flags, excluding linker flags) compile_opts = [ flag @@ -104,11 +110,6 @@ idf_component_register( # Apply C++ standard target_compile_features(${{COMPONENT_LIB}} PUBLIC cxx_std_20) -# ESPHome compile definitions -target_compile_definitions(${{COMPONENT_LIB}} PUBLIC - {compile_defs_str} -) - # ESPHome compile options target_compile_options(${{COMPONENT_LIB}} PUBLIC {compile_opts_str} diff --git a/esphome/bundle.py b/esphome/bundle.py new file mode 100644 index 0000000000..b6816c7c95 --- /dev/null +++ b/esphome/bundle.py @@ -0,0 +1,699 @@ +"""Config bundle creator and extractor for ESPHome. + +A bundle is a self-contained .tar.gz archive containing a YAML config +and every local file it depends on. Bundles can be created from a config +and compiled directly: ``esphome compile my_device.esphomebundle.tar.gz`` +""" + +from __future__ import annotations + +from dataclasses import dataclass +from enum import StrEnum +import io +import json +import logging +from pathlib import Path +import re +import shutil +import tarfile +from typing import Any + +from esphome import const, yaml_util +from esphome.const import ( + CONF_ESPHOME, + CONF_EXTERNAL_COMPONENTS, + CONF_INCLUDES, + CONF_INCLUDES_C, + CONF_PATH, + CONF_SOURCE, + CONF_TYPE, +) +from esphome.core import CORE, EsphomeError + +_LOGGER = logging.getLogger(__name__) + +BUNDLE_EXTENSION = ".esphomebundle.tar.gz" +MANIFEST_FILENAME = "manifest.json" +CURRENT_MANIFEST_VERSION = 1 +MAX_DECOMPRESSED_SIZE = 500 * 1024 * 1024 # 500 MB +MAX_MANIFEST_SIZE = 1024 * 1024 # 1 MB + +# Directories preserved across bundle extractions (build caches) +_PRESERVE_DIRS = (".esphome", ".pioenvs", ".pio") +_BUNDLE_STAGING_DIR = ".bundle_staging" + + +class ManifestKey(StrEnum): + """Keys used in bundle manifest.json.""" + + MANIFEST_VERSION = "manifest_version" + ESPHOME_VERSION = "esphome_version" + CONFIG_FILENAME = "config_filename" + FILES = "files" + HAS_SECRETS = "has_secrets" + + +# String prefixes that are never local file paths +_NON_PATH_PREFIXES = ("http://", "https://", "ftp://", "mdi:", "<") + +# File extensions recognized when resolving relative path strings. +# A relative string with one of these extensions is resolved against the +# config directory and included if the file exists. +_KNOWN_FILE_EXTENSIONS = frozenset( + { + # Fonts + ".ttf", + ".otf", + ".woff", + ".woff2", + ".pcf", + ".bdf", + # Images + ".png", + ".jpg", + ".jpeg", + ".bmp", + ".gif", + ".svg", + ".ico", + ".webp", + # Certificates + ".pem", + ".crt", + ".key", + ".der", + ".p12", + ".pfx", + # C/C++ includes + ".h", + ".hpp", + ".c", + ".cpp", + ".ino", + # Web assets + ".css", + ".js", + ".html", + } +) + + +# Matches !secret references in YAML text. This is intentionally a simple +# regex scan rather than a YAML parse — it may match inside comments or +# multi-line strings, which is the conservative direction (include more +# secrets rather than fewer). +_SECRET_RE = re.compile(r"!secret\s+(\S+)") + + +def _find_used_secret_keys(yaml_files: list[Path]) -> set[str]: + """Scan YAML files for ``!secret `` references.""" + keys: set[str] = set() + for fpath in yaml_files: + try: + text = fpath.read_text(encoding="utf-8") + except (OSError, UnicodeDecodeError): + continue + for match in _SECRET_RE.finditer(text): + keys.add(match.group(1)) + return keys + + +@dataclass +class BundleFile: + """A file to include in the bundle.""" + + path: str # Relative path inside the archive + source: Path # Absolute path on disk + + +@dataclass +class BundleResult: + """Result of creating a bundle.""" + + data: bytes + manifest: dict[str, Any] + files: list[BundleFile] + + +@dataclass +class BundleManifest: + """Parsed and validated bundle manifest.""" + + manifest_version: int + esphome_version: str + config_filename: str + files: list[str] + has_secrets: bool + + +class ConfigBundleCreator: + """Creates a self-contained bundle from an ESPHome config.""" + + def __init__(self, config: dict[str, Any]) -> None: + self._config = config + self._config_dir = CORE.config_dir + self._config_path = CORE.config_path + self._files: list[BundleFile] = [] + self._seen_paths: set[Path] = set() + self._secrets_paths: set[Path] = set() + + def discover_files(self) -> list[BundleFile]: + """Discover all files needed for the bundle.""" + self._files = [] + self._seen_paths = set() + self._secrets_paths = set() + + # The main config file + self._add_file(self._config_path) + + # Phase 1: YAML includes (tracked during config loading) + self._discover_yaml_includes() + + # Phase 2: Component-referenced files from validated config + self._discover_component_files() + + return list(self._files) + + def create_bundle(self) -> BundleResult: + """Create the bundle archive.""" + files = self.discover_files() + + # Determine which secret keys are actually referenced by the + # bundled YAML files so we only ship those, not the entire + # secrets.yaml which may contain secrets for other devices. + yaml_sources = [ + bf.source for bf in files if bf.source.suffix in (".yaml", ".yml") + ] + used_secret_keys = _find_used_secret_keys(yaml_sources) + filtered_secrets = self._build_filtered_secrets(used_secret_keys) + + has_secrets = bool(filtered_secrets) + if has_secrets: + _LOGGER.warning( + "Bundle contains secrets (e.g. Wi-Fi passwords). " + "Do not share it with untrusted parties." + ) + + manifest = self._build_manifest(files, has_secrets=has_secrets) + + buf = io.BytesIO() + with tarfile.open(fileobj=buf, mode="w:gz") as tar: + # Add manifest first + manifest_data = json.dumps(manifest, indent=2).encode("utf-8") + _add_bytes_to_tar(tar, MANIFEST_FILENAME, manifest_data) + + # Add filtered secrets files + for rel_path, data in sorted(filtered_secrets.items()): + _add_bytes_to_tar(tar, rel_path, data) + + # Add files in sorted order for determinism, skipping secrets + # files which were already added above with filtered content + for bf in sorted(files, key=lambda f: f.path): + if bf.source in self._secrets_paths: + continue + self._add_to_tar(tar, bf) + + return BundleResult(data=buf.getvalue(), manifest=manifest, files=files) + + def _add_file(self, abs_path: Path) -> bool: + """Add a file to the bundle. Returns False if already added.""" + abs_path = abs_path.resolve() + if abs_path in self._seen_paths: + return False + if not abs_path.is_file(): + _LOGGER.warning("Bundle: skipping missing file %s", abs_path) + return False + + rel_path = self._relative_to_config_dir(abs_path) + if rel_path is None: + _LOGGER.warning( + "Bundle: skipping file outside config directory: %s", abs_path + ) + return False + + self._seen_paths.add(abs_path) + self._files.append(BundleFile(path=rel_path, source=abs_path)) + return True + + def _add_directory(self, abs_path: Path) -> None: + """Recursively add all files in a directory.""" + abs_path = abs_path.resolve() + if not abs_path.is_dir(): + _LOGGER.warning("Bundle: skipping missing directory %s", abs_path) + return + for child in sorted(abs_path.rglob("*")): + if child.is_file() and "__pycache__" not in child.parts: + self._add_file(child) + + def _relative_to_config_dir(self, abs_path: Path) -> str | None: + """Get a path relative to the config directory. Returns None if outside. + + Always uses forward slashes for consistency in tar archives. + """ + try: + return abs_path.relative_to(self._config_dir).as_posix() + except ValueError: + return None + + def _discover_yaml_includes(self) -> None: + """Discover YAML files loaded during config parsing. + + We track files by wrapping _load_yaml_internal. The config has already + been loaded at this point (bundle is a POST_CONFIG_ACTION), so we + re-load just to discover the file list. + + Secrets files are tracked separately so we can filter them to + only include the keys this config actually references. + """ + with yaml_util.track_yaml_loads() as loaded_files: + try: + yaml_util.load_yaml(self._config_path) + except EsphomeError: + _LOGGER.debug( + "Bundle: re-loading YAML for include discovery failed, " + "proceeding with partial file list" + ) + + for fpath in loaded_files: + if fpath == self._config_path.resolve(): + continue # Already added as config + if fpath.name in const.SECRETS_FILES: + self._secrets_paths.add(fpath) + self._add_file(fpath) + + def _discover_component_files(self) -> None: + """Walk the validated config for file references. + + Uses a generic recursive walk to find file paths instead of + hardcoding per-component knowledge about config dict formats. + After validation, components typically resolve paths to absolute + using CORE.relative_config_path() or cv.file_(). Relative paths + with known file extensions are also resolved and checked. + + Core ESPHome concepts that use relative paths or directories + are handled explicitly. + """ + config = self._config + + # Generic walk: find all file paths in the validated config + self._walk_config_for_files(config) + + # --- Core ESPHome concepts needing explicit handling --- + + # esphome.includes / includes_c - can be relative paths and directories + esphome_conf = config.get(CONF_ESPHOME, {}) + for include_path in esphome_conf.get(CONF_INCLUDES, []): + resolved = _resolve_include_path(include_path) + if resolved is None: + continue + if resolved.is_dir(): + self._add_directory(resolved) + else: + self._add_file(resolved) + for include_path in esphome_conf.get(CONF_INCLUDES_C, []): + resolved = _resolve_include_path(include_path) + if resolved is not None: + self._add_file(resolved) + + # external_components with source: local - directories + for ext_conf in config.get(CONF_EXTERNAL_COMPONENTS, []): + source = ext_conf.get(CONF_SOURCE, {}) + if not isinstance(source, dict): + continue + if source.get(CONF_TYPE) != "local": + continue + path = source.get(CONF_PATH) + if not path: + continue + p = Path(path) + if not p.is_absolute(): + p = CORE.relative_config_path(p) + self._add_directory(p) + + def _walk_config_for_files(self, obj: Any) -> None: + """Recursively walk the config dict looking for file path references.""" + if isinstance(obj, dict): + for value in obj.values(): + self._walk_config_for_files(value) + elif isinstance(obj, (list, tuple)): + for item in obj: + self._walk_config_for_files(item) + elif isinstance(obj, Path): + if obj.is_absolute() and obj.is_file(): + self._add_file(obj) + elif isinstance(obj, str): + self._check_string_path(obj) + + def _check_string_path(self, value: str) -> None: + """Check if a string value is a local file reference.""" + # Fast exits for strings that cannot be file paths + if len(value) < 2 or "\n" in value: + return + if value.startswith(_NON_PATH_PREFIXES): + return + # File paths must contain a path separator or a dot (for extension) + if "/" not in value and "\\" not in value and "." not in value: + return + + p = Path(value) + + # Absolute path - check if it points to an existing file + if p.is_absolute(): + if p.is_file(): + self._add_file(p) + return + + # Relative path with a known file extension - likely a component + # validator that forgot to resolve to absolute via cv.file_() or + # CORE.relative_config_path(). Warn and try to resolve. + if p.suffix.lower() in _KNOWN_FILE_EXTENSIONS: + _LOGGER.warning( + "Bundle: non-absolute path in validated config: %s " + "(component validator should return absolute paths)", + value, + ) + resolved = CORE.relative_config_path(p) + if resolved.is_file(): + self._add_file(resolved) + + def _build_filtered_secrets(self, used_keys: set[str]) -> dict[str, bytes]: + """Build filtered secrets files containing only the referenced keys. + + Returns a dict mapping relative archive path to YAML bytes. + """ + if not used_keys or not self._secrets_paths: + return {} + + result: dict[str, bytes] = {} + for secrets_path in self._secrets_paths: + rel_path = self._relative_to_config_dir(secrets_path) + if rel_path is None: + continue + try: + all_secrets = yaml_util.load_yaml(secrets_path, clear_secrets=False) + except EsphomeError: + _LOGGER.warning("Bundle: failed to load secrets file %s", secrets_path) + continue + if not isinstance(all_secrets, dict): + continue + filtered = {k: v for k, v in all_secrets.items() if k in used_keys} + if filtered: + data = yaml_util.dump(filtered, show_secrets=True).encode("utf-8") + result[rel_path] = data + return result + + def _build_manifest( + self, files: list[BundleFile], *, has_secrets: bool + ) -> dict[str, Any]: + """Build the manifest.json content.""" + return { + ManifestKey.MANIFEST_VERSION: CURRENT_MANIFEST_VERSION, + ManifestKey.ESPHOME_VERSION: const.__version__, + ManifestKey.CONFIG_FILENAME: self._config_path.name, + ManifestKey.FILES: [f.path for f in files], + ManifestKey.HAS_SECRETS: has_secrets, + } + + @staticmethod + def _add_to_tar(tar: tarfile.TarFile, bf: BundleFile) -> None: + """Add a BundleFile to the tar archive with deterministic metadata.""" + with open(bf.source, "rb") as f: + _add_bytes_to_tar(tar, bf.path, f.read()) + + +def extract_bundle( + bundle_path: Path, + target_dir: Path | None = None, +) -> Path: + """Extract a bundle archive and return the path to the config YAML. + + Sanity checks reject path traversal, symlinks, absolute paths, and + oversized archives to prevent accidental file overwrites or extraction + outside the target directory. These are **not** a security boundary — + bundles are assumed to come from the user's own machine or a trusted + build pipeline. + + Args: + bundle_path: Path to the .tar.gz bundle file. + target_dir: Directory to extract into. If None, extracts next to + the bundle file in a directory named after it. + + Returns: + Absolute path to the extracted config YAML file. + + Raises: + EsphomeError: If the bundle is invalid or extraction fails. + """ + + bundle_path = bundle_path.resolve() + if not bundle_path.is_file(): + raise EsphomeError(f"Bundle file not found: {bundle_path}") + + if target_dir is None: + target_dir = _default_target_dir(bundle_path) + + target_dir = target_dir.resolve() + target_dir.mkdir(parents=True, exist_ok=True) + + # Read and validate the archive + try: + with tarfile.open(bundle_path, "r:gz") as tar: + manifest = _read_manifest_from_tar(tar) + _validate_tar_members(tar, target_dir) + tar.extractall(path=target_dir, filter="data") + except tarfile.TarError as err: + raise EsphomeError(f"Failed to extract bundle: {err}") from err + + config_filename = manifest[ManifestKey.CONFIG_FILENAME] + config_path = target_dir / config_filename + if not config_path.is_file(): + raise EsphomeError( + f"Bundle manifest references config '{config_filename}' " + f"but it was not found in the archive" + ) + + return config_path + + +def read_bundle_manifest(bundle_path: Path) -> BundleManifest: + """Read and validate the manifest from a bundle without full extraction. + + Args: + bundle_path: Path to the .tar.gz bundle file. + + Returns: + Parsed BundleManifest. + + Raises: + EsphomeError: If the manifest is missing, invalid, or version unsupported. + """ + + try: + with tarfile.open(bundle_path, "r:gz") as tar: + manifest = _read_manifest_from_tar(tar) + except tarfile.TarError as err: + raise EsphomeError(f"Failed to read bundle: {err}") from err + + return BundleManifest( + manifest_version=manifest[ManifestKey.MANIFEST_VERSION], + esphome_version=manifest.get(ManifestKey.ESPHOME_VERSION, "unknown"), + config_filename=manifest[ManifestKey.CONFIG_FILENAME], + files=manifest.get(ManifestKey.FILES, []), + has_secrets=manifest.get(ManifestKey.HAS_SECRETS, False), + ) + + +def _read_manifest_from_tar(tar: tarfile.TarFile) -> dict[str, Any]: + """Read and validate manifest.json from an open tar archive.""" + + try: + member = tar.getmember(MANIFEST_FILENAME) + except KeyError: + raise EsphomeError("Invalid bundle: missing manifest.json") from None + + f = tar.extractfile(member) + if f is None: + raise EsphomeError("Invalid bundle: manifest.json is not a regular file") + + if member.size > MAX_MANIFEST_SIZE: + raise EsphomeError( + f"Invalid bundle: manifest.json too large " + f"({member.size} bytes, max {MAX_MANIFEST_SIZE})" + ) + + try: + manifest = json.loads(f.read()) + except (json.JSONDecodeError, UnicodeDecodeError) as err: + raise EsphomeError(f"Invalid bundle: malformed manifest.json: {err}") from err + + # Version check + version = manifest.get(ManifestKey.MANIFEST_VERSION) + if version is None: + raise EsphomeError("Invalid bundle: manifest.json missing 'manifest_version'") + if not isinstance(version, int) or version < 1: + raise EsphomeError( + f"Invalid bundle: manifest_version must be a positive integer, got {version!r}" + ) + if version > CURRENT_MANIFEST_VERSION: + raise EsphomeError( + f"Bundle manifest version {version} is newer than this ESPHome " + f"version supports (max {CURRENT_MANIFEST_VERSION}). " + f"Please upgrade ESPHome to compile this bundle." + ) + + # Required fields + if ManifestKey.CONFIG_FILENAME not in manifest: + raise EsphomeError("Invalid bundle: manifest.json missing 'config_filename'") + + return manifest + + +def _validate_tar_members(tar: tarfile.TarFile, target_dir: Path) -> None: + """Sanity-check tar members to prevent mistakes and accidental overwrites. + + This is not a security boundary — bundles are created locally or come + from a trusted build pipeline. The checks catch malformed archives + and common mistakes (stray absolute paths, ``..`` components) that + could silently overwrite unrelated files. + """ + + total_size = 0 + for member in tar.getmembers(): + # Reject absolute paths (Unix and Windows) + if member.name.startswith(("/", "\\")): + raise EsphomeError( + f"Invalid bundle: absolute path in archive: {member.name}" + ) + + # Reject path traversal (split on both / and \ for cross-platform) + parts = re.split(r"[/\\]", member.name) + if ".." in parts: + raise EsphomeError( + f"Invalid bundle: path traversal in archive: {member.name}" + ) + + # Reject symlinks + if member.issym() or member.islnk(): + raise EsphomeError(f"Invalid bundle: symlink in archive: {member.name}") + + # Ensure extraction stays within target_dir + target_path = (target_dir / member.name).resolve() + if not target_path.is_relative_to(target_dir): + raise EsphomeError( + f"Invalid bundle: file would extract outside target: {member.name}" + ) + + # Track total decompressed size + total_size += member.size + if total_size > MAX_DECOMPRESSED_SIZE: + raise EsphomeError( + f"Invalid bundle: decompressed size exceeds " + f"{MAX_DECOMPRESSED_SIZE // (1024 * 1024)}MB limit" + ) + + +def is_bundle_path(path: Path) -> bool: + """Check if a path looks like a bundle file.""" + return path.name.lower().endswith(BUNDLE_EXTENSION) + + +def _add_bytes_to_tar(tar: tarfile.TarFile, name: str, data: bytes) -> None: + """Add in-memory bytes to a tar archive with deterministic metadata.""" + info = tarfile.TarInfo(name=name) + info.size = len(data) + info.mtime = 0 + info.uid = 0 + info.gid = 0 + info.mode = 0o644 + tar.addfile(info, io.BytesIO(data)) + + +def _resolve_include_path(include_path: Any) -> Path | None: + """Resolve an include path to absolute, skipping system includes.""" + if isinstance(include_path, str) and include_path.startswith("<"): + return None # System include, not a local file + p = Path(include_path) + if not p.is_absolute(): + p = CORE.relative_config_path(p) + return p + + +def _default_target_dir(bundle_path: Path) -> Path: + """Compute the default extraction directory for a bundle.""" + name = bundle_path.name + if name.lower().endswith(BUNDLE_EXTENSION): + name = name[: -len(BUNDLE_EXTENSION)] + return bundle_path.parent / name + + +def _restore_preserved_dirs(preserved: dict[str, Path], target_dir: Path) -> None: + """Move preserved build cache directories back into target_dir. + + If the bundle contained entries under a preserved directory name, + the extracted copy is removed so the original cache always wins. + """ + for dirname, src in preserved.items(): + dst = target_dir / dirname + if dst.exists(): + shutil.rmtree(dst) + shutil.move(str(src), str(dst)) + + +def prepare_bundle_for_compile( + bundle_path: Path, + target_dir: Path | None = None, +) -> Path: + """Extract a bundle for compilation, preserving build caches. + + Unlike extract_bundle(), this preserves .esphome/ and .pioenvs/ + directories in the target if they already exist (for incremental builds). + + Args: + bundle_path: Path to the .tar.gz bundle file. + target_dir: Directory to extract into. Must be specified for + build server use. + + Returns: + Absolute path to the extracted config YAML file. + """ + + bundle_path = bundle_path.resolve() + if not bundle_path.is_file(): + raise EsphomeError(f"Bundle file not found: {bundle_path}") + + if target_dir is None: + target_dir = _default_target_dir(bundle_path) + + target_dir = target_dir.resolve() + target_dir.mkdir(parents=True, exist_ok=True) + + preserved: dict[str, Path] = {} + + # Temporarily move preserved dirs out of the way + staging = target_dir / _BUNDLE_STAGING_DIR + for dirname in _PRESERVE_DIRS: + src = target_dir / dirname + if src.is_dir(): + dst = staging / dirname + dst.parent.mkdir(parents=True, exist_ok=True) + shutil.move(str(src), str(dst)) + preserved[dirname] = dst + + try: + # Clean non-preserved content and extract fresh + for item in target_dir.iterdir(): + if item.name == _BUNDLE_STAGING_DIR: + continue + if item.is_dir(): + shutil.rmtree(item) + else: + item.unlink() + + config_path = extract_bundle(bundle_path, target_dir) + finally: + # Restore preserved dirs (idempotent) and clean staging + _restore_preserved_dirs(preserved, target_dir) + if staging.is_dir(): + shutil.rmtree(staging) + + return config_path diff --git a/esphome/codegen.py b/esphome/codegen.py index 30e3135360..a5b5abe447 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -79,6 +79,7 @@ from esphome.cpp_types import ( # noqa: F401 float_, global_ns, gpio_Flags, + int8, int16, int32, int64, diff --git a/esphome/components/absolute_humidity/absolute_humidity.cpp b/esphome/components/absolute_humidity/absolute_humidity.cpp index 40676f8655..3137a59fad 100644 --- a/esphome/components/absolute_humidity/absolute_humidity.cpp +++ b/esphome/components/absolute_humidity/absolute_humidity.cpp @@ -1,22 +1,29 @@ #include "esphome/core/log.h" #include "absolute_humidity.h" -namespace esphome { -namespace absolute_humidity { +namespace esphome::absolute_humidity { -static const char *const TAG = "absolute_humidity.sensor"; +static const char *const TAG{"absolute_humidity.sensor"}; void AbsoluteHumidityComponent::setup() { + this->temperature_sensor_->add_on_state_callback([this](float state) { + this->temperature_ = state; + this->enable_loop(); + }); ESP_LOGD(TAG, " Added callback for temperature '%s'", this->temperature_sensor_->get_name().c_str()); - this->temperature_sensor_->add_on_state_callback([this](float state) { this->temperature_callback_(state); }); + // Get initial value if (this->temperature_sensor_->has_state()) { - this->temperature_callback_(this->temperature_sensor_->get_state()); + this->temperature_ = this->temperature_sensor_->get_state(); } + this->humidity_sensor_->add_on_state_callback([this](float state) { + this->humidity_ = state; + this->enable_loop(); + }); ESP_LOGD(TAG, " Added callback for relative humidity '%s'", this->humidity_sensor_->get_name().c_str()); - this->humidity_sensor_->add_on_state_callback([this](float state) { this->humidity_callback_(state); }); + // Get initial value if (this->humidity_sensor_->has_state()) { - this->humidity_callback_(this->humidity_sensor_->get_state()); + this->humidity_ = this->humidity_sensor_->get_state(); } } @@ -46,14 +53,12 @@ void AbsoluteHumidityComponent::dump_config() { } void AbsoluteHumidityComponent::loop() { - if (!this->next_update_) { - return; - } - this->next_update_ = false; + // Only run once + this->disable_loop(); // Ensure we have source data - const bool no_temperature = std::isnan(this->temperature_); - const bool no_humidity = std::isnan(this->humidity_); + const bool no_temperature{std::isnan(this->temperature_)}; + const bool no_humidity{std::isnan(this->humidity_)}; if (no_temperature || no_humidity) { if (no_temperature) { ESP_LOGW(TAG, "No valid state from temperature sensor!"); @@ -67,9 +72,9 @@ void AbsoluteHumidityComponent::loop() { } // Convert to desired units - const float temperature_c = this->temperature_; - const float temperature_k = temperature_c + 273.15; - const float hr = this->humidity_ / 100; + const float temperature_c{this->temperature_}; + const float temperature_k{temperature_c + 273.15f}; + const float hr{this->humidity_ / 100.0f}; // Calculate saturation vapor pressure float es; @@ -90,7 +95,7 @@ void AbsoluteHumidityComponent::loop() { } // Calculate absolute humidity - const float absolute_humidity = vapor_density(es, hr, temperature_k); + const float absolute_humidity{vapor_density(es, hr, temperature_k)}; ESP_LOGD(TAG, "Saturation vapor pressure %f kPa, absolute humidity %f g/m³", es, absolute_humidity); @@ -103,16 +108,16 @@ void AbsoluteHumidityComponent::loop() { // More accurate than Tetens in normal meteorologic conditions float AbsoluteHumidityComponent::es_buck(float temperature_c) { float a, b, c, d; - if (temperature_c >= 0) { - a = 0.61121; - b = 18.678; - c = 234.5; - d = 257.14; + if (temperature_c >= 0.0f) { + a = 0.61121f; + b = 18.678f; + c = 234.5f; + d = 257.14f; } else { - a = 0.61115; - b = 18.678; - c = 233.7; - d = 279.82; + a = 0.61115f; + b = 18.678f; + c = 233.7f; + d = 279.82f; } return a * expf((b - (temperature_c / c)) * (temperature_c / (d + temperature_c))); } @@ -120,14 +125,14 @@ float AbsoluteHumidityComponent::es_buck(float temperature_c) { // Tetens equation (https://en.wikipedia.org/wiki/Tetens_equation) float AbsoluteHumidityComponent::es_tetens(float temperature_c) { float a, b; - if (temperature_c >= 0) { - a = 17.27; - b = 237.3; + if (temperature_c >= 0.0f) { + a = 17.27f; + b = 237.3f; } else { - a = 21.875; - b = 265.5; + a = 21.875f; + b = 265.5f; } - return 0.61078 * expf((a * temperature_c) / (temperature_c + b)); + return 0.61078f * expf((a * temperature_c) / (temperature_c + b)); } // Wobus equation @@ -146,18 +151,18 @@ float AbsoluteHumidityComponent::es_wobus(float t) { // // Baker, Schlatter 17-MAY-1982 Original version. - const float c0 = +0.99999683e00; - const float c1 = -0.90826951e-02; - const float c2 = +0.78736169e-04; - const float c3 = -0.61117958e-06; - const float c4 = +0.43884187e-08; - const float c5 = -0.29883885e-10; - const float c6 = +0.21874425e-12; - const float c7 = -0.17892321e-14; - const float c8 = +0.11112018e-16; - const float c9 = -0.30994571e-19; - const float p = c0 + t * (c1 + t * (c2 + t * (c3 + t * (c4 + t * (c5 + t * (c6 + t * (c7 + t * (c8 + t * (c9))))))))); - return 0.61078 / pow(p, 8); + constexpr float c0{+0.99999683e+00f}; + constexpr float c1{-0.90826951e-02f}; + constexpr float c2{+0.78736169e-04f}; + constexpr float c3{-0.61117958e-06f}; + constexpr float c4{+0.43884187e-08f}; + constexpr float c5{-0.29883885e-10f}; + constexpr float c6{+0.21874425e-12f}; + constexpr float c7{-0.17892321e-14f}; + constexpr float c8{+0.11112018e-16f}; + constexpr float c9{-0.30994571e-19f}; + const float p{c0 + t * (c1 + t * (c2 + t * (c3 + t * (c4 + t * (c5 + t * (c6 + t * (c7 + t * (c8 + t * (c9)))))))))}; + return 0.61078f / powf(p, 8.0f); } // From https://www.environmentalbiophysics.org/chalk-talk-how-to-calculate-absolute-humidity/ @@ -168,11 +173,10 @@ float AbsoluteHumidityComponent::vapor_density(float es, float hr, float ta) { // hr = relative humidity [0-1] // ta = absolute temperature (K) - const float ea = hr * es * 1000; // vapor pressure of the air (Pa) - const float mw = 18.01528; // molar mass of water (g⋅mol⁻¹) - const float r = 8.31446261815324; // molar gas constant (J⋅K⁻¹) + const float ea{hr * es * 1000.0f}; // vapor pressure of the air (Pa) + const float mw{18.01528f}; // molar mass of water (g⋅mol⁻¹) + const float r{8.31446261815324f}; // molar gas constant (J⋅K⁻¹) return (ea * mw) / (r * ta); } -} // namespace absolute_humidity -} // namespace esphome +} // namespace esphome::absolute_humidity diff --git a/esphome/components/absolute_humidity/absolute_humidity.h b/esphome/components/absolute_humidity/absolute_humidity.h index 71feee2c42..be28d3dc50 100644 --- a/esphome/components/absolute_humidity/absolute_humidity.h +++ b/esphome/components/absolute_humidity/absolute_humidity.h @@ -3,8 +3,7 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -namespace esphome { -namespace absolute_humidity { +namespace esphome::absolute_humidity { /// Enum listing all implemented saturation vapor pressure equations. enum SaturationVaporPressureEquation { @@ -16,8 +15,6 @@ enum SaturationVaporPressureEquation { /// This class implements calculation of absolute humidity from temperature and relative humidity. class AbsoluteHumidityComponent : public sensor::Sensor, public Component { public: - AbsoluteHumidityComponent() = default; - void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; } void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; } void set_equation(SaturationVaporPressureEquation equation) { this->equation_ = equation; } @@ -27,15 +24,6 @@ class AbsoluteHumidityComponent : public sensor::Sensor, public Component { void loop() override; protected: - void temperature_callback_(float state) { - this->next_update_ = true; - this->temperature_ = state; - } - void humidity_callback_(float state) { - this->next_update_ = true; - this->humidity_ = state; - } - /** Buck equation for saturation vapor pressure in kPa. * * @param temperature_c Air temperature in °C. @@ -57,19 +45,15 @@ class AbsoluteHumidityComponent : public sensor::Sensor, public Component { * @param es Saturation vapor pressure in kPa. * @param hr Relative humidity 0 to 1. * @param ta Absolute temperature in K. - * @param heater_duration The duration in ms that the heater should turn on for when measuring. */ static float vapor_density(float es, float hr, float ta); sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr}; - bool next_update_{false}; - float temperature_{NAN}; float humidity_{NAN}; SaturationVaporPressureEquation equation_; }; -} // namespace absolute_humidity -} // namespace esphome +} // namespace esphome::absolute_humidity diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h index 91cf4eaafc..cf48ccd9c3 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -22,7 +22,8 @@ namespace adc { #ifdef USE_ESP32 // clang-format off -#if (ESP_IDF_VERSION_MAJOR == 5 && \ +#if ESP_IDF_VERSION_MAJOR >= 6 || \ + (ESP_IDF_VERSION_MAJOR == 5 && \ ((ESP_IDF_VERSION_MINOR == 0 && ESP_IDF_VERSION_PATCH >= 5) || \ (ESP_IDF_VERSION_MINOR == 1 && ESP_IDF_VERSION_PATCH >= 3) || \ (ESP_IDF_VERSION_MINOR >= 2)) \ diff --git a/esphome/components/adc/adc_sensor_esp32.cpp b/esphome/components/adc/adc_sensor_esp32.cpp index 1d3138623e..fc707013a8 100644 --- a/esphome/components/adc/adc_sensor_esp32.cpp +++ b/esphome/components/adc/adc_sensor_esp32.cpp @@ -2,6 +2,7 @@ #include "adc_sensor.h" #include "esphome/core/log.h" +#include namespace esphome { namespace adc { @@ -346,7 +347,8 @@ float ADCSensor::sample_autorange_() { ESP_LOGVV(TAG, "Autorange summary:"); ESP_LOGVV(TAG, " Raw readings: 12db=%d, 6db=%d, 2.5db=%d, 0db=%d", raw12, raw6, raw2, raw0); ESP_LOGVV(TAG, " Voltages: 12db=%.6f, 6db=%.6f, 2.5db=%.6f, 0db=%.6f", mv12, mv6, mv2, mv0); - ESP_LOGVV(TAG, " Coefficients: c12=%u, c6=%u, c2=%u, c0=%u, sum=%u", c12, c6, c2, c0, csum); + ESP_LOGVV(TAG, " Coefficients: c12=%" PRIu32 ", c6=%" PRIu32 ", c2=%" PRIu32 ", c0=%" PRIu32 ", sum=%" PRIu32, c12, + c6, c2, c0, csum); if (csum == 0) { ESP_LOGE(TAG, "Invalid weight sum in autorange calculation"); @@ -354,8 +356,10 @@ float ADCSensor::sample_autorange_() { } const float final_result = (mv12 * c12 + mv6 * c6 + mv2 * c2 + mv0 * c0) / csum; - ESP_LOGV(TAG, "Autorange final: (%.6f*%u + %.6f*%u + %.6f*%u + %.6f*%u)/%u = %.6fV", mv12, c12, mv6, c6, mv2, c2, mv0, - c0, csum, final_result); + ESP_LOGV(TAG, + "Autorange final: (%.6f*%" PRIu32 " + %.6f*%" PRIu32 " + %.6f*%" PRIu32 " + %.6f*%" PRIu32 ")/%" PRIu32 + " = %.6fV", + mv12, c12, mv6, c6, mv2, c2, mv0, c0, csum, final_result); return final_result; } diff --git a/esphome/components/ads1118/__init__.py b/esphome/components/ads1118/__init__.py index 128e0d0701..45d47a329e 100644 --- a/esphome/components/ads1118/__init__.py +++ b/esphome/components/ads1118/__init__.py @@ -12,11 +12,15 @@ CONF_ADS1118_ID = "ads1118_id" ads1118_ns = cg.esphome_ns.namespace("ads1118") ADS1118 = ads1118_ns.class_("ADS1118", cg.Component, spi.SPIDevice) -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(ADS1118), - } -).extend(spi.spi_device_schema(cs_pin_required=True)) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ADS1118), + } + ) + .extend(spi.spi_device_schema(cs_pin_required=True)) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): diff --git a/esphome/components/ags10/sensor.py b/esphome/components/ags10/sensor.py index 4cfa9e67ec..6491d7d810 100644 --- a/esphome/components/ags10/sensor.py +++ b/esphome/components/ags10/sensor.py @@ -35,7 +35,7 @@ CONFIG_SCHEMA = ( cv.Schema( { cv.GenerateID(): cv.declare_id(AGS10Component), - cv.Optional(CONF_TVOC): sensor.sensor_schema( + cv.Required(CONF_TVOC): sensor.sensor_schema( unit_of_measurement=UNIT_PARTS_PER_BILLION, icon=ICON_RADIATOR, accuracy_decimals=0, @@ -97,7 +97,7 @@ AGS10_NEW_I2C_ADDRESS_SCHEMA = cv.maybe_simple_value( async def ags10newi2caddress_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) - address = await cg.templatable(config[CONF_ADDRESS], args, int) + address = await cg.templatable(config[CONF_ADDRESS], args, cg.uint8) cg.add(var.set_new_address(address)) return var @@ -112,7 +112,9 @@ AGS10_SET_ZERO_POINT_ACTION_MODE = { AGS10_SET_ZERO_POINT_SCHEMA = cv.Schema( { cv.GenerateID(): cv.use_id(AGS10Component), - cv.Required(CONF_MODE): cv.enum(AGS10_SET_ZERO_POINT_ACTION_MODE, upper=True), + cv.Required(CONF_MODE): cv.templatable( + cv.enum(AGS10_SET_ZERO_POINT_ACTION_MODE, upper=True) + ), cv.Optional(CONF_VALUE, default=0xFFFF): cv.templatable(cv.uint16_t), }, ) @@ -127,8 +129,10 @@ AGS10_SET_ZERO_POINT_SCHEMA = cv.Schema( async def ags10setzeropoint_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) - mode = await cg.templatable(config.get(CONF_MODE), args, enumerate) + mode = await cg.templatable( + config.get(CONF_MODE), args, AGS10SetZeroPointActionMode + ) cg.add(var.set_mode(mode)) - value = await cg.templatable(config[CONF_VALUE], args, int) + value = await cg.templatable(config[CONF_VALUE], args, cg.uint16) cg.add(var.set_value(value)) return var diff --git a/esphome/components/aic3204/audio_dac.py b/esphome/components/aic3204/audio_dac.py index a644638f69..b478b573a3 100644 --- a/esphome/components/aic3204/audio_dac.py +++ b/esphome/components/aic3204/audio_dac.py @@ -43,7 +43,7 @@ async def aic3204_set_volume_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) - template_ = await cg.templatable(config.get(CONF_MODE), args, int) + template_ = await cg.templatable(config.get(CONF_MODE), args, cg.uint8) cg.add(var.set_auto_mute_mode(template_)) return var diff --git a/esphome/components/alarm_control_panel/__init__.py b/esphome/components/alarm_control_panel/__init__.py index aefb18d25c..9fcdf42ecb 100644 --- a/esphome/components/alarm_control_panel/__init__.py +++ b/esphome/components/alarm_control_panel/__init__.py @@ -10,7 +10,6 @@ from esphome.const import ( CONF_ID, CONF_MQTT_ID, CONF_ON_STATE, - CONF_TRIGGER_ID, CONF_WEB_SERVER, ) from esphome.core import CORE, CoroPriority, coroutine_with_priority @@ -34,39 +33,9 @@ CONF_ON_READY = "on_ready" alarm_control_panel_ns = cg.esphome_ns.namespace("alarm_control_panel") AlarmControlPanel = alarm_control_panel_ns.class_("AlarmControlPanel", cg.EntityBase) -StateTrigger = alarm_control_panel_ns.class_( - "StateTrigger", automation.Trigger.template() -) -TriggeredTrigger = alarm_control_panel_ns.class_( - "TriggeredTrigger", automation.Trigger.template() -) -ClearedTrigger = alarm_control_panel_ns.class_( - "ClearedTrigger", automation.Trigger.template() -) -ArmingTrigger = alarm_control_panel_ns.class_( - "ArmingTrigger", automation.Trigger.template() -) -PendingTrigger = alarm_control_panel_ns.class_( - "PendingTrigger", automation.Trigger.template() -) -ArmedHomeTrigger = alarm_control_panel_ns.class_( - "ArmedHomeTrigger", automation.Trigger.template() -) -ArmedNightTrigger = alarm_control_panel_ns.class_( - "ArmedNightTrigger", automation.Trigger.template() -) -ArmedAwayTrigger = alarm_control_panel_ns.class_( - "ArmedAwayTrigger", automation.Trigger.template() -) -DisarmedTrigger = alarm_control_panel_ns.class_( - "DisarmedTrigger", automation.Trigger.template() -) -ChimeTrigger = alarm_control_panel_ns.class_( - "ChimeTrigger", automation.Trigger.template() -) -ReadyTrigger = alarm_control_panel_ns.class_( - "ReadyTrigger", automation.Trigger.template() -) +StateAnyForwarder = alarm_control_panel_ns.class_("StateAnyForwarder") +StateEnterForwarder = alarm_control_panel_ns.class_("StateEnterForwarder") +AlarmControlPanelState = alarm_control_panel_ns.enum("AlarmControlPanelState") ArmAwayAction = alarm_control_panel_ns.class_("ArmAwayAction", automation.Action) ArmHomeAction = alarm_control_panel_ns.class_("ArmHomeAction", automation.Action) @@ -89,61 +58,17 @@ _ALARM_CONTROL_PANEL_SCHEMA = ( cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id( mqtt.MQTTAlarmControlPanelComponent ), - cv.Optional(CONF_ON_STATE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), - } - ), - cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger), - } - ), - cv.Optional(CONF_ON_ARMING): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmingTrigger), - } - ), - cv.Optional(CONF_ON_PENDING): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PendingTrigger), - } - ), - cv.Optional(CONF_ON_ARMED_HOME): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedHomeTrigger), - } - ), - cv.Optional(CONF_ON_ARMED_NIGHT): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedNightTrigger), - } - ), - cv.Optional(CONF_ON_ARMED_AWAY): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedAwayTrigger), - } - ), - cv.Optional(CONF_ON_DISARMED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DisarmedTrigger), - } - ), - cv.Optional(CONF_ON_CLEARED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger), - } - ), - cv.Optional(CONF_ON_CHIME): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChimeTrigger), - } - ), - cv.Optional(CONF_ON_READY): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReadyTrigger), - } - ), + cv.Optional(CONF_ON_STATE): automation.validate_automation({}), + cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation({}), + cv.Optional(CONF_ON_ARMING): automation.validate_automation({}), + cv.Optional(CONF_ON_PENDING): automation.validate_automation({}), + cv.Optional(CONF_ON_ARMED_HOME): automation.validate_automation({}), + cv.Optional(CONF_ON_ARMED_NIGHT): automation.validate_automation({}), + cv.Optional(CONF_ON_ARMED_AWAY): automation.validate_automation({}), + cv.Optional(CONF_ON_DISARMED): automation.validate_automation({}), + cv.Optional(CONF_ON_CLEARED): automation.validate_automation({}), + cv.Optional(CONF_ON_CHIME): automation.validate_automation({}), + cv.Optional(CONF_ON_READY): automation.validate_automation({}), } ) ) @@ -186,41 +111,66 @@ ALARM_CONTROL_PANEL_CONDITION_SCHEMA = maybe_simple_id( ) +_CALLBACK_AUTOMATIONS = ( + automation.CallbackAutomation( + CONF_ON_STATE, "add_on_state_callback", forwarder=StateAnyForwarder + ), + automation.CallbackAutomation( + CONF_ON_TRIGGERED, + "add_on_state_callback", + forwarder=StateEnterForwarder.template( + AlarmControlPanelState.ACP_STATE_TRIGGERED + ), + ), + automation.CallbackAutomation( + CONF_ON_ARMING, + "add_on_state_callback", + forwarder=StateEnterForwarder.template(AlarmControlPanelState.ACP_STATE_ARMING), + ), + automation.CallbackAutomation( + CONF_ON_PENDING, + "add_on_state_callback", + forwarder=StateEnterForwarder.template( + AlarmControlPanelState.ACP_STATE_PENDING + ), + ), + automation.CallbackAutomation( + CONF_ON_ARMED_HOME, + "add_on_state_callback", + forwarder=StateEnterForwarder.template( + AlarmControlPanelState.ACP_STATE_ARMED_HOME + ), + ), + automation.CallbackAutomation( + CONF_ON_ARMED_NIGHT, + "add_on_state_callback", + forwarder=StateEnterForwarder.template( + AlarmControlPanelState.ACP_STATE_ARMED_NIGHT + ), + ), + automation.CallbackAutomation( + CONF_ON_ARMED_AWAY, + "add_on_state_callback", + forwarder=StateEnterForwarder.template( + AlarmControlPanelState.ACP_STATE_ARMED_AWAY + ), + ), + automation.CallbackAutomation( + CONF_ON_DISARMED, + "add_on_state_callback", + forwarder=StateEnterForwarder.template( + AlarmControlPanelState.ACP_STATE_DISARMED + ), + ), + automation.CallbackAutomation(CONF_ON_CLEARED, "add_on_cleared_callback"), + automation.CallbackAutomation(CONF_ON_CHIME, "add_on_chime_callback"), + automation.CallbackAutomation(CONF_ON_READY, "add_on_ready_callback"), +) + + @setup_entity("alarm_control_panel") async def setup_alarm_control_panel_core_(var, config): - for conf in config.get(CONF_ON_STATE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_TRIGGERED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_ARMING, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_PENDING, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_ARMED_HOME, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_ARMED_NIGHT, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_ARMED_AWAY, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_DISARMED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_CLEARED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_CHIME, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_READY, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS) if web_server_config := config.get(CONF_WEB_SERVER): await web_server.add_entity_config(var, web_server_config) if mqtt_id := config.get(CONF_MQTT_ID): diff --git a/esphome/components/alarm_control_panel/alarm_control_panel.cpp b/esphome/components/alarm_control_panel/alarm_control_panel.cpp index ab0a780cef..fc72c13ce3 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel.cpp @@ -31,12 +31,12 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) { this->last_update_ = millis(); if (state != this->current_state_) { auto prev_state = this->current_state_; - ESP_LOGD(TAG, "'%s' >> %s (was %s)", this->get_name().c_str(), + ESP_LOGV(TAG, "'%s' >> %s (was %s)", this->get_name().c_str(), LOG_STR_ARG(alarm_control_panel_state_to_string(state)), LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state))); this->current_state_ = state; - // Single state callback - triggers check get_state() for specific states - this->state_callback_.call(); + // Single state callback - listeners receive the new state as an argument + this->state_callback_.call(state); #if defined(USE_ALARM_CONTROL_PANEL) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_alarm_control_panel_update(this); #endif @@ -51,22 +51,6 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) { } } -void AlarmControlPanel::add_on_state_callback(std::function &&callback) { - this->state_callback_.add(std::move(callback)); -} - -void AlarmControlPanel::add_on_cleared_callback(std::function &&callback) { - this->cleared_callback_.add(std::move(callback)); -} - -void AlarmControlPanel::add_on_chime_callback(std::function &&callback) { - this->chime_callback_.add(std::move(callback)); -} - -void AlarmControlPanel::add_on_ready_callback(std::function &&callback) { - this->ready_callback_.add(std::move(callback)); -} - void AlarmControlPanel::arm_with_code_(AlarmControlPanelCall &(AlarmControlPanelCall::*arm_method)(), const char *code) { auto call = this->make_call(); diff --git a/esphome/components/alarm_control_panel/alarm_control_panel.h b/esphome/components/alarm_control_panel/alarm_control_panel.h index e8dc197e26..e748b8621b 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel.h +++ b/esphome/components/alarm_control_panel/alarm_control_panel.h @@ -37,25 +37,24 @@ class AlarmControlPanel : public EntityBase { * * @param callback The callback function */ - void add_on_state_callback(std::function &&callback); + template void add_on_state_callback(F &&callback) { + this->state_callback_.add(std::forward(callback)); + } - /** Add a callback for when the state of the alarm_control_panel clears from triggered - * - * @param callback The callback function - */ - void add_on_cleared_callback(std::function &&callback); + /** Add a callback for when the state of the alarm_control_panel clears from triggered. */ + template void add_on_cleared_callback(F &&callback) { + this->cleared_callback_.add(std::forward(callback)); + } - /** Add a callback for when a chime zone goes from closed to open - * - * @param callback The callback function - */ - void add_on_chime_callback(std::function &&callback); + /** Add a callback for when a chime zone goes from closed to open. */ + template void add_on_chime_callback(F &&callback) { + this->chime_callback_.add(std::forward(callback)); + } - /** Add a callback for when a ready state changes - * - * @param callback The callback function - */ - void add_on_ready_callback(std::function &&callback); + /** Add a callback for when a ready state changes. */ + template void add_on_ready_callback(F &&callback) { + this->ready_callback_.add(std::forward(callback)); + } /** A numeric representation of the supported features as per HomeAssistant * @@ -146,8 +145,8 @@ class AlarmControlPanel : public EntityBase { uint32_t last_update_; // the call control function virtual void control(const AlarmControlPanelCall &call) = 0; - // state callback - triggers check get_state() for specific state - LazyCallbackManager state_callback_{}; + // state callback - passes the new state to listeners + LazyCallbackManager state_callback_{}; // clear callback - fires when leaving TRIGGERED state LazyCallbackManager cleared_callback_{}; // chime callback diff --git a/esphome/components/alarm_control_panel/automation.h b/esphome/components/alarm_control_panel/automation.h index 4ff34de0d5..022d2650d2 100644 --- a/esphome/components/alarm_control_panel/automation.h +++ b/esphome/components/alarm_control_panel/automation.h @@ -5,60 +5,27 @@ namespace esphome::alarm_control_panel { -/// Trigger on any state change -class StateTrigger : public Trigger<> { - public: - explicit StateTrigger(AlarmControlPanel *alarm_control_panel) { - alarm_control_panel->add_on_state_callback([this]() { this->trigger(); }); +/// Callback forwarder that triggers an Automation<> on any state change. +/// Pointer-sized (single Automation* field) to fit inline in Callback::ctx_. +struct StateAnyForwarder { + Automation<> *automation; + void operator()(AlarmControlPanelState /*state*/) const { this->automation->trigger(); } +}; + +/// Callback forwarder that triggers an Automation<> only when the alarm enters a specific state. +/// Pointer-sized (single Automation* field) to fit inline in Callback::ctx_. +template struct StateEnterForwarder { + Automation<> *automation; + void operator()(AlarmControlPanelState state) const { + if (state == State) + this->automation->trigger(); } }; -/// Template trigger that fires when entering a specific state -template class StateEnterTrigger : public Trigger<> { - public: - explicit StateEnterTrigger(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) { - alarm_control_panel->add_on_state_callback([this]() { - if (this->alarm_control_panel_->get_state() == State) - this->trigger(); - }); - } - - protected: - AlarmControlPanel *alarm_control_panel_; -}; - -// Type aliases for state-specific triggers -using TriggeredTrigger = StateEnterTrigger; -using ArmingTrigger = StateEnterTrigger; -using PendingTrigger = StateEnterTrigger; -using ArmedHomeTrigger = StateEnterTrigger; -using ArmedNightTrigger = StateEnterTrigger; -using ArmedAwayTrigger = StateEnterTrigger; -using DisarmedTrigger = StateEnterTrigger; - -/// Trigger when leaving TRIGGERED state (alarm cleared) -class ClearedTrigger : public Trigger<> { - public: - explicit ClearedTrigger(AlarmControlPanel *alarm_control_panel) { - alarm_control_panel->add_on_cleared_callback([this]() { this->trigger(); }); - } -}; - -/// Trigger on chime event (zone opened while disarmed) -class ChimeTrigger : public Trigger<> { - public: - explicit ChimeTrigger(AlarmControlPanel *alarm_control_panel) { - alarm_control_panel->add_on_chime_callback([this]() { this->trigger(); }); - } -}; - -/// Trigger on ready state change -class ReadyTrigger : public Trigger<> { - public: - explicit ReadyTrigger(AlarmControlPanel *alarm_control_panel) { - alarm_control_panel->add_on_ready_callback([this]() { this->trigger(); }); - } -}; +static_assert(sizeof(StateAnyForwarder) <= sizeof(void *)); +static_assert(std::is_trivially_copyable_v); +static_assert(sizeof(StateEnterForwarder) <= sizeof(void *)); +static_assert(std::is_trivially_copyable_v>); template class ArmAwayAction : public Action { public: diff --git a/esphome/components/alpha3/sensor.py b/esphome/components/alpha3/sensor.py index 361e1d101f..279ab214cf 100644 --- a/esphome/components/alpha3/sensor.py +++ b/esphome/components/alpha3/sensor.py @@ -9,6 +9,10 @@ from esphome.const import ( CONF_POWER, CONF_SPEED, CONF_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_POWER, + DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, UNIT_AMPERE, UNIT_CUBIC_METER_PER_HOUR, UNIT_METER, @@ -27,26 +31,35 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_FLOW): sensor.sensor_schema( unit_of_measurement=UNIT_CUBIC_METER_PER_HOUR, accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HEAD): sensor.sensor_schema( unit_of_measurement=UNIT_METER, accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_POWER): sensor.sensor_schema( unit_of_measurement=UNIT_WATT, accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CURRENT): sensor.sensor_schema( unit_of_measurement=UNIT_AMPERE, accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_SPEED): sensor.sensor_schema( unit_of_measurement=UNIT_REVOLUTIONS_PER_MINUTE, accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/am43/sensor/__init__.py b/esphome/components/am43/sensor/__init__.py index 4b3e1716a4..2697d364ad 100644 --- a/esphome/components/am43/sensor/__init__.py +++ b/esphome/components/am43/sensor/__init__.py @@ -8,6 +8,7 @@ from esphome.const import ( DEVICE_CLASS_BATTERY, ENTITY_CATEGORY_DIAGNOSTIC, ICON_BRIGHTNESS_5, + STATE_CLASS_MEASUREMENT, UNIT_PERCENT, ) @@ -26,11 +27,13 @@ CONFIG_SCHEMA = ( device_class=DEVICE_CLASS_BATTERY, accuracy_decimals=0, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, icon=ICON_BRIGHTNESS_5, accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/analog_threshold/analog_threshold_binary_sensor.h b/esphome/components/analog_threshold/analog_threshold_binary_sensor.h index dd70768105..55a822b9b0 100644 --- a/esphome/components/analog_threshold/analog_threshold_binary_sensor.h +++ b/esphome/components/analog_threshold/analog_threshold_binary_sensor.h @@ -19,8 +19,8 @@ class AnalogThresholdBinarySensor : public Component, public binary_sensor::Bina protected: sensor::Sensor *sensor_{nullptr}; - TemplatableValue upper_threshold_{}; - TemplatableValue lower_threshold_{}; + TemplatableFn upper_threshold_{}; + TemplatableFn lower_threshold_{}; bool raw_state_{false}; // Pre-filter state for hysteresis logic }; diff --git a/esphome/components/analog_threshold/binary_sensor.py b/esphome/components/analog_threshold/binary_sensor.py index b5f87b9b5c..8c13727755 100644 --- a/esphome/components/analog_threshold/binary_sensor.py +++ b/esphome/components/analog_threshold/binary_sensor.py @@ -40,10 +40,10 @@ async def to_code(config): cg.add(var.set_sensor(sens)) if isinstance(config[CONF_THRESHOLD], dict): - lower = await cg.templatable(config[CONF_THRESHOLD][CONF_LOWER], [], float) - upper = await cg.templatable(config[CONF_THRESHOLD][CONF_UPPER], [], float) + lower = await cg.templatable(config[CONF_THRESHOLD][CONF_LOWER], [], cg.float_) + upper = await cg.templatable(config[CONF_THRESHOLD][CONF_UPPER], [], cg.float_) else: - lower = await cg.templatable(config[CONF_THRESHOLD], [], float) + lower = await cg.templatable(config[CONF_THRESHOLD], [], cg.float_) upper = lower cg.add(var.set_upper_threshold(upper)) cg.add(var.set_lower_threshold(lower)) diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 9772e6afca..84589d540d 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -301,11 +301,12 @@ CONFIG_SCHEMA = cv.All( # Maximum queued send buffers per connection before dropping connection # Each buffer uses ~8-12 bytes overhead plus actual message size # Platform defaults based on available RAM and typical message rates: + # CONF_MAX_SEND_QUEUE defaults are power of 2 for efficient modulo cv.SplitDefault( CONF_MAX_SEND_QUEUE, - esp8266=5, # Limited RAM, need to fail fast + esp8266=4, # Limited RAM, need to fail fast esp32=8, # More RAM, can buffer more - rp2040=5, # Limited RAM + rp2040=8, # Moderate RAM bk72xx=8, # Moderate RAM nrf52=8, # Moderate RAM rtl87xx=8, # Moderate RAM @@ -454,6 +455,9 @@ async def to_code(config: ConfigType) -> None: cg.add_define("USE_API_PLAINTEXT") cg.add_define("USE_API_NOISE") cg.add_library("esphome/noise-c", "0.1.11") + # Enable optimized memzero/memcmp in libsodium instead of volatile byte loops + cg.add_build_flag("-DHAVE_WEAK_SYMBOLS=1") + cg.add_build_flag("-DHAVE_INLINE_ASM=1") else: cg.add_define("USE_API_PLAINTEXT") diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 28332d67a5..33d16f0339 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -129,11 +129,12 @@ message HelloResponse { // A string identifying the server (ESP); like client info this may be empty // and only exists for debugging/logging purposes. - // For example "ESPHome v1.10.0 on ESP8266" - string server_info = 3; + // Currently set to ESPHOME_VERSION string literal. + string server_info = 3 [(max_data_length) = 32, (force) = true]; - // The name of the server (App.get_name()) - string name = 4; + // The name of the server (App.get_name() - device hostname) + // max_data_length matches ESPHOME_DEVICE_NAME_MAX_LEN (validated by validate_hostname) + string name = 4 [(max_data_length) = 31, (force) = true]; } // DEPRECATED in ESPHome 2026.1.0 - Password authentication is no longer supported. @@ -196,12 +197,14 @@ message DeviceInfoRequest { message AreaInfo { uint32 area_id = 1; - string name = 2; + // max_data_length matches core/config.FRIENDLY_NAME_MAX_LEN via AREA_SCHEMA + string name = 2 [(max_data_length) = 120, (force) = true]; } message DeviceInfo { uint32 device_id = 1; - string name = 2; + // max_data_length matches core/config.FRIENDLY_NAME_MAX_LEN via DEVICE_SCHEMA + string name = 2 [(max_data_length) = 120, (force) = true]; uint32 area_id = 3; } @@ -216,6 +219,16 @@ message SerialProxyInfo { SerialProxyPortType port_type = 2; // Port type (RS232, RS485) } +// DeviceInfoResponse max_data_length values: +// name = 31 (ESPHOME_DEVICE_NAME_MAX_LEN, validated by validate_hostname) +// friendly_name = 120 (core/config.FRIENDLY_NAME_MAX_LEN) +// mac_address/bluetooth_mac_address = 17 (MAC_ADDRESS_PRETTY_BUFFER_SIZE - 1, constexpr) +// esphome_version = 32 (ESPHOME_VERSION string literal) +// compilation_time = 25 (Application::BUILD_TIME_STR_SIZE - 1, constexpr) +// manufacturer = 20 (longest hardcoded literal: "Nordic Semiconductor") +// model = 127 (core/config.BOARD_MAX_LENGTH, validated in platform schemas) +// project_name/project_version = 127 (core/config.PROJECT_MAX_LENGTH) +// suggested_area = 120 (core/config.FRIENDLY_NAME_MAX_LEN via AREA_SCHEMA) message DeviceInfoResponse { option (id) = 10; option (source) = SOURCE_SERVER; @@ -224,28 +237,30 @@ message DeviceInfoResponse { // with older ESPHome versions that still send this field. bool uses_password = 1 [deprecated = true]; - // The name of the node, given by "App.set_name()" - string name = 2; + // The name of the node, given by "App.set_name()" - device hostname + string name = 2 [(max_data_length) = 31, (force) = true]; // The mac address of the device. For example "AC:BC:32:89:0E:A9" - string mac_address = 3; + string mac_address = 3 [(max_data_length) = 17, (force) = true]; // A string describing the ESPHome version. For example "1.10.0" - string esphome_version = 4; + string esphome_version = 4 [(max_data_length) = 32, (force) = true]; // A string describing the date of compilation, this is generated by the compiler // and therefore may not be in the same format all the time. // If the user isn't using ESPHome, this will also not be set. - string compilation_time = 5; + string compilation_time = 5 [(max_data_length) = 25, (force) = true]; // The model of the board. For example NodeMCU - string model = 6; + // max_data_length matches core/config.BOARD_MAX_LENGTH (validated in platform schemas) + string model = 6 [(max_data_length) = 127, (force) = true]; bool has_deep_sleep = 7 [(field_ifdef) = "USE_DEEP_SLEEP"]; // The esphome project details if set - string project_name = 8 [(field_ifdef) = "ESPHOME_PROJECT_NAME"]; - string project_version = 9 [(field_ifdef) = "ESPHOME_PROJECT_NAME"]; + // max_data_length matches core/config.PROJECT_MAX_LENGTH + string project_name = 8 [(max_data_length) = 127, (force) = true, (field_ifdef) = "ESPHOME_PROJECT_NAME"]; + string project_version = 9 [(max_data_length) = 127, (force) = true, (field_ifdef) = "ESPHOME_PROJECT_NAME"]; uint32 webserver_port = 10 [(field_ifdef) = "USE_WEBSERVER"]; @@ -253,18 +268,18 @@ message DeviceInfoResponse { uint32 legacy_bluetooth_proxy_version = 11 [deprecated=true, (field_ifdef) = "USE_BLUETOOTH_PROXY"]; uint32 bluetooth_proxy_feature_flags = 15 [(field_ifdef) = "USE_BLUETOOTH_PROXY"]; - string manufacturer = 12; + string manufacturer = 12 [(max_data_length) = 20, (force) = true]; - string friendly_name = 13; + string friendly_name = 13 [(max_data_length) = 120, (force) = true]; // Deprecated in API version 1.10 uint32 legacy_voice_assistant_version = 14 [deprecated=true, (field_ifdef) = "USE_VOICE_ASSISTANT"]; uint32 voice_assistant_feature_flags = 17 [(field_ifdef) = "USE_VOICE_ASSISTANT"]; - string suggested_area = 16 [(field_ifdef) = "USE_AREAS"]; + string suggested_area = 16 [(max_data_length) = 120, (force) = true, (field_ifdef) = "USE_AREAS"]; // The Bluetooth mac address of the device. For example "AC:BC:32:89:0E:AA" - string bluetooth_mac_address = 18 [(field_ifdef) = "USE_BLUETOOTH_PROXY"]; + string bluetooth_mac_address = 18 [(max_data_length) = 17, (force) = true, (field_ifdef) = "USE_BLUETOOTH_PROXY"]; // Supports receiving and saving api encryption key bool api_encryption_supported = 19 [(field_ifdef) = "USE_API_NOISE"]; @@ -308,6 +323,12 @@ enum EntityCategory { ENTITY_CATEGORY_DIAGNOSTIC = 2; } +// Entity field max_data_length values match Python validation constants: +// name/object_id = 120 (config_validation.NAME_MAX_LENGTH) +// icon = 63 (core/config.ICON_MAX_LENGTH) +// device_class = 47 (core/config.DEVICE_CLASS_MAX_LENGTH) +// unit_of_measurement = 63 (core/config.UNIT_OF_MEASUREMENT_MAX_LENGTH) + // ==================== BINARY SENSOR ==================== message ListEntitiesBinarySensorResponse { option (id) = 12; @@ -315,15 +336,15 @@ message ListEntitiesBinarySensorResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_BINARY_SENSOR"; - string object_id = 1; - fixed32 key = 2; - string name = 3; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; reserved 4; // Deprecated: was string unique_id - string device_class = 5; + string device_class = 5 [(max_data_length) = 47]; bool is_status_binary_sensor = 6; bool disabled_by_default = 7; - string icon = 8 [(field_ifdef) = "USE_ENTITY_ICON"]; + string icon = 8 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; EntityCategory entity_category = 9; uint32 device_id = 10 [(field_ifdef) = "USE_DEVICES"]; } @@ -334,7 +355,7 @@ message BinarySensorStateResponse { option (ifdef) = "USE_BINARY_SENSOR"; option (no_delay) = true; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; bool state = 2; // If the binary sensor does not have a valid state yet. // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller @@ -349,17 +370,17 @@ message ListEntitiesCoverResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_COVER"; - string object_id = 1; - fixed32 key = 2; - string name = 3; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; reserved 4; // Deprecated: was string unique_id bool assumed_state = 5; bool supports_position = 6; bool supports_tilt = 7; - string device_class = 8; + string device_class = 8 [(max_data_length) = 47]; bool disabled_by_default = 9; - string icon = 10 [(field_ifdef) = "USE_ENTITY_ICON"]; + string icon = 10 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; EntityCategory entity_category = 11; bool supports_stop = 12; uint32 device_id = 13 [(field_ifdef) = "USE_DEVICES"]; @@ -383,7 +404,7 @@ message CoverStateResponse { option (ifdef) = "USE_COVER"; option (no_delay) = true; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; // legacy: state has been removed in 1.13 // clients/servers must still send/accept it until the next protocol change // Deprecated in API version 1.1 @@ -409,7 +430,7 @@ message CoverCommandRequest { option (no_delay) = true; option (base_class) = "CommandProtoMessage"; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; // legacy: command has been removed in 1.13 // clients/servers must still send/accept it until the next protocol change @@ -433,9 +454,9 @@ message ListEntitiesFanResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_FAN"; - string object_id = 1; - fixed32 key = 2; - string name = 3; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; reserved 4; // Deprecated: was string unique_id bool supports_oscillation = 5; @@ -443,7 +464,7 @@ message ListEntitiesFanResponse { bool supports_direction = 7; int32 supported_speed_count = 8; bool disabled_by_default = 9; - string icon = 10 [(field_ifdef) = "USE_ENTITY_ICON"]; + string icon = 10 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; EntityCategory entity_category = 11; repeated string supported_preset_modes = 12 [(container_pointer_no_template) = "std::vector"]; uint32 device_id = 13 [(field_ifdef) = "USE_DEVICES"]; @@ -466,7 +487,7 @@ message FanStateResponse { option (ifdef) = "USE_FAN"; option (no_delay) = true; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; bool state = 2; bool oscillating = 3; // Deprecated in API version 1.6 @@ -483,7 +504,7 @@ message FanCommandRequest { option (no_delay) = true; option (base_class) = "CommandProtoMessage"; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; bool has_state = 2; bool state = 3; // Deprecated in API version 1.6 @@ -521,9 +542,9 @@ message ListEntitiesLightResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_LIGHT"; - string object_id = 1; - fixed32 key = 2; - string name = 3; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; reserved 4; // Deprecated: was string unique_id repeated ColorMode supported_color_modes = 12 [(container_pointer_no_template) = "light::ColorModeMask"]; @@ -540,7 +561,7 @@ message ListEntitiesLightResponse { float max_mireds = 10; repeated string effects = 11 [(container_pointer_no_template) = "FixedVector"]; bool disabled_by_default = 13; - string icon = 14 [(field_ifdef) = "USE_ENTITY_ICON"]; + string icon = 14 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; EntityCategory entity_category = 15; uint32 device_id = 16 [(field_ifdef) = "USE_DEVICES"]; } @@ -551,7 +572,7 @@ message LightStateResponse { option (ifdef) = "USE_LIGHT"; option (no_delay) = true; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; bool state = 2; float brightness = 3; ColorMode color_mode = 11; @@ -573,7 +594,7 @@ message LightCommandRequest { option (no_delay) = true; option (base_class) = "CommandProtoMessage"; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; bool has_state = 2; bool state = 3; bool has_brightness = 4; @@ -626,16 +647,16 @@ message ListEntitiesSensorResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_SENSOR"; - string object_id = 1; - fixed32 key = 2; - string name = 3; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; reserved 4; // Deprecated: was string unique_id - string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; - string unit_of_measurement = 6; + string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; + string unit_of_measurement = 6 [(max_data_length) = 63]; int32 accuracy_decimals = 7; bool force_update = 8; - string device_class = 9; + string device_class = 9 [(max_data_length) = 47]; SensorStateClass state_class = 10; // Last reset type removed in 2021.9.0 // Deprecated in API version 1.5 @@ -651,7 +672,7 @@ message SensorStateResponse { option (ifdef) = "USE_SENSOR"; option (no_delay) = true; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; float state = 2; // If the sensor does not have a valid state yet. // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller @@ -666,16 +687,16 @@ message ListEntitiesSwitchResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_SWITCH"; - string object_id = 1; - fixed32 key = 2; - string name = 3; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; reserved 4; // Deprecated: was string unique_id - string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; + string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; bool assumed_state = 6; bool disabled_by_default = 7; EntityCategory entity_category = 8; - string device_class = 9; + string device_class = 9 [(max_data_length) = 47]; uint32 device_id = 10 [(field_ifdef) = "USE_DEVICES"]; } message SwitchStateResponse { @@ -685,7 +706,7 @@ message SwitchStateResponse { option (ifdef) = "USE_SWITCH"; option (no_delay) = true; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; bool state = 2; uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"]; } @@ -696,7 +717,7 @@ message SwitchCommandRequest { option (no_delay) = true; option (base_class) = "CommandProtoMessage"; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; bool state = 2; uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"]; } @@ -708,15 +729,15 @@ message ListEntitiesTextSensorResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_TEXT_SENSOR"; - string object_id = 1; - fixed32 key = 2; - string name = 3; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; reserved 4; // Deprecated: was string unique_id - string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; + string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; bool disabled_by_default = 6; EntityCategory entity_category = 7; - string device_class = 8; + string device_class = 8 [(max_data_length) = 47]; uint32 device_id = 9 [(field_ifdef) = "USE_DEVICES"]; } message TextSensorStateResponse { @@ -726,7 +747,7 @@ message TextSensorStateResponse { option (ifdef) = "USE_TEXT_SENSOR"; option (no_delay) = true; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; string state = 2; // If the text sensor does not have a valid state yet. // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller @@ -922,7 +943,7 @@ message ListEntitiesServicesResponse { option (ifdef) = "USE_API_USER_DEFINED_ACTIONS"; string name = 1; - fixed32 key = 2; + fixed32 key = 2 [(force) = true]; repeated ListEntitiesServicesArgument args = 3 [(fixed_vector) = true]; SupportsResponseType supports_response = 4; } @@ -945,7 +966,7 @@ message ExecuteServiceRequest { option (no_delay) = true; option (ifdef) = "USE_API_USER_DEFINED_ACTIONS"; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; repeated ExecuteServiceArgument args = 2 [(fixed_vector) = true]; uint32 call_id = 3 [(field_ifdef) = "USE_API_USER_DEFINED_ACTION_RESPONSES"]; bool return_response = 4 [(field_ifdef) = "USE_API_USER_DEFINED_ACTION_RESPONSES"]; @@ -971,12 +992,12 @@ message ListEntitiesCameraResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_CAMERA"; - string object_id = 1; - fixed32 key = 2; - string name = 3; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; reserved 4; // Deprecated: was string unique_id bool disabled_by_default = 5; - string icon = 6 [(field_ifdef) = "USE_ENTITY_ICON"]; + string icon = 6 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; EntityCategory entity_category = 7; uint32 device_id = 8 [(field_ifdef) = "USE_DEVICES"]; } @@ -987,7 +1008,7 @@ message CameraImageResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_CAMERA"; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; bytes data = 2; bool done = 3; uint32 device_id = 4 [(field_ifdef) = "USE_DEVICES"]; @@ -1056,9 +1077,9 @@ message ListEntitiesClimateResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_CLIMATE"; - string object_id = 1; - fixed32 key = 2; - string name = 3; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; reserved 4; // Deprecated: was string unique_id bool supports_current_temperature = 5; // Deprecated: use feature_flags @@ -1078,7 +1099,7 @@ message ListEntitiesClimateResponse { repeated ClimatePreset supported_presets = 16 [(container_pointer_no_template) = "climate::ClimatePresetMask"]; repeated string supported_custom_presets = 17 [(container_pointer_no_template) = "std::vector"]; bool disabled_by_default = 18; - string icon = 19 [(field_ifdef) = "USE_ENTITY_ICON"]; + string icon = 19 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; EntityCategory entity_category = 20; float visual_current_temperature_step = 21; bool supports_current_humidity = 22; // Deprecated: use feature_flags @@ -1095,7 +1116,7 @@ message ClimateStateResponse { option (ifdef) = "USE_CLIMATE"; option (no_delay) = true; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; ClimateMode mode = 2; float current_temperature = 3; float target_temperature = 4; @@ -1121,7 +1142,7 @@ message ClimateCommandRequest { option (no_delay) = true; option (base_class) = "CommandProtoMessage"; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; bool has_mode = 2; ClimateMode mode = 3; bool has_target_temperature = 4; @@ -1167,10 +1188,10 @@ message ListEntitiesWaterHeaterResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_WATER_HEATER"; - string object_id = 1; - fixed32 key = 2; - string name = 3; - string icon = 4 [(field_ifdef) = "USE_ENTITY_ICON"]; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; + string icon = 4 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; bool disabled_by_default = 5; EntityCategory entity_category = 6; uint32 device_id = 7 [(field_ifdef) = "USE_DEVICES"]; @@ -1189,7 +1210,7 @@ message WaterHeaterStateResponse { option (ifdef) = "USE_WATER_HEATER"; option (no_delay) = true; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; float current_temperature = 2; float target_temperature = 3; WaterHeaterMode mode = 4; @@ -1219,7 +1240,7 @@ message WaterHeaterCommandRequest { option (no_delay) = true; option (base_class) = "CommandProtoMessage"; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; // Bitmask of which fields are set (see WaterHeaterCommandHasField) uint32 has_fields = 2; WaterHeaterMode mode = 3; @@ -1243,20 +1264,20 @@ message ListEntitiesNumberResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_NUMBER"; - string object_id = 1; - fixed32 key = 2; - string name = 3; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; reserved 4; // Deprecated: was string unique_id - string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; + string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; float min_value = 6; float max_value = 7; float step = 8; bool disabled_by_default = 9; EntityCategory entity_category = 10; - string unit_of_measurement = 11; + string unit_of_measurement = 11 [(max_data_length) = 63]; NumberMode mode = 12; - string device_class = 13; + string device_class = 13 [(max_data_length) = 47]; uint32 device_id = 14 [(field_ifdef) = "USE_DEVICES"]; } message NumberStateResponse { @@ -1266,7 +1287,7 @@ message NumberStateResponse { option (ifdef) = "USE_NUMBER"; option (no_delay) = true; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; float state = 2; // If the number does not have a valid state yet. // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller @@ -1280,7 +1301,7 @@ message NumberCommandRequest { option (no_delay) = true; option (base_class) = "CommandProtoMessage"; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; float state = 2; uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"]; } @@ -1292,12 +1313,12 @@ message ListEntitiesSelectResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_SELECT"; - string object_id = 1; - fixed32 key = 2; - string name = 3; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; reserved 4; // Deprecated: was string unique_id - string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; + string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; repeated string options = 6 [(container_pointer_no_template) = "FixedVector"]; bool disabled_by_default = 7; EntityCategory entity_category = 8; @@ -1310,7 +1331,7 @@ message SelectStateResponse { option (ifdef) = "USE_SELECT"; option (no_delay) = true; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; string state = 2; // If the select does not have a valid state yet. // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller @@ -1324,7 +1345,7 @@ message SelectCommandRequest { option (no_delay) = true; option (base_class) = "CommandProtoMessage"; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; string state = 2; uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"]; } @@ -1336,12 +1357,12 @@ message ListEntitiesSirenResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_SIREN"; - string object_id = 1; - fixed32 key = 2; - string name = 3; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; reserved 4; // Deprecated: was string unique_id - string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; + string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; bool disabled_by_default = 6; repeated string tones = 7 [(container_pointer_no_template) = "FixedVector"]; bool supports_duration = 8; @@ -1356,7 +1377,7 @@ message SirenStateResponse { option (ifdef) = "USE_SIREN"; option (no_delay) = true; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; bool state = 2; uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"]; } @@ -1367,7 +1388,7 @@ message SirenCommandRequest { option (no_delay) = true; option (base_class) = "CommandProtoMessage"; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; bool has_state = 2; bool state = 3; bool has_tone = 4; @@ -1399,12 +1420,12 @@ message ListEntitiesLockResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_LOCK"; - string object_id = 1; - fixed32 key = 2; - string name = 3; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; reserved 4; // Deprecated: was string unique_id - string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; + string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; bool disabled_by_default = 6; EntityCategory entity_category = 7; bool assumed_state = 8; @@ -1422,7 +1443,7 @@ message LockStateResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_LOCK"; option (no_delay) = true; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; LockState state = 2; uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"]; } @@ -1432,7 +1453,7 @@ message LockCommandRequest { option (ifdef) = "USE_LOCK"; option (no_delay) = true; option (base_class) = "CommandProtoMessage"; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; LockCommand command = 2; // Not yet implemented: @@ -1448,15 +1469,15 @@ message ListEntitiesButtonResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_BUTTON"; - string object_id = 1; - fixed32 key = 2; - string name = 3; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; reserved 4; // Deprecated: was string unique_id - string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; + string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; bool disabled_by_default = 6; EntityCategory entity_category = 7; - string device_class = 8; + string device_class = 8 [(max_data_length) = 47]; uint32 device_id = 9 [(field_ifdef) = "USE_DEVICES"]; } message ButtonCommandRequest { @@ -1466,7 +1487,7 @@ message ButtonCommandRequest { option (no_delay) = true; option (base_class) = "CommandProtoMessage"; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; uint32 device_id = 2 [(field_ifdef) = "USE_DEVICES"]; } @@ -1515,12 +1536,12 @@ message ListEntitiesMediaPlayerResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_MEDIA_PLAYER"; - string object_id = 1; - fixed32 key = 2; - string name = 3; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; reserved 4; // Deprecated: was string unique_id - string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; + string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; bool disabled_by_default = 6; EntityCategory entity_category = 7; @@ -1538,7 +1559,7 @@ message MediaPlayerStateResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_MEDIA_PLAYER"; option (no_delay) = true; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; MediaPlayerState state = 2; float volume = 3; bool muted = 4; @@ -1551,7 +1572,7 @@ message MediaPlayerCommandRequest { option (no_delay) = true; option (base_class) = "CommandProtoMessage"; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; bool has_command = 2; MediaPlayerCommand command = 3; @@ -1606,7 +1627,7 @@ message BluetoothLEAdvertisementResponse { message BluetoothLERawAdvertisement { uint64 address = 1 [(force) = true]; sint32 rssi = 2 [(force) = true]; - uint32 address_type = 3; + uint32 address_type = 3 [(max_value) = 4]; bytes data = 4 [(fixed_array_size) = 62, (force) = true]; } @@ -2103,11 +2124,11 @@ message ListEntitiesAlarmControlPanelResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_ALARM_CONTROL_PANEL"; - string object_id = 1; - fixed32 key = 2; - string name = 3; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; reserved 4; // Deprecated: was string unique_id - string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; + string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; bool disabled_by_default = 6; EntityCategory entity_category = 7; uint32 supported_features = 8; @@ -2122,7 +2143,7 @@ message AlarmControlPanelStateResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_ALARM_CONTROL_PANEL"; option (no_delay) = true; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; AlarmControlPanelState state = 2; uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"]; } @@ -2133,7 +2154,7 @@ message AlarmControlPanelCommandRequest { option (ifdef) = "USE_ALARM_CONTROL_PANEL"; option (no_delay) = true; option (base_class) = "CommandProtoMessage"; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; AlarmControlPanelStateCommand command = 2; string code = 3; uint32 device_id = 4 [(field_ifdef) = "USE_DEVICES"]; @@ -2150,11 +2171,11 @@ message ListEntitiesTextResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_TEXT"; - string object_id = 1; - fixed32 key = 2; - string name = 3; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; reserved 4; // Deprecated: was string unique_id - string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; + string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; bool disabled_by_default = 6; EntityCategory entity_category = 7; @@ -2171,7 +2192,7 @@ message TextStateResponse { option (ifdef) = "USE_TEXT"; option (no_delay) = true; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; string state = 2; // If the Text does not have a valid state yet. // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller @@ -2185,7 +2206,7 @@ message TextCommandRequest { option (no_delay) = true; option (base_class) = "CommandProtoMessage"; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; string state = 2; uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"]; } @@ -2198,12 +2219,12 @@ message ListEntitiesDateResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_DATETIME_DATE"; - string object_id = 1; - fixed32 key = 2; - string name = 3; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; reserved 4; // Deprecated: was string unique_id - string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; + string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; bool disabled_by_default = 6; EntityCategory entity_category = 7; uint32 device_id = 8 [(field_ifdef) = "USE_DEVICES"]; @@ -2215,7 +2236,7 @@ message DateStateResponse { option (ifdef) = "USE_DATETIME_DATE"; option (no_delay) = true; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; // If the date does not have a valid state yet. // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller bool missing_state = 2; @@ -2231,7 +2252,7 @@ message DateCommandRequest { option (no_delay) = true; option (base_class) = "CommandProtoMessage"; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; uint32 year = 2; uint32 month = 3; uint32 day = 4; @@ -2245,12 +2266,12 @@ message ListEntitiesTimeResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_DATETIME_TIME"; - string object_id = 1; - fixed32 key = 2; - string name = 3; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; reserved 4; // Deprecated: was string unique_id - string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; + string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; bool disabled_by_default = 6; EntityCategory entity_category = 7; uint32 device_id = 8 [(field_ifdef) = "USE_DEVICES"]; @@ -2262,7 +2283,7 @@ message TimeStateResponse { option (ifdef) = "USE_DATETIME_TIME"; option (no_delay) = true; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; // If the time does not have a valid state yet. // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller bool missing_state = 2; @@ -2278,7 +2299,7 @@ message TimeCommandRequest { option (no_delay) = true; option (base_class) = "CommandProtoMessage"; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; uint32 hour = 2; uint32 minute = 3; uint32 second = 4; @@ -2292,15 +2313,15 @@ message ListEntitiesEventResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_EVENT"; - string object_id = 1; - fixed32 key = 2; - string name = 3; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; reserved 4; // Deprecated: was string unique_id - string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; + string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; bool disabled_by_default = 6; EntityCategory entity_category = 7; - string device_class = 8; + string device_class = 8 [(max_data_length) = 47]; repeated string event_types = 9 [(container_pointer_no_template) = "FixedVector"]; uint32 device_id = 10 [(field_ifdef) = "USE_DEVICES"]; @@ -2311,7 +2332,7 @@ message EventResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_EVENT"; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; string event_type = 2; uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"]; } @@ -2323,15 +2344,15 @@ message ListEntitiesValveResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_VALVE"; - string object_id = 1; - fixed32 key = 2; - string name = 3; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; reserved 4; // Deprecated: was string unique_id - string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; + string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; bool disabled_by_default = 6; EntityCategory entity_category = 7; - string device_class = 8; + string device_class = 8 [(max_data_length) = 47]; bool assumed_state = 9; bool supports_position = 10; @@ -2351,7 +2372,7 @@ message ValveStateResponse { option (ifdef) = "USE_VALVE"; option (no_delay) = true; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; float position = 2; ValveOperation current_operation = 3; uint32 device_id = 4 [(field_ifdef) = "USE_DEVICES"]; @@ -2364,7 +2385,7 @@ message ValveCommandRequest { option (no_delay) = true; option (base_class) = "CommandProtoMessage"; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; bool has_position = 2; float position = 3; bool stop = 4; @@ -2378,12 +2399,12 @@ message ListEntitiesDateTimeResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_DATETIME_DATETIME"; - string object_id = 1; - fixed32 key = 2; - string name = 3; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; reserved 4; // Deprecated: was string unique_id - string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; + string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; bool disabled_by_default = 6; EntityCategory entity_category = 7; uint32 device_id = 8 [(field_ifdef) = "USE_DEVICES"]; @@ -2395,7 +2416,7 @@ message DateTimeStateResponse { option (ifdef) = "USE_DATETIME_DATETIME"; option (no_delay) = true; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; // If the datetime does not have a valid state yet. // Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller bool missing_state = 2; @@ -2409,7 +2430,7 @@ message DateTimeCommandRequest { option (no_delay) = true; option (base_class) = "CommandProtoMessage"; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; fixed32 epoch_seconds = 2; uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"]; } @@ -2421,15 +2442,15 @@ message ListEntitiesUpdateResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_UPDATE"; - string object_id = 1; - fixed32 key = 2; - string name = 3; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; reserved 4; // Deprecated: was string unique_id - string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; + string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; bool disabled_by_default = 6; EntityCategory entity_category = 7; - string device_class = 8; + string device_class = 8 [(max_data_length) = 47]; uint32 device_id = 9 [(field_ifdef) = "USE_DEVICES"]; } message UpdateStateResponse { @@ -2439,7 +2460,7 @@ message UpdateStateResponse { option (ifdef) = "USE_UPDATE"; option (no_delay) = true; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; bool missing_state = 2; bool in_progress = 3; bool has_progress = 4; @@ -2463,7 +2484,7 @@ message UpdateCommandRequest { option (no_delay) = true; option (base_class) = "CommandProtoMessage"; - fixed32 key = 1; + fixed32 key = 1 [(force) = true]; UpdateCommand command = 2; uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"]; } @@ -2504,14 +2525,15 @@ message ListEntitiesInfraredResponse { option (source) = SOURCE_SERVER; option (ifdef) = "USE_INFRARED"; - string object_id = 1; - fixed32 key = 2; - string name = 3; - string icon = 4 [(field_ifdef) = "USE_ENTITY_ICON"]; + string object_id = 1 [(max_data_length) = 120, (force) = true]; + fixed32 key = 2 [(force) = true]; + string name = 3 [(max_data_length) = 120, (force) = true]; + string icon = 4 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; bool disabled_by_default = 5; EntityCategory entity_category = 6; uint32 device_id = 7 [(field_ifdef) = "USE_DEVICES"]; uint32 capabilities = 8; // Bitfield of InfraredCapabilityFlags + uint32 receiver_frequency = 9; // Demodulation frequency of the IR receiver in Hz (0 = unspecified) } // Command to transmit infrared/RF data using raw timings @@ -2521,7 +2543,7 @@ message InfraredRFTransmitRawTimingsRequest { option (ifdef) = "USE_IR_RF"; uint32 device_id = 1 [(field_ifdef) = "USE_DEVICES"]; - fixed32 key = 2; // Key identifying the transmitter instance + fixed32 key = 2 [(force) = true]; // Key identifying the transmitter instance uint32 carrier_frequency = 3; // Carrier frequency in Hz uint32 repeat_count = 4; // Number of times to transmit (1 = once, 2 = twice, etc.) repeated sint32 timings = 5 [packed = true, (packed_buffer) = true]; // Raw timings in microseconds (zigzag-encoded): positive = mark (LED/TX on), negative = space (LED/TX off) @@ -2535,7 +2557,7 @@ message InfraredRFReceiveEvent { option (no_delay) = true; uint32 device_id = 1 [(field_ifdef) = "USE_DEVICES"]; - fixed32 key = 2; // Key identifying the receiver instance + fixed32 key = 2 [(force) = true]; // Key identifying the receiver instance repeated sint32 timings = 3 [packed = true, (container_pointer_no_template) = "std::vector"]; // Raw timings in microseconds (zigzag-encoded): alternating mark/space periods } diff --git a/esphome/components/api/api_buffer.h b/esphome/components/api/api_buffer.h index 00801e3ee5..1d0cccf61c 100644 --- a/esphome/components/api/api_buffer.h +++ b/esphome/components/api/api_buffer.h @@ -44,6 +44,12 @@ class APIBuffer { this->reserve(n); this->size_ = n; // no zero-fill } + /// Reserve capacity for max(reserve_size, new_size) bytes, then set size to new_size. + /// Single grow_ check regardless of argument order. + inline void reserve_and_resize(size_t reserve_size, size_t new_size) ESPHOME_ALWAYS_INLINE { + this->reserve(std::max(reserve_size, new_size)); + this->size_ = new_size; + } uint8_t *data() { return this->data_.get(); } const uint8_t *data() const { return this->data_.get(); } size_t size() const { return this->size_; } diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index d97ef97762..7db423141c 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -72,6 +72,14 @@ static constexpr uint32_t HANDSHAKE_TIMEOUT_MS = 60000; static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION); +// Cross-validate C++ constants against proto max_data_length annotations in api.proto +static_assert(MAC_ADDRESS_PRETTY_BUFFER_SIZE - 1 == 17, + "Update max_data_length for mac_address/bluetooth_mac_address in api.proto"); +static_assert(Application::BUILD_TIME_STR_SIZE - 1 == 25, "Update max_data_length for compilation_time in api.proto"); +static_assert(sizeof(ESPHOME_VERSION) - 1 <= 32, "Update max_data_length for esphome_version in api.proto"); +static_assert(ESPHOME_DEVICE_NAME_MAX_LEN <= 31, "Update max_data_length for name in api.proto"); +static_assert(ESPHOME_FRIENDLY_NAME_MAX_LEN <= 120, "Update max_data_length for friendly_name in api.proto"); + static const char *const TAG = "api.connection"; #ifdef USE_CAMERA static const int CAMERA_STOP_STREAM = 5000; @@ -132,8 +140,6 @@ APIConnection::APIConnection(std::unique_ptr sock, APIServer *pa #endif } -uint32_t APIConnection::get_batch_delay_ms_() const { return this->parent_->get_batch_delay(); } - void APIConnection::start() { this->last_traffic_ = App.get_loop_component_start_time(); @@ -234,7 +240,7 @@ void APIConnection::loop() { this->last_traffic_ = now; } // read a packet - this->read_message(buffer.data_len, buffer.type, buffer.data); + this->read_message_(buffer.data_len, buffer.type, buffer.data); if (this->flags_.remove) return; } @@ -309,6 +315,8 @@ void APIConnection::process_active_iterator_() { this->destroy_active_iterator_(); if (this->flags_.state_subscription) { this->begin_iterator_(ActiveIterator::INITIAL_STATE); + } else { + this->finalize_iterator_sync_(); } } else { this->process_iterator_batch_(this->iterator_storage_.list_entities); @@ -316,21 +324,27 @@ void APIConnection::process_active_iterator_() { } else { // INITIAL_STATE if (this->iterator_storage_.initial_state.completed()) { this->destroy_active_iterator_(); - // Process any remaining batched messages immediately - if (!this->deferred_batch_.empty()) { - this->process_batch_(); - } - // Now that everything is sent, enable immediate sending for future state changes - this->flags_.should_try_send_immediately = true; - // Release excess memory from buffers that grew during initial sync - this->deferred_batch_.release_buffer(); - this->helper_->release_buffers(); + this->finalize_iterator_sync_(); } else { this->process_iterator_batch_(this->iterator_storage_.initial_state); } } } +void APIConnection::finalize_iterator_sync_() { + // Flush any remaining batched messages immediately so clients + // receive completion responses (e.g. ListEntitiesDoneResponse) + // without waiting for the batch timer. + if (!this->deferred_batch_.empty()) { + this->process_batch_(); + } + // Enable immediate sending for future state changes + this->flags_.should_try_send_immediately = true; + // Release excess memory from buffers that grew during initial sync + this->deferred_batch_.release_buffer(); + this->helper_->release_buffers(); +} + void APIConnection::process_iterator_batch_(ComponentIterator &iterator) { size_t initial_size = this->deferred_batch_.size(); size_t max_batch = this->get_max_batch_size_(); @@ -400,7 +414,7 @@ uint16_t APIConnection::fill_and_encode_entity_info(EntityBase *entity, InfoResp #ifdef USE_DEVICES msg.device_id = entity->get_device_id(); #endif - return encode_to_buffer(size_fn(&msg), encode_fn, &msg, conn, remaining_size); + return encode_to_buffer_slow(size_fn(&msg), encode_fn, &msg, conn, remaining_size); } uint16_t APIConnection::fill_and_encode_entity_info_with_device_class(EntityBase *entity, InfoResponseProtoMessage &msg, @@ -1465,7 +1479,7 @@ void APIConnection::send_infrared_rf_receive_event(const InfraredRFReceiveEvent void APIConnection::on_serial_proxy_configure_request(const SerialProxyConfigureRequest &msg) { auto &proxies = App.get_serial_proxies(); if (msg.instance >= proxies.size()) { - ESP_LOGW(TAG, "Serial proxy instance %u out of range (max %u)", msg.instance, + ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range (max %" PRIu32 ")", msg.instance, static_cast(proxies.size())); return; } @@ -1476,7 +1490,7 @@ void APIConnection::on_serial_proxy_configure_request(const SerialProxyConfigure void APIConnection::on_serial_proxy_write_request(const SerialProxyWriteRequest &msg) { auto &proxies = App.get_serial_proxies(); if (msg.instance >= proxies.size()) { - ESP_LOGW(TAG, "Serial proxy instance %u out of range", msg.instance); + ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance); return; } proxies[msg.instance]->write_from_client(msg.data, msg.data_len); @@ -1485,7 +1499,7 @@ void APIConnection::on_serial_proxy_write_request(const SerialProxyWriteRequest void APIConnection::on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &msg) { auto &proxies = App.get_serial_proxies(); if (msg.instance >= proxies.size()) { - ESP_LOGW(TAG, "Serial proxy instance %u out of range", msg.instance); + ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance); return; } proxies[msg.instance]->set_modem_pins(msg.line_states); @@ -1494,7 +1508,7 @@ void APIConnection::on_serial_proxy_set_modem_pins_request(const SerialProxySetM void APIConnection::on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &msg) { auto &proxies = App.get_serial_proxies(); if (msg.instance >= proxies.size()) { - ESP_LOGW(TAG, "Serial proxy instance %u out of range", msg.instance); + ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance); return; } SerialProxyGetModemPinsResponse resp{}; @@ -1506,7 +1520,7 @@ void APIConnection::on_serial_proxy_get_modem_pins_request(const SerialProxyGetM void APIConnection::on_serial_proxy_request(const SerialProxyRequest &msg) { auto &proxies = App.get_serial_proxies(); if (msg.instance >= proxies.size()) { - ESP_LOGW(TAG, "Serial proxy instance %u out of range", msg.instance); + ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance); return; } switch (msg.type) { @@ -1519,16 +1533,16 @@ void APIConnection::on_serial_proxy_request(const SerialProxyRequest &msg) { resp.instance = msg.instance; resp.type = enums::SERIAL_PROXY_REQUEST_TYPE_FLUSH; switch (proxies[msg.instance]->flush_port()) { - case uart::FlushResult::SUCCESS: + case uart::UARTFlushResult::UART_FLUSH_RESULT_SUCCESS: resp.status = enums::SERIAL_PROXY_STATUS_OK; break; - case uart::FlushResult::ASSUMED_SUCCESS: + case uart::UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS: resp.status = enums::SERIAL_PROXY_STATUS_ASSUMED_SUCCESS; break; - case uart::FlushResult::TIMEOUT: + case uart::UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT: resp.status = enums::SERIAL_PROXY_STATUS_TIMEOUT; break; - case uart::FlushResult::FAILED: + case uart::UARTFlushResult::UART_FLUSH_RESULT_FAILED: resp.status = enums::SERIAL_PROXY_STATUS_ERROR; break; } @@ -1536,7 +1550,7 @@ void APIConnection::on_serial_proxy_request(const SerialProxyRequest &msg) { break; } default: - ESP_LOGW(TAG, "Unknown serial proxy request type: %u", static_cast(msg.type)); + ESP_LOGW(TAG, "Unknown serial proxy request type: %" PRIu32, static_cast(msg.type)); break; } } @@ -1549,6 +1563,7 @@ uint16_t APIConnection::try_send_infrared_info(EntityBase *entity, APIConnection auto *infrared = static_cast(entity); ListEntitiesInfraredResponse msg; msg.capabilities = infrared->get_capability_flags(); + msg.receiver_frequency = infrared->get_traits().get_receiver_frequency_hz(); return fill_and_encode_entity_info(infrared, msg, conn, remaining_size); } #endif @@ -1717,6 +1732,7 @@ bool APIConnection::send_device_info_response_() { static constexpr auto MANUFACTURER = StringRef::from_lit(ESPHOME_MANUFACTURER); resp.manufacturer = MANUFACTURER; #endif + static_assert(sizeof(ESPHOME_MANUFACTURER) - 1 <= 20, "Update max_data_length for manufacturer in api.proto"); #undef ESPHOME_MANUFACTURER #ifdef USE_ESP8266 @@ -1994,53 +2010,15 @@ bool APIConnection::send_message_(uint32_t payload_size, uint8_t message_type, M size_t write_start = shared_buf.size(); shared_buf.resize(write_start + payload_size); ProtoWriteBuffer buffer{&shared_buf, write_start}; - encode_fn(msg, buffer); + encode_fn(msg, buffer PROTO_ENCODE_DEBUG_INIT(&shared_buf)); return this->send_buffer(ProtoWriteBuffer{&shared_buf}, message_type); } -// Encodes a message to the buffer and returns the total number of bytes used, -// including header and footer overhead. Returns 0 if the message doesn't fit. -uint16_t APIConnection::encode_to_buffer(uint32_t calculated_size, MessageEncodeFn encode_fn, const void *msg, - APIConnection *conn, uint32_t remaining_size) { -#ifdef HAS_PROTO_MESSAGE_DUMP - if (conn->flags_.log_only_mode) { - auto *proto_msg = static_cast(msg); - DumpBuffer dump_buf; - conn->log_send_message_(proto_msg->message_name(), proto_msg->dump_to(dump_buf)); - return 1; - } -#endif - // Cache frame sizes to avoid repeated virtual calls - const uint8_t header_padding = conn->helper_->frame_header_padding(); - const uint8_t footer_size = conn->helper_->frame_footer_size(); +// encode_to_buffer is defined inline in api_connection.h (ESPHOME_ALWAYS_INLINE) - // Calculate total size with padding for buffer allocation - size_t total_calculated_size = calculated_size + header_padding + footer_size; - - // Check if it fits - if (total_calculated_size > remaining_size) - return 0; // Doesn't fit - - auto &shared_buf = conn->parent_->get_shared_buffer_ref(); - - if (conn->flags_.batch_first_message) { - // First message - buffer already prepared by caller, just clear flag - conn->flags_.batch_first_message = false; - } else { - // Batch message second or later - // Add padding for previous message footer + this message header - size_t current_size = shared_buf.size(); - shared_buf.reserve(current_size + total_calculated_size); - shared_buf.resize(current_size + footer_size + header_padding); - } - - // Pre-resize buffer to include payload, then encode through raw pointer - size_t write_start = shared_buf.size(); - shared_buf.resize(write_start + calculated_size); - ProtoWriteBuffer buffer{&shared_buf, write_start}; - encode_fn(msg, buffer); - - // Return total size (header + payload + footer) - return static_cast(header_padding + calculated_size + footer_size); +// Noinline version for cold paths — single shared copy +uint16_t APIConnection::encode_to_buffer_slow(uint32_t calculated_size, MessageEncodeFn encode_fn, const void *msg, + APIConnection *conn, uint32_t remaining_size) { + return encode_to_buffer(calculated_size, encode_fn, msg, conn, remaining_size); } bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) { const bool is_log_message = (message_type == SubscribeLogsResponse::MESSAGE_TYPE); @@ -2072,37 +2050,9 @@ void APIConnection::on_fatal_error() { this->flags_.remove = true; } -void __attribute__((flatten)) APIConnection::DeferredBatch::push_item(const BatchItem &item) { items.push_back(item); } - -void APIConnection::DeferredBatch::add_item(EntityBase *entity, uint8_t message_type, uint8_t estimated_size, - uint8_t aux_data_index) { - // Check if we already have a message of this type for this entity - // This provides deduplication per entity/message_type combination - // O(n) but optimized for RAM and not performance. - // Skip deduplication for events - they are edge-triggered, every occurrence matters -#ifdef USE_EVENT - if (message_type != EventResponse::MESSAGE_TYPE) -#endif - { - for (const auto &item : items) { - if (item.entity == entity && item.message_type == message_type) - return; // Already queued - } - } - // No existing item found (or event), add new one - this->push_item({entity, message_type, estimated_size, aux_data_index}); -} - -void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) { - // Add high priority message and swap to front - // This avoids expensive vector::insert which shifts all elements - // Note: We only ever have one high-priority message at a time (ping OR disconnect) - // If we're disconnecting, pings are blocked, so this simple swap is sufficient - this->push_item({entity, message_type, estimated_size, AUX_DATA_UNUSED}); - if (items.size() > 1) { - // Swap the new high-priority item to the front - std::swap(items.front(), items.back()); - } +bool APIConnection::schedule_message_front_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) { + this->deferred_batch_.add_item_front(entity, message_type, estimated_size); + return this->schedule_batch_(); } bool APIConnection::send_message_smart_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size, @@ -2195,17 +2145,15 @@ void APIConnection::process_batch_multi_(APIBuffer &shared_buf, size_t num_items "MessageInfo must remain trivially destructible with this placement-new approach"); const size_t messages_to_process = std::min(num_items, MAX_MESSAGES_PER_BATCH); - const uint8_t frame_overhead = header_padding + footer_size; // Stack-allocated array for message info alignas(MessageInfo) char message_info_storage[MAX_MESSAGES_PER_BATCH * sizeof(MessageInfo)]; MessageInfo *message_info = reinterpret_cast(message_info_storage); size_t items_processed = 0; uint16_t remaining_size = std::numeric_limits::max(); - // Track where each message's header padding begins in the buffer - // For plaintext: this is where the 6-byte header padding starts - // For noise: this is where the 7-byte header padding starts - // The actual message data follows after the header padding + // Track where each message's header begins in the buffer + // First message: offset 0 (max padding, may have unused leading bytes) + // Subsequent messages: offset points to exact header start (no gaps) uint32_t current_offset = 0; // Process items and encode directly to buffer (up to our limit) @@ -2221,13 +2169,14 @@ void APIConnection::process_batch_multi_(APIBuffer &shared_buf, size_t num_items } // Message was encoded successfully - // payload_size is header_padding + actual payload size + footer_size - uint16_t proto_payload_size = payload_size - frame_overhead; + // payload_size = header_size + proto_payload_size + footer_size + uint16_t proto_payload_size = payload_size - this->batch_header_size_ - footer_size; // Use placement new to construct MessageInfo in pre-allocated stack array // This avoids default-constructing all MAX_MESSAGES_PER_BATCH elements // Explicit destruction is not needed because MessageInfo is trivially destructible, // as ensured by the static_assert in its definition. - new (&message_info[items_processed++]) MessageInfo(item.message_type, current_offset, proto_payload_size); + new (&message_info[items_processed++]) + MessageInfo(item.message_type, current_offset, proto_payload_size, this->batch_header_size_); // After first message, set remaining size to MAX_BATCH_PACKET_SIZE to avoid fragmentation if (items_processed == 1) { remaining_size = MAX_BATCH_PACKET_SIZE; @@ -2244,6 +2193,13 @@ void APIConnection::process_batch_multi_(APIBuffer &shared_buf, size_t num_items shared_buf.resize(shared_buf.size() + footer_size); } + // Ensure TCP_NODELAY is on before writing batch data. + // Log messages enable Nagle (NODELAY off) to coalesce small packets. + // Without this, batch data written to the socket sits in LWIP's Nagle + // buffer — the remote won't ACK until it sends its own data (e.g. a + // ping), which can take 20+ seconds. + this->helper_->set_nodelay_for_message(false); + // Send all collected messages APIError err = this->helper_->write_protobuf_messages(ProtoWriteBuffer{&shared_buf}, std::span(message_info, items_processed)); @@ -2277,6 +2233,7 @@ void APIConnection::process_batch_multi_(APIBuffer &shared_buf, size_t num_items uint16_t APIConnection::dispatch_message_(const DeferredBatch::BatchItem &item, uint32_t remaining_size, bool batch_first) { this->flags_.batch_first_message = batch_first; + this->batch_message_type_ = item.message_type; #ifdef USE_EVENT // Events need aux_data_index to look up event type from entity if (item.message_type == EventResponse::MESSAGE_TYPE) { diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 68f698d190..284c4475de 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -20,6 +20,9 @@ #ifdef USE_RP2040_CRASH_HANDLER #include "esphome/components/rp2040/crash_handler.h" #endif +#ifdef USE_ESP8266_CRASH_HANDLER +#include "esphome/components/esp8266/crash_handler.h" +#endif #include "esphome/core/entity_base.h" #include "esphome/core/string_ref.h" @@ -44,16 +47,46 @@ static constexpr size_t MAX_INITIAL_PER_BATCH = 34; // For clients >= AP static_assert(MAX_MESSAGES_PER_BATCH >= MAX_INITIAL_PER_BATCH, "MAX_MESSAGES_PER_BATCH must be >= MAX_INITIAL_PER_BATCH"); +#ifdef USE_BENCHMARK +class APIConnection; +void bench_enable_immediate_send(APIConnection *conn); +void bench_clear_batch(APIConnection *conn); +void bench_process_batch(APIConnection *conn); +#endif + class APIConnection final : public APIServerConnectionBase { public: friend class APIServer; friend class ListEntitiesIterator; +#ifdef USE_BENCHMARK + friend void bench_enable_immediate_send(APIConnection *conn); + friend void bench_clear_batch(APIConnection *conn); + friend void bench_process_batch(APIConnection *conn); +#endif APIConnection(std::unique_ptr socket, APIServer *parent); - virtual ~APIConnection(); + ~APIConnection(); void start(); void loop(); + protected: + // read_message_ is defined here (instead of in APIServerConnectionBase) so the + // compiler can devirtualize and inline on_* handler calls within this final class. + void read_message_(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data); + + // Auth helpers defined here (not in ProtoService) so the compiler can + // devirtualize is_connection_setup()/on_no_setup_connection() calls + // within this final class. + inline bool check_connection_setup_() { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return false; + } + return true; + } + inline bool check_authenticated_() { return this->check_connection_setup_(); } + + public: bool send_list_info_done() { return this->schedule_message_(nullptr, ListEntitiesDoneResponse::MESSAGE_TYPE, ListEntitiesDoneResponse::ESTIMATED_SIZE); @@ -63,72 +96,72 @@ class APIConnection final : public APIServerConnectionBase { #endif #ifdef USE_COVER bool send_cover_state(cover::Cover *cover); - void on_cover_command_request(const CoverCommandRequest &msg) override; + void on_cover_command_request(const CoverCommandRequest &msg); #endif #ifdef USE_FAN bool send_fan_state(fan::Fan *fan); - void on_fan_command_request(const FanCommandRequest &msg) override; + void on_fan_command_request(const FanCommandRequest &msg); #endif #ifdef USE_LIGHT bool send_light_state(light::LightState *light); - void on_light_command_request(const LightCommandRequest &msg) override; + void on_light_command_request(const LightCommandRequest &msg); #endif #ifdef USE_SENSOR bool send_sensor_state(sensor::Sensor *sensor); #endif #ifdef USE_SWITCH bool send_switch_state(switch_::Switch *a_switch); - void on_switch_command_request(const SwitchCommandRequest &msg) override; + void on_switch_command_request(const SwitchCommandRequest &msg); #endif #ifdef USE_TEXT_SENSOR bool send_text_sensor_state(text_sensor::TextSensor *text_sensor); #endif #ifdef USE_CAMERA void set_camera_state(std::shared_ptr image); - void on_camera_image_request(const CameraImageRequest &msg) override; + void on_camera_image_request(const CameraImageRequest &msg); #endif #ifdef USE_CLIMATE bool send_climate_state(climate::Climate *climate); - void on_climate_command_request(const ClimateCommandRequest &msg) override; + void on_climate_command_request(const ClimateCommandRequest &msg); #endif #ifdef USE_NUMBER bool send_number_state(number::Number *number); - void on_number_command_request(const NumberCommandRequest &msg) override; + void on_number_command_request(const NumberCommandRequest &msg); #endif #ifdef USE_DATETIME_DATE bool send_date_state(datetime::DateEntity *date); - void on_date_command_request(const DateCommandRequest &msg) override; + void on_date_command_request(const DateCommandRequest &msg); #endif #ifdef USE_DATETIME_TIME bool send_time_state(datetime::TimeEntity *time); - void on_time_command_request(const TimeCommandRequest &msg) override; + void on_time_command_request(const TimeCommandRequest &msg); #endif #ifdef USE_DATETIME_DATETIME bool send_datetime_state(datetime::DateTimeEntity *datetime); - void on_date_time_command_request(const DateTimeCommandRequest &msg) override; + void on_date_time_command_request(const DateTimeCommandRequest &msg); #endif #ifdef USE_TEXT bool send_text_state(text::Text *text); - void on_text_command_request(const TextCommandRequest &msg) override; + void on_text_command_request(const TextCommandRequest &msg); #endif #ifdef USE_SELECT bool send_select_state(select::Select *select); - void on_select_command_request(const SelectCommandRequest &msg) override; + void on_select_command_request(const SelectCommandRequest &msg); #endif #ifdef USE_BUTTON - void on_button_command_request(const ButtonCommandRequest &msg) override; + void on_button_command_request(const ButtonCommandRequest &msg); #endif #ifdef USE_LOCK bool send_lock_state(lock::Lock *a_lock); - void on_lock_command_request(const LockCommandRequest &msg) override; + void on_lock_command_request(const LockCommandRequest &msg); #endif #ifdef USE_VALVE bool send_valve_state(valve::Valve *valve); - void on_valve_command_request(const ValveCommandRequest &msg) override; + void on_valve_command_request(const ValveCommandRequest &msg); #endif #ifdef USE_MEDIA_PLAYER bool send_media_player_state(media_player::MediaPlayer *media_player); - void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override; + void on_media_player_command_request(const MediaPlayerCommandRequest &msg); #endif bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len); #ifdef USE_API_HOMEASSISTANT_SERVICES @@ -138,23 +171,23 @@ class APIConnection final : public APIServerConnectionBase { this->send_message(call); } #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES - void on_homeassistant_action_response(const HomeassistantActionResponse &msg) override; + void on_homeassistant_action_response(const HomeassistantActionResponse &msg); #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES #endif // USE_API_HOMEASSISTANT_SERVICES #ifdef USE_BLUETOOTH_PROXY - void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; - void on_unsubscribe_bluetooth_le_advertisements_request() override; + void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg); + void on_unsubscribe_bluetooth_le_advertisements_request(); - void on_bluetooth_device_request(const BluetoothDeviceRequest &msg) override; - void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) override; - void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) override; - void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) override; - void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) override; - void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) override; - void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) override; - void on_subscribe_bluetooth_connections_free_request() override; - void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) override; - void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &msg) override; + void on_bluetooth_device_request(const BluetoothDeviceRequest &msg); + void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg); + void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg); + void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg); + void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg); + void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg); + void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg); + void on_subscribe_bluetooth_connections_free_request(); + void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg); + void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &msg); #endif #ifdef USE_HOMEASSISTANT_TIME @@ -165,42 +198,42 @@ class APIConnection final : public APIServerConnectionBase { #endif #ifdef USE_VOICE_ASSISTANT - void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override; - void on_voice_assistant_response(const VoiceAssistantResponse &msg) override; - void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override; - void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override; - void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override; - void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) override; - void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) override; - void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override; + void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg); + void on_voice_assistant_response(const VoiceAssistantResponse &msg); + void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg); + void on_voice_assistant_audio(const VoiceAssistantAudio &msg); + void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg); + void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg); + void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg); + void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg); #endif #ifdef USE_ZWAVE_PROXY - void on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) override; - void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override; + void on_z_wave_proxy_frame(const ZWaveProxyFrame &msg); + void on_z_wave_proxy_request(const ZWaveProxyRequest &msg); #endif #ifdef USE_ALARM_CONTROL_PANEL bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel); - void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override; + void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg); #endif #ifdef USE_WATER_HEATER bool send_water_heater_state(water_heater::WaterHeater *water_heater); - void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override; + void on_water_heater_command_request(const WaterHeaterCommandRequest &msg); #endif #ifdef USE_IR_RF - void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) override; + void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg); void send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg); #endif #ifdef USE_SERIAL_PROXY - void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &msg) override; - void on_serial_proxy_write_request(const SerialProxyWriteRequest &msg) override; - void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &msg) override; - void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &msg) override; - void on_serial_proxy_request(const SerialProxyRequest &msg) override; + void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &msg); + void on_serial_proxy_write_request(const SerialProxyWriteRequest &msg); + void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &msg); + void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &msg); + void on_serial_proxy_request(const SerialProxyRequest &msg); void send_serial_proxy_data(const SerialProxyDataReceived &msg); #endif @@ -210,26 +243,26 @@ class APIConnection final : public APIServerConnectionBase { #ifdef USE_UPDATE bool send_update_state(update::UpdateEntity *update); - void on_update_command_request(const UpdateCommandRequest &msg) override; + void on_update_command_request(const UpdateCommandRequest &msg); #endif - void on_disconnect_response() override; - void on_ping_response() override { + void on_disconnect_response(); + void on_ping_response() { // we initiated ping this->flags_.sent_ping = false; } #ifdef USE_API_HOMEASSISTANT_STATES - void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override; + void on_home_assistant_state_response(const HomeAssistantStateResponse &msg); #endif #ifdef USE_HOMEASSISTANT_TIME - void on_get_time_response(const GetTimeResponse &value) override; + void on_get_time_response(const GetTimeResponse &value); #endif - void on_hello_request(const HelloRequest &msg) override; - void on_disconnect_request() override; - void on_ping_request() override; - void on_device_info_request() override; - void on_list_entities_request() override { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); } - void on_subscribe_states_request() override { + void on_hello_request(const HelloRequest &msg); + void on_disconnect_request(); + void on_ping_request(); + void on_device_info_request(); + void on_list_entities_request() { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); } + void on_subscribe_states_request() { this->flags_.state_subscription = true; // Start initial state iterator only if no iterator is active // If list_entities is running, we'll start initial_state when it completes @@ -237,25 +270,29 @@ class APIConnection final : public APIServerConnectionBase { this->begin_iterator_(ActiveIterator::INITIAL_STATE); } } - void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override { + void on_subscribe_logs_request(const SubscribeLogsRequest &msg) { this->flags_.log_subscription = msg.level; if (msg.dump_config) App.schedule_dump_config(); #ifdef USE_ESP32_CRASH_HANDLER esp32::crash_handler_log(); + esp32::crash_handler_clear(); #endif #ifdef USE_RP2040_CRASH_HANDLER rp2040::crash_handler_log(); +#endif +#ifdef USE_ESP8266_CRASH_HANDLER + esp8266::crash_handler_log(); #endif } #ifdef USE_API_HOMEASSISTANT_SERVICES - void on_subscribe_homeassistant_services_request() override { this->flags_.service_call_subscription = true; } + void on_subscribe_homeassistant_services_request() { this->flags_.service_call_subscription = true; } #endif #ifdef USE_API_HOMEASSISTANT_STATES - void on_subscribe_home_assistant_states_request() override; + void on_subscribe_home_assistant_states_request(); #endif #ifdef USE_API_USER_DEFINED_ACTIONS - void on_execute_service_request(const ExecuteServiceRequest &msg) override; + void on_execute_service_request(const ExecuteServiceRequest &msg); #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES void send_execute_service_response(uint32_t call_id, bool success, StringRef error_message); #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON @@ -265,13 +302,13 @@ class APIConnection final : public APIServerConnectionBase { #endif // USE_API_USER_DEFINED_ACTION_RESPONSES #endif #ifdef USE_API_NOISE - void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override; + void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg); #endif - bool is_authenticated() override { + bool is_authenticated() { return static_cast(this->flags_.connection_state) == ConnectionState::AUTHENTICATED; } - bool is_connection_setup() override { + bool is_connection_setup() { return static_cast(this->flags_.connection_state) == ConnectionState::CONNECTED || this->is_authenticated(); } @@ -284,11 +321,11 @@ class APIConnection final : public APIServerConnectionBase { (this->client_api_version_major_ == major && this->client_api_version_minor_ >= minor); } - void on_fatal_error() override; - void on_no_setup_connection() override; + void on_fatal_error(); + void on_no_setup_connection(); // Function pointer type for type-erased message encoding - using MessageEncodeFn = void (*)(const void *, ProtoWriteBuffer &); + using MessageEncodeFn = uint8_t *(*) (const void *, ProtoWriteBuffer &PROTO_ENCODE_DEBUG_PARAM); // Function pointer type for type-erased size calculation using CalculateSizeFn = uint32_t (*)(const void *); @@ -305,9 +342,9 @@ class APIConnection final : public APIServerConnectionBase { // Reserve space for header padding + message + footer // - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext) // - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext) - shared_buf.reserve(total_size); - // Resize to add header padding so message encoding starts at the correct position - shared_buf.resize(header_padding); + // Reserve full size but only set initial size to header padding + // so message encoding starts at the correct position + shared_buf.reserve_and_resize(total_size, header_padding); } // Convenience overload - computes frame overhead internally @@ -324,7 +361,7 @@ class APIConnection final : public APIServerConnectionBase { return true; return this->try_to_clear_buffer_slow_(log_out_of_space); } - bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override; + bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type); const char *get_name() const { return this->helper_->get_client_name(); } /// Get peer name (IP address) into caller-provided buffer, returns buf for convenience @@ -367,21 +404,66 @@ class APIConnection final : public APIServerConnectionBase { } // Shared no-op encode thunk for empty messages (ESTIMATED_SIZE == 0) - static void encode_msg_noop(const void *, ProtoWriteBuffer &) {} + static uint8_t *encode_msg_noop(const void *, ProtoWriteBuffer &buf PROTO_ENCODE_DEBUG_PARAM) { + return buf.get_pos(); + } // Non-template buffer management for send_message bool send_message_(uint32_t payload_size, uint8_t message_type, MessageEncodeFn encode_fn, const void *msg); - // Non-template buffer management for batch encoding - static uint16_t encode_to_buffer(uint32_t calculated_size, MessageEncodeFn encode_fn, const void *msg, - APIConnection *conn, uint32_t remaining_size); + // Core batch encoding logic. Computes header size, checks fit, resizes buffer, encodes. + // ALWAYS_INLINE so the compiler can devirtualize encode_fn at hot call sites. + static inline uint16_t ESPHOME_ALWAYS_INLINE encode_to_buffer(uint32_t calculated_size, MessageEncodeFn encode_fn, + const void *msg, APIConnection *conn, + uint32_t remaining_size) { +#ifdef HAS_PROTO_MESSAGE_DUMP + if (conn->flags_.log_only_mode) { + auto *proto_msg = static_cast(msg); + DumpBuffer dump_buf; + conn->log_send_message_(proto_msg->message_name(), proto_msg->dump_to(dump_buf)); + return 1; + } +#endif + const uint8_t footer_size = conn->helper_->frame_footer_size(); - // Thin template wrapper — computes size, delegates buffer work to non-template helper + // First message uses max padding (already in buffer), subsequent use exact header size + size_t to_add; + if (conn->flags_.batch_first_message) { + conn->flags_.batch_first_message = false; + conn->batch_header_size_ = conn->helper_->frame_header_padding(); + to_add = calculated_size; + } else { + conn->batch_header_size_ = conn->helper_->frame_header_size(calculated_size, conn->batch_message_type_); + to_add = calculated_size + conn->batch_header_size_ + footer_size; + } + + // Check if it fits (using actual header size, not max padding) + uint16_t total_calculated_size = calculated_size + conn->batch_header_size_ + footer_size; + if (total_calculated_size > remaining_size) + return 0; + + auto &shared_buf = conn->parent_->get_shared_buffer_ref(); + shared_buf.resize(shared_buf.size() + to_add); + ProtoWriteBuffer buffer{&shared_buf, shared_buf.size() - calculated_size}; + encode_fn(msg, buffer PROTO_ENCODE_DEBUG_INIT(&shared_buf)); + + return total_calculated_size; + } + + // Noinline version of encode_to_buffer for cold paths (entity info, zero-payload messages). + // All cold callers share this single copy instead of each getting an ALWAYS_INLINE expansion. + static uint16_t encode_to_buffer_slow(uint32_t calculated_size, MessageEncodeFn encode_fn, const void *msg, + APIConnection *conn, uint32_t remaining_size); + + // Thin template wrapper — uses noinline encode_to_buffer_slow since + // encode_message_to_buffer callers are cold paths (zero-payload control messages). + // Hot paths (state/info) go through fill_and_encode_entity_state/info instead. + // batch_message_type_ is already set by dispatch_message_ before reaching here. template static uint16_t encode_message_to_buffer(T &msg, APIConnection *conn, uint32_t remaining_size) { if constexpr (T::ESTIMATED_SIZE == 0) { - return encode_to_buffer(0, &encode_msg_noop, &msg, conn, remaining_size); + return encode_to_buffer_slow(0, &encode_msg_noop, &msg, conn, remaining_size); } else { - return encode_to_buffer(msg.calculate_size(), &proto_encode_msg, &msg, conn, remaining_size); + return encode_to_buffer_slow(msg.calculate_size(), &proto_encode_msg, &msg, conn, remaining_size); } } @@ -580,6 +662,7 @@ class APIConnection final : public APIServerConnectionBase { // Helper methods for iterator lifecycle management void destroy_active_iterator_(); void begin_iterator_(ActiveIterator type); + void finalize_iterator_sync_(); #ifdef USE_CAMERA std::unique_ptr image_reader_; #endif @@ -614,11 +697,28 @@ class APIConnection final : public APIServerConnectionBase { // Add item to the batch (with deduplication) void add_item(EntityBase *entity, uint8_t message_type, uint8_t estimated_size, - uint8_t aux_data_index = AUX_DATA_UNUSED); + uint8_t aux_data_index = AUX_DATA_UNUSED) { + // Dedup: O(n) scan but optimized for RAM over performance + // Skip deduplication for events - they are edge-triggered, every occurrence matters +#ifdef USE_EVENT + if (message_type != EventResponse::MESSAGE_TYPE) +#endif + { + for (const auto &item : this->items) { + if (item.entity == entity && item.message_type == message_type) + return; // Already queued + } + } + this->items.push_back({entity, message_type, estimated_size, aux_data_index}); + } // Add item to the front of the batch (for high priority messages like ping) - void add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size); - // Single push_back site to avoid duplicate _M_realloc_insert instantiation - void push_item(const BatchItem &item); + void add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) { + // Swap to front avoids expensive vector::insert which shifts all elements + this->items.push_back({entity, message_type, estimated_size, AUX_DATA_UNUSED}); + if (this->items.size() > 1) { + std::swap(this->items.front(), this->items.back()); + } + } // Clear all items void clear() { @@ -679,11 +779,16 @@ class APIConnection final : public APIServerConnectionBase { // 2-byte types immediately after flags_ (no padding between them) uint16_t client_api_version_major_{0}; uint16_t client_api_version_minor_{0}; - // 1-byte type to fill padding + // 1-byte types to fill remaining space before next 4-byte boundary ActiveIterator active_iterator_{ActiveIterator::NONE}; - // Total: 2 (flags) + 2 + 2 + 1 = 7 bytes, then 1 byte padding to next 4-byte boundary + uint8_t batch_message_type_{0}; // Current message type during batch encoding + // Total: 2 (flags) + 2 + 2 + 1 + 1 = 8 bytes, aligned to 4-byte boundary - uint32_t get_batch_delay_ms_() const; + // Actual header size used by encode_to_buffer for the current message. + // Read by process_batch_multi_ to pass into MessageInfo. + uint8_t batch_header_size_{0}; + + uint32_t get_batch_delay_ms_() const { return this->parent_->get_batch_delay(); } // Message will use 8 more bytes than the minimum size, and typical // MTU is 1500. Sometimes users will see as low as 1460 MTU. // If its IPv6 the header is 40 bytes, and if its IPv4 @@ -750,10 +855,8 @@ class APIConnection final : public APIServerConnectionBase { } // Helper function to schedule a high priority message at the front of the batch - bool schedule_message_front_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) { - this->deferred_batch_.add_item_front(entity, message_type, estimated_size); - return this->schedule_batch_(); - } + // Out-of-line: callers (on_shutdown, check_keepalive_) are cold paths + bool schedule_message_front_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size); // Helper function to log client messages with name and peername void log_client_(int level, const LogString *message); diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index e432a976b0..90353b6402 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -100,149 +100,81 @@ const LogString *api_error_to_logstr(APIError err) { return LOG_STR("UNKNOWN"); } -// Default implementation for loop - handles sending buffered data -APIError APIFrameHelper::loop() { - if (this->tx_buf_count_ > 0) { - APIError err = try_send_tx_buf_(); - if (err != APIError::OK && err != APIError::WOULD_BLOCK) { - return err; - } - } - return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination -} - -// Common socket write error handling -APIError APIFrameHelper::handle_socket_write_error_() { - if (errno == EWOULDBLOCK || errno == EAGAIN) { - return APIError::WOULD_BLOCK; - } - HELPER_LOG("Socket write failed with errno %d", errno); - this->state_ = State::FAILED; - return APIError::SOCKET_WRITE_FAILED; -} - -// Helper method to buffer data from IOVs -void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, - uint16_t offset) { - // Check if queue is full - if (this->tx_buf_count_ >= API_MAX_SEND_QUEUE) { - HELPER_LOG("Send queue full (%u buffers), dropping connection", this->tx_buf_count_); - this->state_ = State::FAILED; - return; - } - - uint16_t buffer_size = total_write_len - offset; - auto &buffer = this->tx_buf_[this->tx_buf_tail_]; - buffer = std::make_unique(SendBuffer{ - .data = std::make_unique(buffer_size), - .size = buffer_size, - .offset = 0, - }); - - uint16_t to_skip = offset; - uint16_t write_pos = 0; - - for (int i = 0; i < iovcnt; i++) { - if (to_skip >= iov[i].iov_len) { - // Skip this entire segment - to_skip -= static_cast(iov[i].iov_len); - } else { - // Include this segment (partially or fully) - const uint8_t *src = reinterpret_cast(iov[i].iov_base) + to_skip; - uint16_t len = static_cast(iov[i].iov_len) - to_skip; - std::memcpy(buffer->data.get() + write_pos, src, len); - write_pos += len; - to_skip = 0; - } - } - - // Update circular buffer tracking - this->tx_buf_tail_ = (this->tx_buf_tail_ + 1) % API_MAX_SEND_QUEUE; - this->tx_buf_count_++; -} - -// This method writes data to socket or buffers it -APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) { - // Returns APIError::OK if successful (or would block, but data has been buffered) - // Returns APIError::SOCKET_WRITE_FAILED if socket write failed, and sets state to FAILED - - if (iovcnt == 0) - return APIError::OK; // Nothing to do, success - #ifdef HELPER_LOG_PACKETS - for (int i = 0; i < iovcnt; i++) { - LOG_PACKET_SENDING(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); - } +void APIFrameHelper::log_packet_sending_(const void *data, uint16_t len) { + LOG_PACKET_SENDING(reinterpret_cast(data), len); +} #endif - // Try to send any existing buffered data first if there is any - if (this->tx_buf_count_ > 0) { - APIError send_result = try_send_tx_buf_(); - // If real error occurred (not just WOULD_BLOCK), return it - if (send_result != APIError::OK && send_result != APIError::WOULD_BLOCK) { - return send_result; - } - - // If there is still data in the buffer, we can't send, buffer - // the new data and return - if (this->tx_buf_count_ > 0) { - this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0); - return APIError::OK; // Success, data buffered +APIError APIFrameHelper::drain_overflow_and_handle_errors_() { + if (this->overflow_buf_.try_drain(this->socket_.get()) == -1) { + int err = errno; + if (err != EWOULDBLOCK && err != EAGAIN) { + this->state_ = State::FAILED; + HELPER_LOG("Socket write failed with errno %d", err); + return APIError::SOCKET_WRITE_FAILED; } } - - // Try to send directly if no buffered data - // Optimize for single iovec case (common for plaintext API) - ssize_t sent = - (iovcnt == 1) ? this->socket_->write(iov[0].iov_base, iov[0].iov_len) : this->socket_->writev(iov, iovcnt); - - if (sent == -1) { - APIError err = this->handle_socket_write_error_(); - if (err == APIError::WOULD_BLOCK) { - // Socket would block, buffer the data - this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0); - return APIError::OK; // Success, data buffered - } - return err; // Socket write failed - } else if (static_cast(sent) < total_write_len) { - // Partially sent, buffer the remaining data - this->buffer_data_from_iov_(iov, iovcnt, total_write_len, static_cast(sent)); - } - - return APIError::OK; // Success, all data sent or buffered + return APIError::OK; } -// Common implementation for trying to send buffered data -// IMPORTANT: Caller MUST ensure tx_buf_count_ > 0 before calling this method -APIError APIFrameHelper::try_send_tx_buf_() { - // Try to send from tx_buf - we assume it's not empty as it's the caller's responsibility to check - while (this->tx_buf_count_ > 0) { - // Get the first buffer in the queue - SendBuffer *front_buffer = this->tx_buf_[this->tx_buf_head_].get(); +// Single-buffer write path: wraps in iovec and delegates. +APIError APIFrameHelper::write_raw_buf_(const void *data, uint16_t len, ssize_t sent) { + struct iovec iov = {const_cast(data), len}; + APIError err = this->write_raw_iov_(&iov, 1, len, sent); +#ifdef HELPER_LOG_PACKETS + // Log after write/enqueue so re-entrant log sends can't corrupt data before it's sent + if (err == APIError::OK) + LOG_PACKET_SENDING(reinterpret_cast(data), len); +#endif + return err; +} - // Try to send the remaining data in this buffer - ssize_t sent = this->socket_->write(front_buffer->current_data(), front_buffer->remaining()); - - if (sent == -1) { - return this->handle_socket_write_error_(); - } else if (sent == 0) { - // Nothing sent but not an error - return APIError::WOULD_BLOCK; - } else if (static_cast(sent) < front_buffer->remaining()) { - // Partially sent, update offset - // Cast to ensure no overflow issues with uint16_t - front_buffer->offset += static_cast(sent); - return APIError::WOULD_BLOCK; // Stop processing more buffers if we couldn't send a complete buffer - } else { - // Buffer completely sent, remove it from the queue - this->tx_buf_[this->tx_buf_head_].reset(); - this->tx_buf_head_ = (this->tx_buf_head_ + 1) % API_MAX_SEND_QUEUE; - this->tx_buf_count_--; - // Continue loop to try sending the next buffer +// Handles partial writes, errors, and overflow buffering. +// Called when the inline fast path couldn't complete the write, +// or directly from cold paths (handshake, error handling). +APIError APIFrameHelper::write_raw_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, ssize_t sent) { + if (sent <= 0) { + if (sent == WRITE_NOT_ATTEMPTED) { + // Cold path: no write attempted yet, drain overflow and try + if (!this->overflow_buf_.empty()) { + APIError err = this->drain_overflow_and_handle_errors_(); + if (err != APIError::OK) + return err; + } + if (this->overflow_buf_.empty()) { + sent = this->write_iov_to_socket_(iov, iovcnt); + if (sent == static_cast(total_write_len)) + return APIError::OK; + // Partial write or -1: fall through to error check / enqueue below + } else { + // Overflow backlog remains after drain; skip socket write, enqueue everything + sent = 0; + } + } + // WRITE_FAILED (-1): fast path or retry write returned -1, check errno + if (sent == WRITE_FAILED) { + int err = errno; + if (err != EWOULDBLOCK && err != EAGAIN) { + this->state_ = State::FAILED; + HELPER_LOG("Socket write failed with errno %d", err); + return APIError::SOCKET_WRITE_FAILED; + } + sent = 0; // Treat WOULD_BLOCK as zero bytes sent } } - return APIError::OK; // All buffers sent successfully + // Full write completed (possible when called directly, not via write_raw_fast_buf_) + if (sent == static_cast(total_write_len)) + return APIError::OK; + + // Queue unsent data into overflow buffer + if (!this->overflow_buf_.enqueue_iov(iov, iovcnt, total_write_len, static_cast(sent))) { + HELPER_LOG("Overflow buffer full, dropping connection"); + this->state_ = State::FAILED; + return APIError::SOCKET_WRITE_FAILED; + } + return APIError::OK; } const char *APIFrameHelper::get_peername_to(std::span buf) const { @@ -278,11 +210,12 @@ APIError APIFrameHelper::init_common_() { APIError APIFrameHelper::handle_socket_read_result_(ssize_t received) { if (received == -1) { - if (errno == EWOULDBLOCK || errno == EAGAIN) { + const int err = errno; + if (err == EWOULDBLOCK || err == EAGAIN) { return APIError::WOULD_BLOCK; } state_ = State::FAILED; - HELPER_LOG("Socket read failed with errno %d", errno); + HELPER_LOG("Socket read failed with errno %d", err); return APIError::SOCKET_READ_FAILED; } else if (received == 0) { state_ = State::FAILED; diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 5e07ad43a9..d1215388d2 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -9,9 +9,11 @@ #include "esphome/core/defines.h" #ifdef USE_API #include "esphome/components/api/api_buffer.h" +#include "esphome/components/api/api_overflow_buffer.h" #include "esphome/components/socket/socket.h" #include "esphome/core/application.h" #include "esphome/core/log.h" +#include "proto.h" namespace esphome::api { @@ -37,8 +39,6 @@ static constexpr uint16_t RX_BUF_NULL_TERMINATOR = 1; // Must be >= MAX_INITIAL_PER_BATCH in api_connection.h (enforced by static_assert there) static constexpr size_t MAX_MESSAGES_PER_BATCH = 34; -class ProtoWriteBuffer; - // Max client name length (e.g., "Home Assistant 2026.1.0.dev0" = 28 chars) static constexpr size_t CLIENT_INFO_NAME_MAX_LEN = 32; @@ -49,12 +49,17 @@ struct ReadPacketBuffer { }; // Packed message info structure to minimize memory usage +// Note: message_type is uint8_t — all current protobuf message types fit in 8 bits. +// The noise wire format encodes types as 16-bit, but the high byte is always 0. +// If message types ever exceed 255, this and encrypt_noise_message_ must be updated. struct MessageInfo { uint16_t offset; // Offset in buffer where message starts uint16_t payload_size; // Size of the message payload uint8_t message_type; // Message type (0-255) + uint8_t header_size; // Actual header size used (avoids recomputation in write path) - MessageInfo(uint8_t type, uint16_t off, uint16_t size) : offset(off), payload_size(size), message_type(type) {} + MessageInfo(uint8_t type, uint16_t off, uint16_t size, uint8_t hdr) + : offset(off), payload_size(size), message_type(type), header_size(hdr) {} }; enum class APIError : uint16_t { @@ -105,9 +110,9 @@ class APIFrameHelper { } virtual ~APIFrameHelper() = default; virtual APIError init() = 0; - virtual APIError loop(); + virtual APIError loop() = 0; virtual APIError read_packet(ReadPacketBuffer *buffer) = 0; - bool can_write_without_blocking() { return this->state_ == State::DATA && this->tx_buf_count_ == 0; } + bool can_write_without_blocking() { return this->state_ == State::DATA && this->overflow_buf_.empty(); } int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); } APIError close() { if (state_ == State::CLOSED) @@ -147,31 +152,47 @@ class APIFrameHelper { // void set_nodelay_for_message(bool is_log_message) { if (!is_log_message) { - if (this->nodelay_state_ != NODELAY_ON) { + if (this->nodelay_counter_) { this->set_nodelay_raw_(true); - this->nodelay_state_ = NODELAY_ON; + this->nodelay_counter_ = 0; } return; } - - // Log messages: state transitions -1 -> 1 -> ... -> LOG_NAGLE_COUNT -> -1 (flush) - if (this->nodelay_state_ == NODELAY_ON) { + // Log message: enable Nagle on first, flush after LOG_NAGLE_COUNT + if (!this->nodelay_counter_) this->set_nodelay_raw_(false); - this->nodelay_state_ = 1; - } else if (this->nodelay_state_ >= LOG_NAGLE_COUNT) { + if (++this->nodelay_counter_ > LOG_NAGLE_COUNT) { this->set_nodelay_raw_(true); - this->nodelay_state_ = NODELAY_ON; - } else { - this->nodelay_state_++; + this->nodelay_counter_ = 0; } } + // Write a single protobuf message - the hot path (87-100% of all writes). + // Caller must ensure state is DATA before calling. virtual APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) = 0; - // Write multiple protobuf messages in a single operation - // messages contains (message_type, offset, length) for each message in the buffer - // The buffer contains all messages with appropriate padding before each + // Write multiple protobuf messages in a single batched operation. + // Caller must ensure state is DATA and messages is not empty. + // messages contains (message_type, offset, length) for each message in the buffer. + // The buffer contains all messages with appropriate padding before each. virtual APIError write_protobuf_messages(ProtoWriteBuffer buffer, std::span messages) = 0; - // Get the frame header padding required by this protocol + // Get the maximum frame header padding required by this protocol (worst case) uint8_t frame_header_padding() const { return frame_header_padding_; } + // Get the actual frame header size for a specific message. + // For noise: always returns frame_header_padding_ (fixed 7-byte header). + // For plaintext: computes actual size from varint lengths (3-6 bytes). + // Distinguishes protocols via frame_footer_size_ (noise always has a non-zero MAC + // footer, plaintext has footer=0). If a protocol with a plaintext footer is ever + // added, this should become a virtual method. + uint8_t frame_header_size(uint16_t payload_size, uint8_t message_type) const { +#if defined(USE_API_NOISE) && defined(USE_API_PLAINTEXT) + return this->frame_footer_size_ + ? this->frame_header_padding_ + : static_cast(1 + ProtoSize::varint16(payload_size) + ProtoSize::varint8(message_type)); +#elif defined(USE_API_NOISE) + return this->frame_header_padding_; +#else // USE_API_PLAINTEXT only + return static_cast(1 + ProtoSize::varint16(payload_size) + ProtoSize::varint8(message_type)); +#endif + } // Get the frame footer size required by this protocol uint8_t frame_footer_size() const { return frame_footer_size_; } // Check if socket has data ready to read @@ -187,28 +208,46 @@ class APIFrameHelper { } protected: - // Buffer containing data to be sent - struct SendBuffer { - std::unique_ptr data; - uint16_t size{0}; // Total size of the buffer - uint16_t offset{0}; // Current offset within the buffer + // Drain backlogged overflow data to the socket and handle errors. + // Called when overflow_buf_.empty() is false. Out-of-line to keep the + // fast path (empty check) inline at call sites. + // Returns OK for transient errors (WOULD_BLOCK), SOCKET_WRITE_FAILED for hard errors. + APIError drain_overflow_and_handle_errors_(); - // Using uint16_t reduces memory usage since ESPHome API messages are limited to UINT16_MAX (65535) bytes - uint16_t remaining() const { return size - offset; } - const uint8_t *current_data() const { return data.get() + offset; } - }; + // Sentinel values for the sent parameter in write_raw_ methods + static constexpr ssize_t WRITE_FAILED = -1; // Fast path: write()/writev() returned -1 + static constexpr ssize_t WRITE_NOT_ATTEMPTED = -2; // Cold path: no write attempted yet - // Common implementation for writing raw data to socket - APIError write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len); + // Dispatch to write() or writev() based on iovec count + inline ssize_t ESPHOME_ALWAYS_INLINE write_iov_to_socket_(const struct iovec *iov, int iovcnt) { + return (iovcnt == 1) ? this->socket_->write(iov[0].iov_base, iov[0].iov_len) : this->socket_->writev(iov, iovcnt); + } - // Try to send data from the tx buffer - APIError try_send_tx_buf_(); - - // Helper method to buffer data from IOVs - void buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, uint16_t offset); - - // Common socket write error handling - APIError handle_socket_write_error_(); + // Inlined write methods — used by hot paths (write_protobuf_packet, write_protobuf_messages) + // These inline the fast path (overflow empty + full write) and tail-call the out-of-line + // slow path only on failure/partial write. + inline APIError ESPHOME_ALWAYS_INLINE write_raw_fast_buf_(const void *data, uint16_t len) { + if (this->overflow_buf_.empty()) [[likely]] { + ssize_t sent = this->socket_->write(data, len); + if (sent == static_cast(len)) [[likely]] { +#ifdef HELPER_LOG_PACKETS + this->log_packet_sending_(data, len); +#endif + return APIError::OK; + } + // sent is -1 (WRITE_FAILED) or partial write count + return this->write_raw_buf_(data, len, sent); + } + return this->write_raw_buf_(data, len, WRITE_NOT_ATTEMPTED); + } + // Out-of-line write paths: handle partial writes, errors, overflow buffering + // sent: WRITE_NOT_ATTEMPTED (cold path), WRITE_FAILED (fast path write returned -1), or bytes sent (partial write) + APIError write_raw_buf_(const void *data, uint16_t len, ssize_t sent = WRITE_NOT_ATTEMPTED); + APIError write_raw_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, + ssize_t sent = WRITE_NOT_ATTEMPTED); +#ifdef HELPER_LOG_PACKETS + void log_packet_sending_(const void *data, uint16_t len); +#endif // Socket ownership (4 bytes on 32-bit, 8 bytes on 64-bit) std::unique_ptr socket_; @@ -243,8 +282,8 @@ class APIFrameHelper { return APIError::WOULD_BLOCK; } - // Containers (size varies, but typically 12+ bytes on 32-bit) - std::array, API_MAX_SEND_QUEUE> tx_buf_; + // Backlog for unsent data when TCP send buffer is full (rarely used in production) + APIOverflowBuffer overflow_buf_; APIBuffer rx_buf_; // Client name buffer - stores name from Hello message or initial peername @@ -255,21 +294,17 @@ class APIFrameHelper { State state_{State::INITIALIZE}; uint8_t frame_header_padding_{0}; uint8_t frame_footer_size_{0}; - uint8_t tx_buf_head_{0}; - uint8_t tx_buf_tail_{0}; - uint8_t tx_buf_count_{0}; - // Nagle batching state for log messages. NODELAY_ON (-1) means NODELAY is enabled - // (immediate send). Values 1..LOG_NAGLE_COUNT count log messages in the current Nagle batch. - // After LOG_NAGLE_COUNT logs, we switch to NODELAY to flush and reset. + // Nagle batching counter for log messages. 0 means NODELAY is enabled (immediate send). + // Values 1..LOG_NAGLE_COUNT count log messages in the current Nagle batch. + // After LOG_NAGLE_COUNT logs, we flush by re-enabling NODELAY and resetting to 0. // ESP8266 has the tightest TCP send buffer (2×MSS) and needs conservative batching. // ESP32 (4×MSS+), RP2040 (8×MSS), and LibreTiny (4×MSS) can coalesce more. - static constexpr int8_t NODELAY_ON = -1; #ifdef USE_ESP8266 - static constexpr int8_t LOG_NAGLE_COUNT = 2; + static constexpr uint8_t LOG_NAGLE_COUNT = 2; #else - static constexpr int8_t LOG_NAGLE_COUNT = 3; + static constexpr uint8_t LOG_NAGLE_COUNT = 3; #endif - int8_t nodelay_state_{NODELAY_ON}; + uint8_t nodelay_counter_{0}; // Internal helper to set TCP_NODELAY socket option void set_nodelay_raw_(bool enable) { diff --git a/esphome/components/api/api_frame_helper_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index f945253c89..6dba64a7f8 100644 --- a/esphome/components/api/api_frame_helper_noise.cpp +++ b/esphome/components/api/api_frame_helper_noise.cpp @@ -47,15 +47,8 @@ static constexpr size_t API_MAX_LOG_BYTES = 168; format_hex_pretty_to(hex_buf_, (buffer).data(), \ (buffer).size() < API_MAX_LOG_BYTES ? (buffer).size() : API_MAX_LOG_BYTES)); \ } while (0) -#define LOG_PACKET_SENDING(data, len) \ - do { \ - char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \ - ESP_LOGVV(TAG, "Sending raw: %s", \ - format_hex_pretty_to(hex_buf_, data, (len) < API_MAX_LOG_BYTES ? (len) : API_MAX_LOG_BYTES)); \ - } while (0) #else #define LOG_PACKET_RECEIVED(buffer) ((void) 0) -#define LOG_PACKET_SENDING(data, len) ((void) 0) #endif /// Convert a noise error code to a readable error @@ -153,8 +146,10 @@ APIError APINoiseFrameHelper::loop() { } } - // Use base class implementation for buffer sending - return APIFrameHelper::loop(); + if (!this->overflow_buf_.empty()) [[unlikely]] { + return this->drain_overflow_and_handle_errors_(); + } + return APIError::OK; } /** Read a packet into the rx_buf_. @@ -242,132 +237,144 @@ APIError APINoiseFrameHelper::try_read_frame_() { * If an error occurred, returns that error. Only returns OK if the transport is ready for data * traffic. */ +// Split into per-state methods so the compiler doesn't allocate stack space +// for all branches simultaneously. On RP2040 the core0 stack lives in a 4KB +// scratch RAM bank; the Noise crypto path (curve25519) needs ~2KB+ of stack, +// so every byte saved in the caller matters. APIError APINoiseFrameHelper::state_action_() { - int err; - APIError aerr; - if (state_ == State::INITIALIZE) { - HELPER_LOG("Bad state for method: %d", (int) state_); - return APIError::BAD_STATE; + switch (this->state_) { + case State::INITIALIZE: + HELPER_LOG("Bad state for method: %d", (int) this->state_); + return APIError::BAD_STATE; + case State::CLIENT_HELLO: + return this->state_action_client_hello_(); + case State::SERVER_HELLO: + return this->state_action_server_hello_(); + case State::HANDSHAKE: + return this->state_action_handshake_(); + case State::CLOSED: + case State::FAILED: + return APIError::BAD_STATE; + default: + return APIError::OK; } - if (state_ == State::CLIENT_HELLO) { - // waiting for client hello - aerr = this->try_read_frame_(); - if (aerr != APIError::OK) { - return handle_handshake_frame_error_(aerr); - } - // ignore contents, may be used in future for flags - // Resize for: existing prologue + 2 size bytes + frame data - size_t old_size = this->prologue_.size(); - size_t rx_size = this->rx_buf_.size(); - this->prologue_.resize(old_size + 2 + rx_size); - this->prologue_[old_size] = (uint8_t) (rx_size >> 8); - this->prologue_[old_size + 1] = (uint8_t) rx_size; - if (rx_size > 0) { - std::memcpy(this->prologue_.data() + old_size + 2, this->rx_buf_.data(), rx_size); - } - - state_ = State::SERVER_HELLO; +} +APIError APINoiseFrameHelper::state_action_client_hello_() { + // waiting for client hello + APIError aerr = this->try_read_frame_(); + if (aerr != APIError::OK) { + return handle_handshake_frame_error_(aerr); } - if (state_ == State::SERVER_HELLO) { - // send server hello - const auto &name = App.get_name(); - char mac[MAC_ADDRESS_BUFFER_SIZE]; - get_mac_address_into_buffer(mac); - - // Calculate positions and sizes - size_t name_len = name.size() + 1; // including null terminator - size_t name_offset = 1; - size_t mac_offset = name_offset + name_len; - size_t total_size = 1 + name_len + MAC_ADDRESS_BUFFER_SIZE; - - // 1 (proto) + name (max ESPHOME_DEVICE_NAME_MAX_LEN) + 1 (name null) - // + mac (MAC_ADDRESS_BUFFER_SIZE - 1) + 1 (mac null) - constexpr size_t max_msg_size = 1 + ESPHOME_DEVICE_NAME_MAX_LEN + 1 + MAC_ADDRESS_BUFFER_SIZE; - uint8_t msg[max_msg_size]; - - // chosen proto - msg[0] = 0x01; - - // node name, terminated by null byte - std::memcpy(msg + name_offset, name.c_str(), name_len); - // node mac, terminated by null byte - std::memcpy(msg + mac_offset, mac, MAC_ADDRESS_BUFFER_SIZE); - - aerr = write_frame_(msg, total_size); - if (aerr != APIError::OK) - return aerr; - - // start handshake - aerr = init_handshake_(); - if (aerr != APIError::OK) - return aerr; - - state_ = State::HANDSHAKE; + // ignore contents, may be used in future for flags + // Resize for: existing prologue + 2 size bytes + frame data + size_t old_size = this->prologue_.size(); + size_t rx_size = this->rx_buf_.size(); + this->prologue_.resize(old_size + 2 + rx_size); + this->prologue_[old_size] = (uint8_t) (rx_size >> 8); + this->prologue_[old_size + 1] = (uint8_t) rx_size; + if (rx_size > 0) { + std::memcpy(this->prologue_.data() + old_size + 2, this->rx_buf_.data(), rx_size); } - if (state_ == State::HANDSHAKE) { - int action = noise_handshakestate_get_action(handshake_); - if (action == NOISE_ACTION_READ_MESSAGE) { - // waiting for handshake msg - aerr = this->try_read_frame_(); - if (aerr != APIError::OK) { - return handle_handshake_frame_error_(aerr); - } - if (this->rx_buf_.empty()) { - send_explicit_handshake_reject_(LOG_STR("Empty handshake message")); - return APIError::BAD_HANDSHAKE_ERROR_BYTE; - } else if (this->rx_buf_[0] != 0x00) { - HELPER_LOG("Bad handshake error byte: %u", this->rx_buf_[0]); - send_explicit_handshake_reject_(LOG_STR("Bad handshake error byte")); - return APIError::BAD_HANDSHAKE_ERROR_BYTE; - } - - NoiseBuffer mbuf; - noise_buffer_init(mbuf); - noise_buffer_set_input(mbuf, this->rx_buf_.data() + 1, this->rx_buf_.size() - 1); - err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr); - if (err != 0) { - // Special handling for MAC failure - send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? LOG_STR("Handshake MAC failure") - : LOG_STR("Handshake error")); - return handle_noise_error_(err, LOG_STR("noise_handshakestate_read_message"), - APIError::HANDSHAKESTATE_READ_FAILED); - } - - aerr = check_handshake_finished_(); - if (aerr != APIError::OK) - return aerr; - } else if (action == NOISE_ACTION_WRITE_MESSAGE) { - uint8_t buffer[65]; - NoiseBuffer mbuf; - noise_buffer_init(mbuf); - noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1); - - err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr); - APIError aerr_write = handle_noise_error_(err, LOG_STR("noise_handshakestate_write_message"), - APIError::HANDSHAKESTATE_WRITE_FAILED); - if (aerr_write != APIError::OK) - return aerr_write; - buffer[0] = 0x00; // success - - aerr = write_frame_(buffer, mbuf.size + 1); - if (aerr != APIError::OK) - return aerr; - aerr = check_handshake_finished_(); - if (aerr != APIError::OK) - return aerr; - } else { - // bad state for action - state_ = State::FAILED; - HELPER_LOG("Bad action for handshake: %d", action); - return APIError::HANDSHAKESTATE_BAD_STATE; - } - } - if (state_ == State::CLOSED || state_ == State::FAILED) { - return APIError::BAD_STATE; - } + state_ = State::SERVER_HELLO; return APIError::OK; } +APIError APINoiseFrameHelper::state_action_server_hello_() { + // send server hello + const auto &name = App.get_name(); + char mac[MAC_ADDRESS_BUFFER_SIZE]; + get_mac_address_into_buffer(mac); + + // Calculate positions and sizes + size_t name_len = name.size() + 1; // including null terminator + size_t name_offset = 1; + size_t mac_offset = name_offset + name_len; + size_t total_size = 1 + name_len + MAC_ADDRESS_BUFFER_SIZE; + + // 1 (proto) + name (max ESPHOME_DEVICE_NAME_MAX_LEN) + 1 (name null) + // + mac (MAC_ADDRESS_BUFFER_SIZE - 1) + 1 (mac null) + constexpr size_t max_msg_size = 1 + ESPHOME_DEVICE_NAME_MAX_LEN + 1 + MAC_ADDRESS_BUFFER_SIZE; + uint8_t msg[max_msg_size]; + + // chosen proto + msg[0] = 0x01; + + // node name, terminated by null byte + std::memcpy(msg + name_offset, name.c_str(), name_len); + // node mac, terminated by null byte + std::memcpy(msg + mac_offset, mac, MAC_ADDRESS_BUFFER_SIZE); + + APIError aerr = write_frame_(msg, total_size); + if (aerr != APIError::OK) + return aerr; + + // start handshake + aerr = init_handshake_(); + if (aerr != APIError::OK) + return aerr; + + state_ = State::HANDSHAKE; + return APIError::OK; +} +APIError APINoiseFrameHelper::state_action_handshake_() { + int action = noise_handshakestate_get_action(this->handshake_); + if (action == NOISE_ACTION_READ_MESSAGE) { + return this->state_action_handshake_read_(); + } else if (action == NOISE_ACTION_WRITE_MESSAGE) { + return this->state_action_handshake_write_(); + } + // bad state for action + this->state_ = State::FAILED; + HELPER_LOG("Bad action for handshake: %d", action); + return APIError::HANDSHAKESTATE_BAD_STATE; +} +APIError APINoiseFrameHelper::state_action_handshake_read_() { + APIError aerr = this->try_read_frame_(); + if (aerr != APIError::OK) { + return this->handle_handshake_frame_error_(aerr); + } + + if (this->rx_buf_.empty()) { + this->send_explicit_handshake_reject_(LOG_STR("Empty handshake message")); + return APIError::BAD_HANDSHAKE_ERROR_BYTE; + } else if (this->rx_buf_[0] != 0x00) { + HELPER_LOG("Bad handshake error byte: %u", this->rx_buf_[0]); + this->send_explicit_handshake_reject_(LOG_STR("Bad handshake error byte")); + return APIError::BAD_HANDSHAKE_ERROR_BYTE; + } + + NoiseBuffer mbuf; + noise_buffer_init(mbuf); + noise_buffer_set_input(mbuf, this->rx_buf_.data() + 1, this->rx_buf_.size() - 1); + int err = noise_handshakestate_read_message(this->handshake_, &mbuf, nullptr); + if (err != 0) { + // Special handling for MAC failure + this->send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? LOG_STR("Handshake MAC failure") + : LOG_STR("Handshake error")); + return this->handle_noise_error_(err, LOG_STR("noise_handshakestate_read_message"), + APIError::HANDSHAKESTATE_READ_FAILED); + } + + return this->check_handshake_finished_(); +} +APIError APINoiseFrameHelper::state_action_handshake_write_() { + uint8_t buffer[65]; + NoiseBuffer mbuf; + noise_buffer_init(mbuf); + noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1); + + int err = noise_handshakestate_write_message(this->handshake_, &mbuf, nullptr); + APIError aerr = this->handle_noise_error_(err, LOG_STR("noise_handshakestate_write_message"), + APIError::HANDSHAKESTATE_WRITE_FAILED); + if (aerr != APIError::OK) + return aerr; + buffer[0] = 0x00; // success + + aerr = this->write_frame_(buffer, mbuf.size + 1); + if (aerr != APIError::OK) + return aerr; + return this->check_handshake_finished_(); +} void APINoiseFrameHelper::send_explicit_handshake_reject_(const LogString *reason) { // Max reject message: "Bad handshake packet len" (24) + 1 (failure byte) = 25 bytes uint8_t data[32]; @@ -450,73 +457,83 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) { buffer->type = type; return APIError::OK; } -APIError APINoiseFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) { - // Resize to include MAC space (required for Noise encryption) - buffer.get_buffer()->resize(buffer.get_buffer()->size() + frame_footer_size_); - MessageInfo msg{type, 0, - static_cast(buffer.get_buffer()->size() - frame_header_padding_ - frame_footer_size_)}; - return write_protobuf_messages(buffer, std::span(&msg, 1)); -} +// Encrypt a single noise message in place and return the encrypted frame length. +// Returns APIError::OK on success. +APIError APINoiseFrameHelper::encrypt_noise_message_(uint8_t *buf_start, uint16_t payload_size, uint8_t message_type, + uint16_t &encrypted_len_out) { + // Write noise header + buf_start[0] = 0x01; // indicator + // buf_start[1], buf_start[2] to be set after encryption -APIError APINoiseFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer, std::span messages) { - APIError aerr = this->check_data_state_(); + // Write message header (to be encrypted) + constexpr uint8_t msg_offset = 3; + buf_start[msg_offset] = static_cast(message_type >> 8); // type high byte + buf_start[msg_offset + 1] = static_cast(message_type); // type low byte + buf_start[msg_offset + 2] = static_cast(payload_size >> 8); // data_len high byte + buf_start[msg_offset + 3] = static_cast(payload_size); // data_len low byte + // payload data is already in the buffer starting at offset + 7 + + // Encrypt the message in place + NoiseBuffer mbuf; + noise_buffer_init(mbuf); + noise_buffer_set_inout(mbuf, buf_start + msg_offset, 4 + payload_size, 4 + payload_size + this->frame_footer_size_); + + int err = noise_cipherstate_encrypt(this->send_cipher_, &mbuf); + APIError aerr = + this->handle_noise_error_(err, LOG_STR("noise_cipherstate_encrypt"), APIError::CIPHERSTATE_ENCRYPT_FAILED); if (aerr != APIError::OK) return aerr; - if (messages.empty()) { - return APIError::OK; - } + // Fill in the encrypted size + buf_start[1] = static_cast(mbuf.size >> 8); + buf_start[2] = static_cast(mbuf.size); + encrypted_len_out = static_cast(3 + mbuf.size); // indicator + size + encrypted data + return APIError::OK; +} + +APIError APINoiseFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) { +#ifdef ESPHOME_DEBUG_API + assert(this->state_ == State::DATA); +#endif + + // Resize buffer to include footer space for Noise MAC + if (this->frame_footer_size_) + buffer.get_buffer()->resize(buffer.get_buffer()->size() + this->frame_footer_size_); + + uint16_t payload_size = + static_cast(buffer.get_buffer()->size() - HEADER_PADDING - this->frame_footer_size_); + uint8_t *buf_start = buffer.get_buffer()->data(); + uint16_t encrypted_len; + APIError aerr = this->encrypt_noise_message_(buf_start, payload_size, type, encrypted_len); + if (aerr != APIError::OK) + return aerr; + return this->write_raw_fast_buf_(buf_start, encrypted_len); +} + +APIError APINoiseFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer, std::span messages) { +#ifdef ESPHOME_DEBUG_API + assert(this->state_ == State::DATA); + assert(!messages.empty()); +#endif + + // Noise messages are already contiguous in the buffer: + // HEADER_PADDING (7) exactly matches the fixed header size, and + // footer space (16) is consumed by the encryption MAC. uint8_t *buffer_data = buffer.get_buffer()->data(); - - // Stack-allocated iovec array - no heap allocation - StaticVector iovs; + uint8_t *write_start = buffer_data + messages[0].offset; uint16_t total_write_len = 0; - // We need to encrypt each message in place for (const auto &msg : messages) { - // The buffer already has padding at offset uint8_t *buf_start = buffer_data + msg.offset; - - // Write noise header - buf_start[0] = 0x01; // indicator - // buf_start[1], buf_start[2] to be set after encryption - - // Write message header (to be encrypted) - constexpr uint8_t msg_offset = 3; - buf_start[msg_offset] = static_cast(msg.message_type >> 8); // type high byte - buf_start[msg_offset + 1] = static_cast(msg.message_type); // type low byte - buf_start[msg_offset + 2] = static_cast(msg.payload_size >> 8); // data_len high byte - buf_start[msg_offset + 3] = static_cast(msg.payload_size); // data_len low byte - // payload data is already in the buffer starting at offset + 7 - - // Make sure we have space for MAC - // The buffer should already have been sized appropriately - - // Encrypt the message in place - NoiseBuffer mbuf; - noise_buffer_init(mbuf); - noise_buffer_set_inout(mbuf, buf_start + msg_offset, 4 + msg.payload_size, - 4 + msg.payload_size + frame_footer_size_); - - int err = noise_cipherstate_encrypt(send_cipher_, &mbuf); - APIError aerr = - handle_noise_error_(err, LOG_STR("noise_cipherstate_encrypt"), APIError::CIPHERSTATE_ENCRYPT_FAILED); + uint16_t encrypted_len; + APIError aerr = this->encrypt_noise_message_(buf_start, msg.payload_size, msg.message_type, encrypted_len); if (aerr != APIError::OK) return aerr; - - // Fill in the encrypted size - buf_start[1] = static_cast(mbuf.size >> 8); - buf_start[2] = static_cast(mbuf.size); - - // Add iovec for this encrypted message - size_t msg_len = static_cast(3 + mbuf.size); // indicator + size + encrypted data - iovs.push_back({buf_start, msg_len}); - total_write_len += msg_len; + total_write_len += encrypted_len; } - // Send all encrypted messages in one writev call - return this->write_raw_(iovs.data(), iovs.size(), total_write_len); + return this->write_raw_fast_buf_(write_start, total_write_len); } APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) { @@ -525,16 +542,16 @@ APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) { header[1] = (uint8_t) (len >> 8); header[2] = (uint8_t) len; + if (len == 0) { + return this->write_raw_buf_(header, 3); + } struct iovec iov[2]; iov[0].iov_base = header; iov[0].iov_len = 3; - if (len == 0) { - return this->write_raw_(iov, 1, 3); // Just header - } iov[1].iov_base = const_cast(data); iov[1].iov_len = len; - return this->write_raw_(iov, 2, 3 + len); // Header + data + return this->write_raw_iov_(iov, 2, 3 + len); } /** Initiate the data structures for the handshake. @@ -600,7 +617,7 @@ APIError APINoiseFrameHelper::check_handshake_finished_() { if (aerr != APIError::OK) return aerr; - frame_footer_size_ = noise_cipherstate_get_mac_length(send_cipher_); + this->frame_footer_size_ = noise_cipherstate_get_mac_length(send_cipher_); HELPER_LOG("Handshake complete!"); noise_handshakestate_free(handshake_); diff --git a/esphome/components/api/api_frame_helper_noise.h b/esphome/components/api/api_frame_helper_noise.h index 83410febb2..0676eab78d 100644 --- a/esphome/components/api/api_frame_helper_noise.h +++ b/esphome/components/api/api_frame_helper_noise.h @@ -9,14 +9,16 @@ namespace esphome::api { class APINoiseFrameHelper final : public APIFrameHelper { public: + // Noise header structure: + // Pos 0: indicator (0x01) + // Pos 1-2: encrypted payload size (16-bit big-endian) + // Pos 3-6: encrypted type (16-bit) + data_len (16-bit) + // Pos 7+: actual payload data + static constexpr uint8_t HEADER_PADDING = 1 + 2 + 2 + 2; // indicator + size + type + data_len + APINoiseFrameHelper(std::unique_ptr socket, APINoiseContext &ctx) : APIFrameHelper(std::move(socket)), ctx_(ctx) { - // Noise header structure: - // Pos 0: indicator (0x01) - // Pos 1-2: encrypted payload size (16-bit big-endian) - // Pos 3-6: encrypted type (16-bit) + data_len (16-bit) - // Pos 7+: actual payload data - frame_header_padding_ = 7; + frame_header_padding_ = HEADER_PADDING; } ~APINoiseFrameHelper() override; APIError init() override; @@ -27,8 +29,15 @@ class APINoiseFrameHelper final : public APIFrameHelper { protected: APIError state_action_(); + APIError state_action_client_hello_(); + APIError state_action_server_hello_(); + APIError state_action_handshake_(); + APIError state_action_handshake_read_(); + APIError state_action_handshake_write_(); APIError try_read_frame_(); APIError write_frame_(const uint8_t *data, uint16_t len); + APIError encrypt_noise_message_(uint8_t *buf_start, uint16_t payload_size, uint8_t message_type, + uint16_t &encrypted_len_out); APIError init_handshake_(); APIError check_handshake_finished_(); void send_explicit_handshake_reject_(const LogString *reason); diff --git a/esphome/components/api/api_frame_helper_plaintext.cpp b/esphome/components/api/api_frame_helper_plaintext.cpp index 007da7ef2b..fa611a6e33 100644 --- a/esphome/components/api/api_frame_helper_plaintext.cpp +++ b/esphome/components/api/api_frame_helper_plaintext.cpp @@ -39,15 +39,8 @@ static constexpr size_t API_MAX_LOG_BYTES = 168; format_hex_pretty_to(hex_buf_, (buffer).data(), \ (buffer).size() < API_MAX_LOG_BYTES ? (buffer).size() : API_MAX_LOG_BYTES)); \ } while (0) -#define LOG_PACKET_SENDING(data, len) \ - do { \ - char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \ - ESP_LOGVV(TAG, "Sending raw: %s", \ - format_hex_pretty_to(hex_buf_, data, (len) < API_MAX_LOG_BYTES ? (len) : API_MAX_LOG_BYTES)); \ - } while (0) #else #define LOG_PACKET_RECEIVED(buffer) ((void) 0) -#define LOG_PACKET_SENDING(data, len) ((void) 0) #endif /// Initialize the frame helper, returns OK if successful. @@ -64,8 +57,10 @@ APIError APIPlaintextFrameHelper::loop() { if (state_ != State::DATA) { return APIError::BAD_STATE; } - // Use base class implementation for buffer sending - return APIFrameHelper::loop(); + if (!this->overflow_buf_.empty()) [[unlikely]] { + return this->drain_overflow_and_handle_errors_(); + } + return APIError::OK; } /** Read a packet into the rx_buf_. @@ -203,7 +198,6 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { // Make sure to tell the remote that we don't // understand the indicator byte so it knows // we do not support it. - struct iovec iov[1]; // The \x00 first byte is the marker for plaintext. // // The remote will know how to handle the indicator byte, @@ -218,14 +212,12 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { "Bad indicator byte"; char msg[INDICATOR_MSG_SIZE]; memcpy_P(msg, MSG_PROGMEM, INDICATOR_MSG_SIZE); - iov[0].iov_base = (void *) msg; + this->write_raw_buf_(msg, INDICATOR_MSG_SIZE); #else static const char MSG[] = "\x00" "Bad indicator byte"; - iov[0].iov_base = (void *) MSG; + this->write_raw_buf_(MSG, INDICATOR_MSG_SIZE); #endif - iov[0].iov_len = INDICATOR_MSG_SIZE; - this->write_raw_(iov, 1, INDICATOR_MSG_SIZE); } return aerr; } @@ -235,76 +227,101 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { buffer->type = this->rx_header_parsed_type_; return APIError::OK; } + +// Encode a 16-bit varint (1-3 bytes) using pre-computed length. +ESPHOME_ALWAYS_INLINE static inline void encode_varint_16(uint16_t value, uint8_t varint_len, uint8_t *p) { + if (varint_len >= 2) { + *p++ = static_cast(value | 0x80); + value >>= 7; + if (varint_len == 3) { + *p++ = static_cast(value | 0x80); + value >>= 7; + } + } + *p = static_cast(value); +} + +// Encode an 8-bit varint (1-2 bytes) using pre-computed length. +ESPHOME_ALWAYS_INLINE static inline void encode_varint_8(uint8_t value, uint8_t varint_len, uint8_t *p) { + if (varint_len == 2) { + *p++ = static_cast(value | 0x80); + *p = static_cast(value >> 7); + } else { + *p = value; + } +} + +// Write plaintext header into pre-allocated padding before payload. +// padding_size: bytes reserved before payload (HEADER_PADDING for first/single msg, +// actual header size for contiguous batch messages). +// Returns the total header length (indicator + varints). +ESPHOME_ALWAYS_INLINE static inline uint8_t write_plaintext_header(uint8_t *buf_start, uint16_t payload_size, + uint8_t message_type, uint8_t padding_size) { + uint8_t size_varint_len = ProtoSize::varint16(payload_size); + uint8_t type_varint_len = ProtoSize::varint8(message_type); + uint8_t total_header_len = 1 + size_varint_len + type_varint_len; + + // The header is right-justified within the padding so it sits immediately before payload. + // + // Single/first message (padding_size = HEADER_PADDING = 6): + // Example (small, header=3): [0-2] unused | [3] 0x00 | [4] size | [5] type | [6...] payload + // Example (medium, header=4): [0-1] unused | [2] 0x00 | [3-4] size | [5] type | [6...] payload + // Example (large, header=6): [0] 0x00 | [1-3] size | [4-5] type | [6...] payload + // + // Batch messages 2+ (padding_size = actual header size, no unused bytes): + // Example (small, header=3): [0] 0x00 | [1] size | [2] type | [3...] payload + // Example (medium, header=4): [0] 0x00 | [1-2] size | [3] type | [4...] payload +#ifdef ESPHOME_DEBUG_API + assert(padding_size >= total_header_len); +#endif + uint32_t header_offset = padding_size - total_header_len; + + // Write the plaintext header + buf_start[header_offset] = 0x00; // indicator + + // Encode varints directly into buffer using pre-computed lengths + encode_varint_16(payload_size, size_varint_len, buf_start + header_offset + 1); + encode_varint_8(message_type, type_varint_len, buf_start + header_offset + 1 + size_varint_len); + + return total_header_len; +} + APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) { - MessageInfo msg{type, 0, static_cast(buffer.get_buffer()->size() - frame_header_padding_)}; - return write_protobuf_messages(buffer, std::span(&msg, 1)); +#ifdef ESPHOME_DEBUG_API + assert(this->state_ == State::DATA); +#endif + + uint16_t payload_size = static_cast(buffer.get_buffer()->size() - HEADER_PADDING); + uint8_t *buffer_data = buffer.get_buffer()->data(); + uint8_t header_len = write_plaintext_header(buffer_data, payload_size, type, HEADER_PADDING); + return this->write_raw_fast_buf_(buffer_data + HEADER_PADDING - header_len, + static_cast(header_len + payload_size)); } APIError APIPlaintextFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer, std::span messages) { - APIError aerr = this->check_data_state_(); - if (aerr != APIError::OK) - return aerr; - - if (messages.empty()) { - return APIError::OK; - } - +#ifdef ESPHOME_DEBUG_API + assert(this->state_ == State::DATA); + assert(!messages.empty()); +#endif uint8_t *buffer_data = buffer.get_buffer()->data(); - // Stack-allocated iovec array - no heap allocation - StaticVector iovs; - uint16_t total_write_len = 0; + // First message has max padding (header_size = HEADER_PADDING), may have unused leading bytes. + // Subsequent messages were encoded with exact header sizes (header_size = actual header len). + // write_plaintext_header right-justifies the header within header_size bytes of padding. + const auto &first = messages[0]; + uint8_t *first_start = buffer_data + first.offset; + uint8_t header_len = write_plaintext_header(first_start, first.payload_size, first.message_type, HEADER_PADDING); + uint8_t *write_start = first_start + HEADER_PADDING - header_len; + uint16_t total_len = header_len + first.payload_size; - for (const auto &msg : messages) { - // Calculate varint sizes for header layout - uint8_t size_varint_len = api::ProtoSize::varint(static_cast(msg.payload_size)); - uint8_t type_varint_len = api::ProtoSize::varint(static_cast(msg.message_type)); - uint8_t total_header_len = 1 + size_varint_len + type_varint_len; - - // Calculate where to start writing the header - // The header starts at the latest possible position to minimize unused padding - // - // Example 1 (small values): total_header_len = 3, header_offset = 6 - 3 = 3 - // [0-2] - Unused padding - // [3] - 0x00 indicator byte - // [4] - Payload size varint (1 byte, for sizes 0-127) - // [5] - Message type varint (1 byte, for types 0-127) - // [6...] - Actual payload data - // - // Example 2 (medium values): total_header_len = 4, header_offset = 6 - 4 = 2 - // [0-1] - Unused padding - // [2] - 0x00 indicator byte - // [3-4] - Payload size varint (2 bytes, for sizes 128-16383) - // [5] - Message type varint (1 byte, for types 0-127) - // [6...] - Actual payload data - // - // Example 3 (large values): total_header_len = 6, header_offset = 6 - 6 = 0 - // [0] - 0x00 indicator byte - // [1-3] - Payload size varint (3 bytes, for sizes 16384-2097151) - // [4-5] - Message type varint (2 bytes, for types 128-32767) - // [6...] - Actual payload data - // - // The message starts at offset + frame_header_padding_ - // So we write the header starting at offset + frame_header_padding_ - total_header_len - uint8_t *buf_start = buffer_data + msg.offset; - uint32_t header_offset = frame_header_padding_ - total_header_len; - - // Write the plaintext header - buf_start[header_offset] = 0x00; // indicator - - // Encode varints directly into buffer - encode_varint_to_buffer(msg.payload_size, buf_start + header_offset + 1); - encode_varint_to_buffer(msg.message_type, buf_start + header_offset + 1 + size_varint_len); - - // Add iovec for this message (header + payload) - size_t msg_len = static_cast(total_header_len + msg.payload_size); - iovs.push_back({buf_start + header_offset, msg_len}); - total_write_len += msg_len; + for (size_t i = 1; i < messages.size(); i++) { + const auto &msg = messages[i]; + header_len = write_plaintext_header(buffer_data + msg.offset, msg.payload_size, msg.message_type, msg.header_size); + total_len += header_len + msg.payload_size; } - // Send all messages in one writev call - return write_raw_(iovs.data(), iovs.size(), total_write_len); + return this->write_raw_fast_buf_(write_start, total_len); } } // namespace esphome::api diff --git a/esphome/components/api/api_frame_helper_plaintext.h b/esphome/components/api/api_frame_helper_plaintext.h index 96d47e9c7b..8314754715 100644 --- a/esphome/components/api/api_frame_helper_plaintext.h +++ b/esphome/components/api/api_frame_helper_plaintext.h @@ -7,13 +7,15 @@ namespace esphome::api { class APIPlaintextFrameHelper final : public APIFrameHelper { public: + // Plaintext header structure (worst case): + // Pos 0: indicator (0x00) + // Pos 1-3: payload size varint (up to 3 bytes) + // Pos 4-5: message type varint (up to 2 bytes) + // Pos 6+: actual payload data + static constexpr uint8_t HEADER_PADDING = 1 + 3 + 2; // indicator + size varint + type varint + explicit APIPlaintextFrameHelper(std::unique_ptr socket) : APIFrameHelper(std::move(socket)) { - // Plaintext header structure (worst case): - // Pos 0: indicator (0x00) - // Pos 1-3: payload size varint (up to 3 bytes) - // Pos 4-5: message type varint (up to 2 bytes) - // Pos 6+: actual payload data - frame_header_padding_ = 6; + frame_header_padding_ = HEADER_PADDING; } ~APIPlaintextFrameHelper() override = default; APIError init() override; diff --git a/esphome/components/api/api_options.proto b/esphome/components/api/api_options.proto index 02600f0977..0f71268d70 100644 --- a/esphome/components/api/api_options.proto +++ b/esphome/components/api/api_options.proto @@ -96,4 +96,16 @@ extend google.protobuf.FieldOptions { // variant of the calc_ method. Use on fields that are almost always non-default // to eliminate dead branches on hot paths. optional bool force = 50016 [default=false]; + + // max_value: Maximum value a field can have. + // When max_value < 128, the code generator emits constant-size calculations + // and direct byte writes instead of varint branching, since the encoded varint + // is guaranteed to be 1 byte. + optional uint32 max_value = 50017; + + // max_data_length: Maximum length of a string or bytes field. + // When max_data_length < 128, the code generator emits constant-size + // length varint calculations and direct byte writes, since the length + // varint is guaranteed to be 1 byte. + optional uint32 max_data_length = 50018; } diff --git a/esphome/components/api/api_overflow_buffer.cpp b/esphome/components/api/api_overflow_buffer.cpp new file mode 100644 index 0000000000..e242d4553e --- /dev/null +++ b/esphome/components/api/api_overflow_buffer.cpp @@ -0,0 +1,73 @@ +#include "api_overflow_buffer.h" +#ifdef USE_API +#include + +namespace esphome::api { + +APIOverflowBuffer::~APIOverflowBuffer() { + for (auto *entry : this->queue_) { + if (entry != nullptr) + Entry::destroy(entry); + } +} + +ssize_t APIOverflowBuffer::try_drain(socket::Socket *socket) { + while (this->count_ > 0) { + Entry *front = this->queue_[this->head_]; + + ssize_t sent = socket->write(front->current_data(), front->remaining()); + + if (sent <= 0) { + // -1 = error (caller checks errno for EWOULDBLOCK vs hard error) + // 0 = nothing sent (treat as no progress) + return sent; + } + + if (static_cast(sent) < front->remaining()) { + // Partially sent, update offset and stop + front->offset += static_cast(sent); + return sent; + } + + // Entry fully sent — free it and advance + Entry::destroy(front); + this->queue_[this->head_] = nullptr; + this->head_ = (this->head_ + 1) % API_MAX_SEND_QUEUE; + this->count_--; + } + + return 0; // All drained +} + +bool APIOverflowBuffer::enqueue_iov(const struct iovec *iov, int iovcnt, uint16_t total_len, uint16_t skip) { + if (this->count_ >= API_MAX_SEND_QUEUE) + return false; + + uint16_t buffer_size = total_len - skip; + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + auto *entry = new Entry{new uint8_t[buffer_size], buffer_size, 0}; + this->queue_[this->tail_] = entry; + + uint16_t to_skip = skip; + uint16_t write_pos = 0; + + for (int i = 0; i < iovcnt; i++) { + if (to_skip >= iov[i].iov_len) { + to_skip -= static_cast(iov[i].iov_len); + } else { + const uint8_t *src = reinterpret_cast(iov[i].iov_base) + to_skip; + uint16_t len = static_cast(iov[i].iov_len) - to_skip; + std::memcpy(entry->data + write_pos, src, len); + write_pos += len; + to_skip = 0; + } + } + + this->tail_ = (this->tail_ + 1) % API_MAX_SEND_QUEUE; + this->count_++; + return true; +} + +} // namespace esphome::api + +#endif // USE_API diff --git a/esphome/components/api/api_overflow_buffer.h b/esphome/components/api/api_overflow_buffer.h new file mode 100644 index 0000000000..19aae680f0 --- /dev/null +++ b/esphome/components/api/api_overflow_buffer.h @@ -0,0 +1,76 @@ +#pragma once +#include +#include +#include + +#include "esphome/core/defines.h" +#ifdef USE_API + +#include "esphome/components/socket/headers.h" +#include "esphome/components/socket/socket.h" +#include "esphome/core/helpers.h" + +namespace esphome::api { + +/// Circular queue of heap-allocated byte buffers used as a TCP send backlog. +/// +/// Under normal operation this buffer is **never used** — data goes straight +/// from the frame helper to the socket. It only fills when the LWIP TCP +/// send buffer is full (slow client, congested network, heavy logging). +/// The queue drains automatically on subsequent write/loop calls once the +/// socket becomes writable again. +/// +/// Capacity is compile-time-fixed via API_MAX_SEND_QUEUE (set from Python +/// config). If the queue fills completely the connection is marked failed. +class APIOverflowBuffer { + public: + /// A single heap-allocated send-backlog entry. + /// Lifetime is manually managed — see destroy(). + struct Entry { + uint8_t *data; + uint16_t size; // Total size of the buffer + uint16_t offset; // Current send offset within the buffer + + uint16_t remaining() const { return this->size - this->offset; } + const uint8_t *current_data() const { return this->data + this->offset; } + + /// Free this entry and its data buffer. + static ESPHOME_ALWAYS_INLINE void destroy(Entry *entry) { + delete[] entry->data; + delete entry; // NOLINT(cppcoreguidelines-owning-memory) + } + }; + + ~APIOverflowBuffer(); + + /// True when no backlogged data is waiting. + bool empty() const { return this->count_ == 0; } + + /// True when the queue has no room for another entry. + bool full() const { return this->count_ >= API_MAX_SEND_QUEUE; } + + /// Number of entries currently queued. + uint8_t count() const { return this->count_; } + + /// Try to drain queued data to the socket. + /// Returns bytes-written > 0 on success/partial, 0 if all drained or no progress, + /// -1 on error (caller must check errno to distinguish EWOULDBLOCK from hard errors). + /// Callers only need to act on -1; 0 and positive values both mean "no error". + /// Frees entries as they are fully sent. + ssize_t try_drain(socket::Socket *socket); + + /// Enqueue unsent IOV data into the backlog. + /// Copies iov data starting at byte offset `skip` into a new entry. + /// Returns false if the queue is full (caller should fail the connection). + bool enqueue_iov(const struct iovec *iov, int iovcnt, uint16_t total_len, uint16_t skip); + + protected: + std::array queue_{}; + uint8_t head_{0}; + uint8_t tail_{0}; + uint8_t count_{0}; +}; + +} // namespace esphome::api + +#endif // USE_API diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 01993cc5e5..d27cfa57cf 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -31,133 +31,143 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) } return true; } -void HelloResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint32(1, this->api_version_major); - buffer.encode_uint32(2, this->api_version_minor); - buffer.encode_string(3, this->server_info); - buffer.encode_string(4, this->name); +uint8_t *HelloResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, this->api_version_major); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, this->api_version_minor); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->server_info); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 34, this->name); + return pos; } uint32_t HelloResponse::calculate_size() const { uint32_t size = 0; size += ProtoSize::calc_uint32(1, this->api_version_major); size += ProtoSize::calc_uint32(1, this->api_version_minor); - size += ProtoSize::calc_length(1, this->server_info.size()); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->server_info.size(); + size += 2 + this->name.size(); return size; } #ifdef USE_AREAS -void AreaInfo::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint32(1, this->area_id); - buffer.encode_string(2, this->name); +uint8_t *AreaInfo::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, this->area_id); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 18, this->name); + return pos; } uint32_t AreaInfo::calculate_size() const { uint32_t size = 0; size += ProtoSize::calc_uint32(1, this->area_id); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->name.size(); return size; } #endif #ifdef USE_DEVICES -void DeviceInfo::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint32(1, this->device_id); - buffer.encode_string(2, this->name); - buffer.encode_uint32(3, this->area_id); +uint8_t *DeviceInfo::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, this->device_id); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 18, this->name); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, this->area_id); + return pos; } uint32_t DeviceInfo::calculate_size() const { uint32_t size = 0; size += ProtoSize::calc_uint32(1, this->device_id); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->name.size(); size += ProtoSize::calc_uint32(1, this->area_id); return size; } #endif #ifdef USE_SERIAL_PROXY -void SerialProxyInfo::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->name); - buffer.encode_uint32(2, static_cast(this->port_type)); +uint8_t *SerialProxyInfo::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 1, this->name); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, static_cast(this->port_type)); + return pos; } uint32_t SerialProxyInfo::calculate_size() const { uint32_t size = 0; size += ProtoSize::calc_length(1, this->name.size()); - size += ProtoSize::calc_uint32(1, static_cast(this->port_type)); + size += this->port_type ? 2 : 0; return size; } #endif -void DeviceInfoResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(2, this->name); - buffer.encode_string(3, this->mac_address); - buffer.encode_string(4, this->esphome_version); - buffer.encode_string(5, this->compilation_time); - buffer.encode_string(6, this->model); +uint8_t *DeviceInfoResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 18, this->name); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->mac_address); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 34, this->esphome_version); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 42, this->compilation_time); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 50, this->model); #ifdef USE_DEEP_SLEEP - buffer.encode_bool(7, this->has_deep_sleep); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 7, this->has_deep_sleep); #endif #ifdef ESPHOME_PROJECT_NAME - buffer.encode_string(8, this->project_name); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 66, this->project_name); #endif #ifdef ESPHOME_PROJECT_NAME - buffer.encode_string(9, this->project_version); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 74, this->project_version); #endif #ifdef USE_WEBSERVER - buffer.encode_uint32(10, this->webserver_port); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 10, this->webserver_port); #endif #ifdef USE_BLUETOOTH_PROXY - buffer.encode_uint32(15, this->bluetooth_proxy_feature_flags); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 15, this->bluetooth_proxy_feature_flags); #endif - buffer.encode_string(12, this->manufacturer); - buffer.encode_string(13, this->friendly_name); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 98, this->manufacturer); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 106, this->friendly_name); #ifdef USE_VOICE_ASSISTANT - buffer.encode_uint32(17, this->voice_assistant_feature_flags); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 17, this->voice_assistant_feature_flags); #endif #ifdef USE_AREAS - buffer.encode_string(16, this->suggested_area); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 16, this->suggested_area, true); #endif #ifdef USE_BLUETOOTH_PROXY - buffer.encode_string(18, this->bluetooth_mac_address); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 18, this->bluetooth_mac_address, true); #endif #ifdef USE_API_NOISE - buffer.encode_bool(19, this->api_encryption_supported); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 19, this->api_encryption_supported); #endif #ifdef USE_DEVICES for (const auto &it : this->devices) { - buffer.encode_sub_message(20, it); + ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 20, it); } #endif #ifdef USE_AREAS for (const auto &it : this->areas) { - buffer.encode_sub_message(21, it); + ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 21, it); } #endif #ifdef USE_AREAS - buffer.encode_optional_sub_message(22, this->area); + ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 22, this->area); #endif #ifdef USE_ZWAVE_PROXY - buffer.encode_uint32(23, this->zwave_proxy_feature_flags); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 23, this->zwave_proxy_feature_flags); #endif #ifdef USE_ZWAVE_PROXY - buffer.encode_uint32(24, this->zwave_home_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 24, this->zwave_home_id); #endif #ifdef USE_SERIAL_PROXY for (const auto &it : this->serial_proxies) { - buffer.encode_sub_message(25, it); + ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 25, it); } #endif + return pos; } uint32_t DeviceInfoResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->name.size()); - size += ProtoSize::calc_length(1, this->mac_address.size()); - size += ProtoSize::calc_length(1, this->esphome_version.size()); - size += ProtoSize::calc_length(1, this->compilation_time.size()); - size += ProtoSize::calc_length(1, this->model.size()); + size += 2 + this->name.size(); + size += 2 + this->mac_address.size(); + size += 2 + this->esphome_version.size(); + size += 2 + this->compilation_time.size(); + size += 2 + this->model.size(); #ifdef USE_DEEP_SLEEP size += ProtoSize::calc_bool(1, this->has_deep_sleep); #endif #ifdef ESPHOME_PROJECT_NAME - size += ProtoSize::calc_length(1, this->project_name.size()); + size += 2 + this->project_name.size(); #endif #ifdef ESPHOME_PROJECT_NAME - size += ProtoSize::calc_length(1, this->project_version.size()); + size += 2 + this->project_version.size(); #endif #ifdef USE_WEBSERVER size += ProtoSize::calc_uint32(1, this->webserver_port); @@ -165,16 +175,16 @@ uint32_t DeviceInfoResponse::calculate_size() const { #ifdef USE_BLUETOOTH_PROXY size += ProtoSize::calc_uint32(1, this->bluetooth_proxy_feature_flags); #endif - size += ProtoSize::calc_length(1, this->manufacturer.size()); - size += ProtoSize::calc_length(1, this->friendly_name.size()); + size += 2 + this->manufacturer.size(); + size += 2 + this->friendly_name.size(); #ifdef USE_VOICE_ASSISTANT size += ProtoSize::calc_uint32(2, this->voice_assistant_feature_flags); #endif #ifdef USE_AREAS - size += ProtoSize::calc_length(2, this->suggested_area.size()); + size += 3 + this->suggested_area.size(); #endif #ifdef USE_BLUETOOTH_PROXY - size += ProtoSize::calc_length(2, this->bluetooth_mac_address.size()); + size += 3 + this->bluetooth_mac_address.size(); #endif #ifdef USE_API_NOISE size += ProtoSize::calc_bool(2, this->api_encryption_supported); @@ -206,49 +216,53 @@ uint32_t DeviceInfoResponse::calculate_size() const { return size; } #ifdef USE_BINARY_SENSOR -void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); - buffer.encode_string(5, this->device_class); - buffer.encode_bool(6, this->is_status_binary_sensor); - buffer.encode_bool(7, this->disabled_by_default); +uint8_t *ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 5, this->device_class); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 6, this->is_status_binary_sensor); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 7, this->disabled_by_default); #ifdef USE_ENTITY_ICON - buffer.encode_string(8, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 8, this->icon); #endif - buffer.encode_uint32(9, static_cast(this->entity_category)); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 9, static_cast(this->entity_category)); #ifdef USE_DEVICES - buffer.encode_uint32(10, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 10, this->device_id); #endif + return pos; } uint32_t ListEntitiesBinarySensorResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); - size += ProtoSize::calc_length(1, this->device_class.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); + size += !this->device_class.empty() ? 2 + this->device_class.size() : 0; size += ProtoSize::calc_bool(1, this->is_status_binary_sensor); size += ProtoSize::calc_bool(1, this->disabled_by_default); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); + size += this->entity_category ? 2 : 0; #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); #endif return size; } -void BinarySensorStateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_bool(2, this->state); - buffer.encode_bool(3, this->missing_state); +uint8_t *BinarySensorStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, this->state); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 3, this->missing_state); #ifdef USE_DEVICES - buffer.encode_uint32(4, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 4, this->device_id); #endif + return pos; } uint32_t BinarySensorStateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_fixed32(1, this->key); + size += 5; size += ProtoSize::calc_bool(1, this->state); size += ProtoSize::calc_bool(1, this->missing_state); #ifdef USE_DEVICES @@ -258,59 +272,63 @@ uint32_t BinarySensorStateResponse::calculate_size() const { } #endif #ifdef USE_COVER -void ListEntitiesCoverResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); - buffer.encode_bool(5, this->assumed_state); - buffer.encode_bool(6, this->supports_position); - buffer.encode_bool(7, this->supports_tilt); - buffer.encode_string(8, this->device_class); - buffer.encode_bool(9, this->disabled_by_default); +uint8_t *ListEntitiesCoverResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 5, this->assumed_state); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 6, this->supports_position); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 7, this->supports_tilt); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 8, this->device_class); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 9, this->disabled_by_default); #ifdef USE_ENTITY_ICON - buffer.encode_string(10, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 10, this->icon); #endif - buffer.encode_uint32(11, static_cast(this->entity_category)); - buffer.encode_bool(12, this->supports_stop); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 11, static_cast(this->entity_category)); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 12, this->supports_stop); #ifdef USE_DEVICES - buffer.encode_uint32(13, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->device_id); #endif + return pos; } uint32_t ListEntitiesCoverResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); size += ProtoSize::calc_bool(1, this->assumed_state); size += ProtoSize::calc_bool(1, this->supports_position); size += ProtoSize::calc_bool(1, this->supports_tilt); - size += ProtoSize::calc_length(1, this->device_class.size()); + size += !this->device_class.empty() ? 2 + this->device_class.size() : 0; size += ProtoSize::calc_bool(1, this->disabled_by_default); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); + size += this->entity_category ? 2 : 0; size += ProtoSize::calc_bool(1, this->supports_stop); #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); #endif return size; } -void CoverStateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_float(3, this->position); - buffer.encode_float(4, this->tilt); - buffer.encode_uint32(5, static_cast(this->current_operation)); +uint8_t *CoverStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 3, this->position); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 4, this->tilt); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 5, static_cast(this->current_operation)); #ifdef USE_DEVICES - buffer.encode_uint32(6, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 6, this->device_id); #endif + return pos; } uint32_t CoverStateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_fixed32(1, this->key); + size += 5; size += ProtoSize::calc_float(1, this->position); size += ProtoSize::calc_float(1, this->tilt); - size += ProtoSize::calc_uint32(1, static_cast(this->current_operation)); + size += this->current_operation ? 2 : 0; #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); #endif @@ -355,40 +373,42 @@ bool CoverCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { } #endif #ifdef USE_FAN -void ListEntitiesFanResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); - buffer.encode_bool(5, this->supports_oscillation); - buffer.encode_bool(6, this->supports_speed); - buffer.encode_bool(7, this->supports_direction); - buffer.encode_int32(8, this->supported_speed_count); - buffer.encode_bool(9, this->disabled_by_default); +uint8_t *ListEntitiesFanResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 5, this->supports_oscillation); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 6, this->supports_speed); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 7, this->supports_direction); + ProtoEncode::encode_int32(pos PROTO_ENCODE_DEBUG_ARG, 8, this->supported_speed_count); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 9, this->disabled_by_default); #ifdef USE_ENTITY_ICON - buffer.encode_string(10, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 10, this->icon); #endif - buffer.encode_uint32(11, static_cast(this->entity_category)); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 11, static_cast(this->entity_category)); for (const char *it : *this->supported_preset_modes) { - buffer.encode_string(12, it, strlen(it), true); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 12, it, strlen(it), true); } #ifdef USE_DEVICES - buffer.encode_uint32(13, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->device_id); #endif + return pos; } uint32_t ListEntitiesFanResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); size += ProtoSize::calc_bool(1, this->supports_oscillation); size += ProtoSize::calc_bool(1, this->supports_speed); size += ProtoSize::calc_bool(1, this->supports_direction); size += ProtoSize::calc_int32(1, this->supported_speed_count); size += ProtoSize::calc_bool(1, this->disabled_by_default); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); + size += this->entity_category ? 2 : 0; if (!this->supported_preset_modes->empty()) { for (const char *it : *this->supported_preset_modes) { size += ProtoSize::calc_length_force(1, strlen(it)); @@ -399,23 +419,25 @@ uint32_t ListEntitiesFanResponse::calculate_size() const { #endif return size; } -void FanStateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_bool(2, this->state); - buffer.encode_bool(3, this->oscillating); - buffer.encode_uint32(5, static_cast(this->direction)); - buffer.encode_int32(6, this->speed_level); - buffer.encode_string(7, this->preset_mode); +uint8_t *FanStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, this->state); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 3, this->oscillating); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 5, static_cast(this->direction)); + ProtoEncode::encode_int32(pos PROTO_ENCODE_DEBUG_ARG, 6, this->speed_level); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 7, this->preset_mode); #ifdef USE_DEVICES - buffer.encode_uint32(8, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 8, this->device_id); #endif + return pos; } uint32_t FanStateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_fixed32(1, this->key); + size += 5; size += ProtoSize::calc_bool(1, this->state); size += ProtoSize::calc_bool(1, this->oscillating); - size += ProtoSize::calc_uint32(1, static_cast(this->direction)); + size += this->direction ? 2 : 0; size += ProtoSize::calc_int32(1, this->speed_level); size += ProtoSize::calc_length(1, this->preset_mode.size()); #ifdef USE_DEVICES @@ -485,36 +507,36 @@ bool FanCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { } #endif #ifdef USE_LIGHT -void ListEntitiesLightResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); +uint8_t *ListEntitiesLightResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); for (const auto &it : *this->supported_color_modes) { - buffer.encode_uint32(12, static_cast(it), true); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 12, static_cast(it), true); } - buffer.encode_float(9, this->min_mireds); - buffer.encode_float(10, this->max_mireds); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 9, this->min_mireds); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 10, this->max_mireds); for (const char *it : *this->effects) { - buffer.encode_string(11, it, strlen(it), true); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 11, it, strlen(it), true); } - buffer.encode_bool(13, this->disabled_by_default); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 13, this->disabled_by_default); #ifdef USE_ENTITY_ICON - buffer.encode_string(14, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 14, this->icon); #endif - buffer.encode_uint32(15, static_cast(this->entity_category)); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 15, static_cast(this->entity_category)); #ifdef USE_DEVICES - buffer.encode_uint32(16, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 16, this->device_id); #endif + return pos; } uint32_t ListEntitiesLightResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); if (!this->supported_color_modes->empty()) { - for (const auto &it : *this->supported_color_modes) { - size += ProtoSize::calc_uint32_force(1, static_cast(it)); - } + size += this->supported_color_modes->size() * 2; } size += ProtoSize::calc_float(1, this->min_mireds); size += ProtoSize::calc_float(1, this->max_mireds); @@ -525,38 +547,40 @@ uint32_t ListEntitiesLightResponse::calculate_size() const { } size += ProtoSize::calc_bool(1, this->disabled_by_default); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); + size += this->entity_category ? 2 : 0; #ifdef USE_DEVICES size += ProtoSize::calc_uint32(2, this->device_id); #endif return size; } -void LightStateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_bool(2, this->state); - buffer.encode_float(3, this->brightness); - buffer.encode_uint32(11, static_cast(this->color_mode)); - buffer.encode_float(10, this->color_brightness); - buffer.encode_float(4, this->red); - buffer.encode_float(5, this->green); - buffer.encode_float(6, this->blue); - buffer.encode_float(7, this->white); - buffer.encode_float(8, this->color_temperature); - buffer.encode_float(12, this->cold_white); - buffer.encode_float(13, this->warm_white); - buffer.encode_string(9, this->effect); +uint8_t *LightStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, this->state); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 3, this->brightness); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 11, static_cast(this->color_mode)); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 10, this->color_brightness); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 4, this->red); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 5, this->green); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 6, this->blue); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 7, this->white); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 8, this->color_temperature); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 12, this->cold_white); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 13, this->warm_white); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 9, this->effect); #ifdef USE_DEVICES - buffer.encode_uint32(14, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 14, this->device_id); #endif + return pos; } uint32_t LightStateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_fixed32(1, this->key); + size += 5; size += ProtoSize::calc_bool(1, this->state); size += ProtoSize::calc_float(1, this->brightness); - size += ProtoSize::calc_uint32(1, static_cast(this->color_mode)); + size += this->color_mode ? 2 : 0; size += ProtoSize::calc_float(1, this->color_brightness); size += ProtoSize::calc_float(1, this->red); size += ProtoSize::calc_float(1, this->green); @@ -681,55 +705,59 @@ bool LightCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { } #endif #ifdef USE_SENSOR -void ListEntitiesSensorResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); +uint8_t *ListEntitiesSensorResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 5, this->icon); #endif - buffer.encode_string(6, this->unit_of_measurement); - buffer.encode_int32(7, this->accuracy_decimals); - buffer.encode_bool(8, this->force_update); - buffer.encode_string(9, this->device_class); - buffer.encode_uint32(10, static_cast(this->state_class)); - buffer.encode_bool(12, this->disabled_by_default); - buffer.encode_uint32(13, static_cast(this->entity_category)); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 6, this->unit_of_measurement); + ProtoEncode::encode_int32(pos PROTO_ENCODE_DEBUG_ARG, 7, this->accuracy_decimals); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 8, this->force_update); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 9, this->device_class); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 10, static_cast(this->state_class)); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 12, this->disabled_by_default); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 13, static_cast(this->entity_category)); #ifdef USE_DEVICES - buffer.encode_uint32(14, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 14, this->device_id); #endif + return pos; } uint32_t ListEntitiesSensorResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif - size += ProtoSize::calc_length(1, this->unit_of_measurement.size()); + size += !this->unit_of_measurement.empty() ? 2 + this->unit_of_measurement.size() : 0; size += ProtoSize::calc_int32(1, this->accuracy_decimals); size += ProtoSize::calc_bool(1, this->force_update); - size += ProtoSize::calc_length(1, this->device_class.size()); - size += ProtoSize::calc_uint32(1, static_cast(this->state_class)); + size += !this->device_class.empty() ? 2 + this->device_class.size() : 0; + size += this->state_class ? 2 : 0; size += ProtoSize::calc_bool(1, this->disabled_by_default); - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); + size += this->entity_category ? 2 : 0; #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); #endif return size; } -void SensorStateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_float(2, this->state); - buffer.encode_bool(3, this->missing_state); +uint8_t *SensorStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 2, this->state); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 3, this->missing_state); #ifdef USE_DEVICES - buffer.encode_uint32(4, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 4, this->device_id); #endif + return pos; } uint32_t SensorStateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_fixed32(1, this->key); + size += 5; size += ProtoSize::calc_float(1, this->state); size += ProtoSize::calc_bool(1, this->missing_state); #ifdef USE_DEVICES @@ -739,48 +767,52 @@ uint32_t SensorStateResponse::calculate_size() const { } #endif #ifdef USE_SWITCH -void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); +uint8_t *ListEntitiesSwitchResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 5, this->icon); #endif - buffer.encode_bool(6, this->assumed_state); - buffer.encode_bool(7, this->disabled_by_default); - buffer.encode_uint32(8, static_cast(this->entity_category)); - buffer.encode_string(9, this->device_class); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 6, this->assumed_state); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 7, this->disabled_by_default); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 8, static_cast(this->entity_category)); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 9, this->device_class); #ifdef USE_DEVICES - buffer.encode_uint32(10, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 10, this->device_id); #endif + return pos; } uint32_t ListEntitiesSwitchResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif size += ProtoSize::calc_bool(1, this->assumed_state); size += ProtoSize::calc_bool(1, this->disabled_by_default); - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); - size += ProtoSize::calc_length(1, this->device_class.size()); + size += this->entity_category ? 2 : 0; + size += !this->device_class.empty() ? 2 + this->device_class.size() : 0; #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); #endif return size; } -void SwitchStateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_bool(2, this->state); +uint8_t *SwitchStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, this->state); #ifdef USE_DEVICES - buffer.encode_uint32(3, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, this->device_id); #endif + return pos; } uint32_t SwitchStateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_fixed32(1, this->key); + size += 5; size += ProtoSize::calc_bool(1, this->state); #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); @@ -814,47 +846,51 @@ bool SwitchCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { } #endif #ifdef USE_TEXT_SENSOR -void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); +uint8_t *ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 5, this->icon); #endif - buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_uint32(7, static_cast(this->entity_category)); - buffer.encode_string(8, this->device_class); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 6, this->disabled_by_default); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 7, static_cast(this->entity_category)); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 8, this->device_class); #ifdef USE_DEVICES - buffer.encode_uint32(9, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 9, this->device_id); #endif + return pos; } uint32_t ListEntitiesTextSensorResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif size += ProtoSize::calc_bool(1, this->disabled_by_default); - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); - size += ProtoSize::calc_length(1, this->device_class.size()); + size += this->entity_category ? 2 : 0; + size += !this->device_class.empty() ? 2 + this->device_class.size() : 0; #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); #endif return size; } -void TextSensorStateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_string(2, this->state); - buffer.encode_bool(3, this->missing_state); +uint8_t *TextSensorStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 2, this->state); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 3, this->missing_state); #ifdef USE_DEVICES - buffer.encode_uint32(4, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 4, this->device_id); #endif + return pos; } uint32_t TextSensorStateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_fixed32(1, this->key); + size += 5; size += ProtoSize::calc_length(1, this->state.size()); size += ProtoSize::calc_bool(1, this->missing_state); #ifdef USE_DEVICES @@ -876,13 +912,15 @@ bool SubscribeLogsRequest::decode_varint(uint32_t field_id, proto_varint_value_t } return true; } -void SubscribeLogsResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint32(1, static_cast(this->level)); - buffer.encode_bytes(3, this->message_ptr_, this->message_len_); +uint8_t *SubscribeLogsResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, static_cast(this->level)); + ProtoEncode::encode_bytes(pos PROTO_ENCODE_DEBUG_ARG, 3, this->message_ptr_, this->message_len_); + return pos; } uint32_t SubscribeLogsResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_uint32(1, static_cast(this->level)); + size += this->level ? 2 : 0; size += ProtoSize::calc_length(1, this->message_len_); return size; } @@ -899,7 +937,11 @@ bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthD } return true; } -void NoiseEncryptionSetKeyResponse::encode(ProtoWriteBuffer &buffer) const { buffer.encode_bool(1, this->success); } +uint8_t *NoiseEncryptionSetKeyResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 1, this->success); + return pos; +} uint32_t NoiseEncryptionSetKeyResponse::calculate_size() const { uint32_t size = 0; size += ProtoSize::calc_bool(1, this->success); @@ -907,9 +949,11 @@ uint32_t NoiseEncryptionSetKeyResponse::calculate_size() const { } #endif #ifdef USE_API_HOMEASSISTANT_SERVICES -void HomeassistantServiceMap::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->key); - buffer.encode_string(2, this->value); +uint8_t *HomeassistantServiceMap::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 1, this->key); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 2, this->value); + return pos; } uint32_t HomeassistantServiceMap::calculate_size() const { uint32_t size = 0; @@ -917,27 +961,29 @@ uint32_t HomeassistantServiceMap::calculate_size() const { size += ProtoSize::calc_length(1, this->value.size()); return size; } -void HomeassistantActionRequest::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->service); +uint8_t *HomeassistantActionRequest::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 1, this->service); for (auto &it : this->data) { - buffer.encode_sub_message(2, it); + ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 2, it); } for (auto &it : this->data_template) { - buffer.encode_sub_message(3, it); + ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 3, it); } for (auto &it : this->variables) { - buffer.encode_sub_message(4, it); + ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 4, it); } - buffer.encode_bool(5, this->is_event); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 5, this->is_event); #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES - buffer.encode_uint32(6, this->call_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 6, this->call_id); #endif #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON - buffer.encode_bool(7, this->wants_response); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 7, this->wants_response); #endif #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON - buffer.encode_string(8, this->response_template); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 8, this->response_template); #endif + return pos; } uint32_t HomeassistantActionRequest::calculate_size() const { uint32_t size = 0; @@ -1004,10 +1050,12 @@ bool HomeassistantActionResponse::decode_length(uint32_t field_id, ProtoLengthDe } #endif #ifdef USE_API_HOMEASSISTANT_STATES -void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->entity_id); - buffer.encode_string(2, this->attribute); - buffer.encode_bool(3, this->once); +uint8_t *SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 1, this->entity_id); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 2, this->attribute); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 3, this->once); + return pos; } uint32_t SubscribeHomeAssistantStateResponse::calculate_size() const { uint32_t size = 0; @@ -1112,34 +1160,38 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { return true; } #ifdef USE_API_USER_DEFINED_ACTIONS -void ListEntitiesServicesArgument::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->name); - buffer.encode_uint32(2, static_cast(this->type)); +uint8_t *ListEntitiesServicesArgument::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 1, this->name); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, static_cast(this->type)); + return pos; } uint32_t ListEntitiesServicesArgument::calculate_size() const { uint32_t size = 0; size += ProtoSize::calc_length(1, this->name.size()); - size += ProtoSize::calc_uint32(1, static_cast(this->type)); + size += this->type ? 2 : 0; return size; } -void ListEntitiesServicesResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->name); - buffer.encode_fixed32(2, this->key); +uint8_t *ListEntitiesServicesResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 1, this->name); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); for (auto &it : this->args) { - buffer.encode_sub_message(3, it); + ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 3, it); } - buffer.encode_uint32(4, static_cast(this->supports_response)); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 4, static_cast(this->supports_response)); + return pos; } uint32_t ListEntitiesServicesResponse::calculate_size() const { uint32_t size = 0; size += ProtoSize::calc_length(1, this->name.size()); - size += ProtoSize::calc_fixed32(1, this->key); + size += 5; if (!this->args.empty()) { for (const auto &it : this->args) { size += ProtoSize::calc_message_force(1, it.calculate_size()); } } - size += ProtoSize::calc_uint32(1, static_cast(this->supports_response)); + size += this->supports_response ? 2 : 0; return size; } bool ExecuteServiceArgument::decode_varint(uint32_t field_id, proto_varint_value_t value) { @@ -1247,13 +1299,15 @@ void ExecuteServiceRequest::decode(const uint8_t *buffer, size_t length) { } #endif #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES -void ExecuteServiceResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint32(1, this->call_id); - buffer.encode_bool(2, this->success); - buffer.encode_string(3, this->error_message); +uint8_t *ExecuteServiceResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, this->call_id); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, this->success); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 3, this->error_message); #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON - buffer.encode_bytes(4, this->response_data, this->response_data_len); + ProtoEncode::encode_bytes(pos PROTO_ENCODE_DEBUG_ARG, 4, this->response_data, this->response_data_len); #endif + return pos; } uint32_t ExecuteServiceResponse::calculate_size() const { uint32_t size = 0; @@ -1267,45 +1321,49 @@ uint32_t ExecuteServiceResponse::calculate_size() const { } #endif #ifdef USE_CAMERA -void ListEntitiesCameraResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); - buffer.encode_bool(5, this->disabled_by_default); +uint8_t *ListEntitiesCameraResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 5, this->disabled_by_default); #ifdef USE_ENTITY_ICON - buffer.encode_string(6, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 6, this->icon); #endif - buffer.encode_uint32(7, static_cast(this->entity_category)); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 7, static_cast(this->entity_category)); #ifdef USE_DEVICES - buffer.encode_uint32(8, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 8, this->device_id); #endif + return pos; } uint32_t ListEntitiesCameraResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); size += ProtoSize::calc_bool(1, this->disabled_by_default); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); + size += this->entity_category ? 2 : 0; #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); #endif return size; } -void CameraImageResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_bytes(2, this->data_ptr_, this->data_len_); - buffer.encode_bool(3, this->done); +uint8_t *CameraImageResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); + ProtoEncode::encode_bytes(pos PROTO_ENCODE_DEBUG_ARG, 2, this->data_ptr_, this->data_len_); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 3, this->done); #ifdef USE_DEVICES - buffer.encode_uint32(4, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 4, this->device_id); #endif + return pos; } uint32_t CameraImageResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_fixed32(1, this->key); + size += 5; size += ProtoSize::calc_length(1, this->data_len_); size += ProtoSize::calc_bool(1, this->done); #ifdef USE_DEVICES @@ -1328,74 +1386,70 @@ bool CameraImageRequest::decode_varint(uint32_t field_id, proto_varint_value_t v } #endif #ifdef USE_CLIMATE -void ListEntitiesClimateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); - buffer.encode_bool(5, this->supports_current_temperature); - buffer.encode_bool(6, this->supports_two_point_target_temperature); +uint8_t *ListEntitiesClimateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 5, this->supports_current_temperature); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 6, this->supports_two_point_target_temperature); for (const auto &it : *this->supported_modes) { - buffer.encode_uint32(7, static_cast(it), true); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 7, static_cast(it), true); } - buffer.encode_float(8, this->visual_min_temperature); - buffer.encode_float(9, this->visual_max_temperature); - buffer.encode_float(10, this->visual_target_temperature_step); - buffer.encode_bool(12, this->supports_action); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 8, this->visual_min_temperature); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 9, this->visual_max_temperature); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 10, this->visual_target_temperature_step); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 12, this->supports_action); for (const auto &it : *this->supported_fan_modes) { - buffer.encode_uint32(13, static_cast(it), true); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 13, static_cast(it), true); } for (const auto &it : *this->supported_swing_modes) { - buffer.encode_uint32(14, static_cast(it), true); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 14, static_cast(it), true); } for (const char *it : *this->supported_custom_fan_modes) { - buffer.encode_string(15, it, strlen(it), true); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 15, it, strlen(it), true); } for (const auto &it : *this->supported_presets) { - buffer.encode_uint32(16, static_cast(it), true); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 16, static_cast(it), true); } for (const char *it : *this->supported_custom_presets) { - buffer.encode_string(17, it, strlen(it), true); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 17, it, strlen(it), true); } - buffer.encode_bool(18, this->disabled_by_default); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 18, this->disabled_by_default); #ifdef USE_ENTITY_ICON - buffer.encode_string(19, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 19, this->icon); #endif - buffer.encode_uint32(20, static_cast(this->entity_category)); - buffer.encode_float(21, this->visual_current_temperature_step); - buffer.encode_bool(22, this->supports_current_humidity); - buffer.encode_bool(23, this->supports_target_humidity); - buffer.encode_float(24, this->visual_min_humidity); - buffer.encode_float(25, this->visual_max_humidity); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 20, static_cast(this->entity_category)); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 21, this->visual_current_temperature_step); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 22, this->supports_current_humidity); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 23, this->supports_target_humidity); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 24, this->visual_min_humidity); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 25, this->visual_max_humidity); #ifdef USE_DEVICES - buffer.encode_uint32(26, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 26, this->device_id); #endif - buffer.encode_uint32(27, this->feature_flags); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 27, this->feature_flags); + return pos; } uint32_t ListEntitiesClimateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); size += ProtoSize::calc_bool(1, this->supports_current_temperature); size += ProtoSize::calc_bool(1, this->supports_two_point_target_temperature); if (!this->supported_modes->empty()) { - for (const auto &it : *this->supported_modes) { - size += ProtoSize::calc_uint32_force(1, static_cast(it)); - } + size += this->supported_modes->size() * 2; } size += ProtoSize::calc_float(1, this->visual_min_temperature); size += ProtoSize::calc_float(1, this->visual_max_temperature); size += ProtoSize::calc_float(1, this->visual_target_temperature_step); size += ProtoSize::calc_bool(1, this->supports_action); if (!this->supported_fan_modes->empty()) { - for (const auto &it : *this->supported_fan_modes) { - size += ProtoSize::calc_uint32_force(1, static_cast(it)); - } + size += this->supported_fan_modes->size() * 2; } if (!this->supported_swing_modes->empty()) { - for (const auto &it : *this->supported_swing_modes) { - size += ProtoSize::calc_uint32_force(1, static_cast(it)); - } + size += this->supported_swing_modes->size() * 2; } if (!this->supported_custom_fan_modes->empty()) { for (const char *it : *this->supported_custom_fan_modes) { @@ -1403,9 +1457,7 @@ uint32_t ListEntitiesClimateResponse::calculate_size() const { } } if (!this->supported_presets->empty()) { - for (const auto &it : *this->supported_presets) { - size += ProtoSize::calc_uint32_force(2, static_cast(it)); - } + size += this->supported_presets->size() * 3; } if (!this->supported_custom_presets->empty()) { for (const char *it : *this->supported_custom_presets) { @@ -1414,9 +1466,9 @@ uint32_t ListEntitiesClimateResponse::calculate_size() const { } size += ProtoSize::calc_bool(2, this->disabled_by_default); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(2, this->icon.size()); + size += !this->icon.empty() ? 3 + this->icon.size() : 0; #endif - size += ProtoSize::calc_uint32(2, static_cast(this->entity_category)); + size += this->entity_category ? 3 : 0; size += ProtoSize::calc_float(2, this->visual_current_temperature_step); size += ProtoSize::calc_bool(2, this->supports_current_humidity); size += ProtoSize::calc_bool(2, this->supports_target_humidity); @@ -1428,38 +1480,40 @@ uint32_t ListEntitiesClimateResponse::calculate_size() const { size += ProtoSize::calc_uint32(2, this->feature_flags); return size; } -void ClimateStateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_uint32(2, static_cast(this->mode)); - buffer.encode_float(3, this->current_temperature); - buffer.encode_float(4, this->target_temperature); - buffer.encode_float(5, this->target_temperature_low); - buffer.encode_float(6, this->target_temperature_high); - buffer.encode_uint32(8, static_cast(this->action)); - buffer.encode_uint32(9, static_cast(this->fan_mode)); - buffer.encode_uint32(10, static_cast(this->swing_mode)); - buffer.encode_string(11, this->custom_fan_mode); - buffer.encode_uint32(12, static_cast(this->preset)); - buffer.encode_string(13, this->custom_preset); - buffer.encode_float(14, this->current_humidity); - buffer.encode_float(15, this->target_humidity); +uint8_t *ClimateStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, static_cast(this->mode)); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 3, this->current_temperature); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 4, this->target_temperature); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 5, this->target_temperature_low); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 6, this->target_temperature_high); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 8, static_cast(this->action)); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 9, static_cast(this->fan_mode)); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 10, static_cast(this->swing_mode)); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 11, this->custom_fan_mode); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 12, static_cast(this->preset)); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 13, this->custom_preset); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 14, this->current_humidity); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 15, this->target_humidity); #ifdef USE_DEVICES - buffer.encode_uint32(16, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 16, this->device_id); #endif + return pos; } uint32_t ClimateStateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_uint32(1, static_cast(this->mode)); + size += 5; + size += this->mode ? 2 : 0; size += ProtoSize::calc_float(1, this->current_temperature); size += ProtoSize::calc_float(1, this->target_temperature); size += ProtoSize::calc_float(1, this->target_temperature_low); size += ProtoSize::calc_float(1, this->target_temperature_high); - size += ProtoSize::calc_uint32(1, static_cast(this->action)); - size += ProtoSize::calc_uint32(1, static_cast(this->fan_mode)); - size += ProtoSize::calc_uint32(1, static_cast(this->swing_mode)); + size += this->action ? 2 : 0; + size += this->fan_mode ? 2 : 0; + size += this->swing_mode ? 2 : 0; size += ProtoSize::calc_length(1, this->custom_fan_mode.size()); - size += ProtoSize::calc_uint32(1, static_cast(this->preset)); + size += this->preset ? 2 : 0; size += ProtoSize::calc_length(1, this->custom_preset.size()); size += ProtoSize::calc_float(1, this->current_humidity); size += ProtoSize::calc_float(1, this->target_humidity); @@ -1561,36 +1615,38 @@ bool ClimateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { } #endif #ifdef USE_WATER_HEATER -void ListEntitiesWaterHeaterResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); +uint8_t *ListEntitiesWaterHeaterResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(4, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 4, this->icon); #endif - buffer.encode_bool(5, this->disabled_by_default); - buffer.encode_uint32(6, static_cast(this->entity_category)); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 5, this->disabled_by_default); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 6, static_cast(this->entity_category)); #ifdef USE_DEVICES - buffer.encode_uint32(7, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 7, this->device_id); #endif - buffer.encode_float(8, this->min_temperature); - buffer.encode_float(9, this->max_temperature); - buffer.encode_float(10, this->target_temperature_step); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 8, this->min_temperature); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 9, this->max_temperature); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 10, this->target_temperature_step); for (const auto &it : *this->supported_modes) { - buffer.encode_uint32(11, static_cast(it), true); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 11, static_cast(it), true); } - buffer.encode_uint32(12, this->supported_features); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 12, this->supported_features); + return pos; } uint32_t ListEntitiesWaterHeaterResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif size += ProtoSize::calc_bool(1, this->disabled_by_default); - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); + size += this->entity_category ? 2 : 0; #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); #endif @@ -1598,31 +1654,31 @@ uint32_t ListEntitiesWaterHeaterResponse::calculate_size() const { size += ProtoSize::calc_float(1, this->max_temperature); size += ProtoSize::calc_float(1, this->target_temperature_step); if (!this->supported_modes->empty()) { - for (const auto &it : *this->supported_modes) { - size += ProtoSize::calc_uint32_force(1, static_cast(it)); - } + size += this->supported_modes->size() * 2; } size += ProtoSize::calc_uint32(1, this->supported_features); return size; } -void WaterHeaterStateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_float(2, this->current_temperature); - buffer.encode_float(3, this->target_temperature); - buffer.encode_uint32(4, static_cast(this->mode)); +uint8_t *WaterHeaterStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 2, this->current_temperature); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 3, this->target_temperature); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 4, static_cast(this->mode)); #ifdef USE_DEVICES - buffer.encode_uint32(5, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 5, this->device_id); #endif - buffer.encode_uint32(6, this->state); - buffer.encode_float(7, this->target_temperature_low); - buffer.encode_float(8, this->target_temperature_high); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 6, this->state); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 7, this->target_temperature_low); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 8, this->target_temperature_high); + return pos; } uint32_t WaterHeaterStateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_fixed32(1, this->key); + size += 5; size += ProtoSize::calc_float(1, this->current_temperature); size += ProtoSize::calc_float(1, this->target_temperature); - size += ProtoSize::calc_uint32(1, static_cast(this->mode)); + size += this->mode ? 2 : 0; #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); #endif @@ -1673,57 +1729,61 @@ bool WaterHeaterCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value } #endif #ifdef USE_NUMBER -void ListEntitiesNumberResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); +uint8_t *ListEntitiesNumberResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 5, this->icon); #endif - buffer.encode_float(6, this->min_value); - buffer.encode_float(7, this->max_value); - buffer.encode_float(8, this->step); - buffer.encode_bool(9, this->disabled_by_default); - buffer.encode_uint32(10, static_cast(this->entity_category)); - buffer.encode_string(11, this->unit_of_measurement); - buffer.encode_uint32(12, static_cast(this->mode)); - buffer.encode_string(13, this->device_class); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 6, this->min_value); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 7, this->max_value); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 8, this->step); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 9, this->disabled_by_default); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 10, static_cast(this->entity_category)); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 11, this->unit_of_measurement); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 12, static_cast(this->mode)); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 13, this->device_class); #ifdef USE_DEVICES - buffer.encode_uint32(14, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 14, this->device_id); #endif + return pos; } uint32_t ListEntitiesNumberResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif size += ProtoSize::calc_float(1, this->min_value); size += ProtoSize::calc_float(1, this->max_value); size += ProtoSize::calc_float(1, this->step); size += ProtoSize::calc_bool(1, this->disabled_by_default); - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); - size += ProtoSize::calc_length(1, this->unit_of_measurement.size()); - size += ProtoSize::calc_uint32(1, static_cast(this->mode)); - size += ProtoSize::calc_length(1, this->device_class.size()); + size += this->entity_category ? 2 : 0; + size += !this->unit_of_measurement.empty() ? 2 + this->unit_of_measurement.size() : 0; + size += this->mode ? 2 : 0; + size += !this->device_class.empty() ? 2 + this->device_class.size() : 0; #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); #endif return size; } -void NumberStateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_float(2, this->state); - buffer.encode_bool(3, this->missing_state); +uint8_t *NumberStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 2, this->state); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 3, this->missing_state); #ifdef USE_DEVICES - buffer.encode_uint32(4, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 4, this->device_id); #endif + return pos; } uint32_t NumberStateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_fixed32(1, this->key); + size += 5; size += ProtoSize::calc_float(1, this->state); size += ProtoSize::calc_bool(1, this->missing_state); #ifdef USE_DEVICES @@ -1758,29 +1818,31 @@ bool NumberCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { } #endif #ifdef USE_SELECT -void ListEntitiesSelectResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); +uint8_t *ListEntitiesSelectResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 5, this->icon); #endif for (const char *it : *this->options) { - buffer.encode_string(6, it, strlen(it), true); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 6, it, strlen(it), true); } - buffer.encode_bool(7, this->disabled_by_default); - buffer.encode_uint32(8, static_cast(this->entity_category)); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 7, this->disabled_by_default); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 8, static_cast(this->entity_category)); #ifdef USE_DEVICES - buffer.encode_uint32(9, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 9, this->device_id); #endif + return pos; } uint32_t ListEntitiesSelectResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif if (!this->options->empty()) { for (const char *it : *this->options) { @@ -1788,23 +1850,25 @@ uint32_t ListEntitiesSelectResponse::calculate_size() const { } } size += ProtoSize::calc_bool(1, this->disabled_by_default); - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); + size += this->entity_category ? 2 : 0; #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); #endif return size; } -void SelectStateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_string(2, this->state); - buffer.encode_bool(3, this->missing_state); +uint8_t *SelectStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 2, this->state); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 3, this->missing_state); #ifdef USE_DEVICES - buffer.encode_uint32(4, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 4, this->device_id); #endif + return pos; } uint32_t SelectStateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_fixed32(1, this->key); + size += 5; size += ProtoSize::calc_length(1, this->state.size()); size += ProtoSize::calc_bool(1, this->missing_state); #ifdef USE_DEVICES @@ -1847,31 +1911,33 @@ bool SelectCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { } #endif #ifdef USE_SIREN -void ListEntitiesSirenResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); +uint8_t *ListEntitiesSirenResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 5, this->icon); #endif - buffer.encode_bool(6, this->disabled_by_default); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 6, this->disabled_by_default); for (const char *it : *this->tones) { - buffer.encode_string(7, it, strlen(it), true); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 7, it, strlen(it), true); } - buffer.encode_bool(8, this->supports_duration); - buffer.encode_bool(9, this->supports_volume); - buffer.encode_uint32(10, static_cast(this->entity_category)); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 8, this->supports_duration); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 9, this->supports_volume); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 10, static_cast(this->entity_category)); #ifdef USE_DEVICES - buffer.encode_uint32(11, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 11, this->device_id); #endif + return pos; } uint32_t ListEntitiesSirenResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif size += ProtoSize::calc_bool(1, this->disabled_by_default); if (!this->tones->empty()) { @@ -1881,22 +1947,24 @@ uint32_t ListEntitiesSirenResponse::calculate_size() const { } size += ProtoSize::calc_bool(1, this->supports_duration); size += ProtoSize::calc_bool(1, this->supports_volume); - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); + size += this->entity_category ? 2 : 0; #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); #endif return size; } -void SirenStateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_bool(2, this->state); +uint8_t *SirenStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, this->state); #ifdef USE_DEVICES - buffer.encode_uint32(3, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, this->device_id); #endif + return pos; } uint32_t SirenStateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_fixed32(1, this->key); + size += 5; size += ProtoSize::calc_bool(1, this->state); #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); @@ -1959,33 +2027,35 @@ bool SirenCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { } #endif #ifdef USE_LOCK -void ListEntitiesLockResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); +uint8_t *ListEntitiesLockResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 5, this->icon); #endif - buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_uint32(7, static_cast(this->entity_category)); - buffer.encode_bool(8, this->assumed_state); - buffer.encode_bool(9, this->supports_open); - buffer.encode_bool(10, this->requires_code); - buffer.encode_string(11, this->code_format); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 6, this->disabled_by_default); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 7, static_cast(this->entity_category)); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 8, this->assumed_state); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 9, this->supports_open); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 10, this->requires_code); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 11, this->code_format); #ifdef USE_DEVICES - buffer.encode_uint32(12, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 12, this->device_id); #endif + return pos; } uint32_t ListEntitiesLockResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif size += ProtoSize::calc_bool(1, this->disabled_by_default); - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); + size += this->entity_category ? 2 : 0; size += ProtoSize::calc_bool(1, this->assumed_state); size += ProtoSize::calc_bool(1, this->supports_open); size += ProtoSize::calc_bool(1, this->requires_code); @@ -1995,17 +2065,19 @@ uint32_t ListEntitiesLockResponse::calculate_size() const { #endif return size; } -void LockStateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_uint32(2, static_cast(this->state)); +uint8_t *LockStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, static_cast(this->state)); #ifdef USE_DEVICES - buffer.encode_uint32(3, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, this->device_id); #endif + return pos; } uint32_t LockStateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_uint32(1, static_cast(this->state)); + size += 5; + size += this->state ? 2 : 0; #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); #endif @@ -2052,31 +2124,33 @@ bool LockCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { } #endif #ifdef USE_BUTTON -void ListEntitiesButtonResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); +uint8_t *ListEntitiesButtonResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 5, this->icon); #endif - buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_uint32(7, static_cast(this->entity_category)); - buffer.encode_string(8, this->device_class); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 6, this->disabled_by_default); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 7, static_cast(this->entity_category)); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 8, this->device_class); #ifdef USE_DEVICES - buffer.encode_uint32(9, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 9, this->device_id); #endif + return pos; } uint32_t ListEntitiesButtonResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif size += ProtoSize::calc_bool(1, this->disabled_by_default); - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); - size += ProtoSize::calc_length(1, this->device_class.size()); + size += this->entity_category ? 2 : 0; + size += !this->device_class.empty() ? 2 + this->device_class.size() : 0; #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); #endif @@ -2106,50 +2180,54 @@ bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { } #endif #ifdef USE_MEDIA_PLAYER -void MediaPlayerSupportedFormat::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->format); - buffer.encode_uint32(2, this->sample_rate); - buffer.encode_uint32(3, this->num_channels); - buffer.encode_uint32(4, static_cast(this->purpose)); - buffer.encode_uint32(5, this->sample_bytes); +uint8_t *MediaPlayerSupportedFormat::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 1, this->format); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, this->sample_rate); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, this->num_channels); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 4, static_cast(this->purpose)); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 5, this->sample_bytes); + return pos; } uint32_t MediaPlayerSupportedFormat::calculate_size() const { uint32_t size = 0; size += ProtoSize::calc_length(1, this->format.size()); size += ProtoSize::calc_uint32(1, this->sample_rate); size += ProtoSize::calc_uint32(1, this->num_channels); - size += ProtoSize::calc_uint32(1, static_cast(this->purpose)); + size += this->purpose ? 2 : 0; size += ProtoSize::calc_uint32(1, this->sample_bytes); return size; } -void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); +uint8_t *ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 5, this->icon); #endif - buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_uint32(7, static_cast(this->entity_category)); - buffer.encode_bool(8, this->supports_pause); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 6, this->disabled_by_default); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 7, static_cast(this->entity_category)); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 8, this->supports_pause); for (auto &it : this->supported_formats) { - buffer.encode_sub_message(9, it); + ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 9, it); } #ifdef USE_DEVICES - buffer.encode_uint32(10, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 10, this->device_id); #endif - buffer.encode_uint32(11, this->feature_flags); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 11, this->feature_flags); + return pos; } uint32_t ListEntitiesMediaPlayerResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif size += ProtoSize::calc_bool(1, this->disabled_by_default); - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); + size += this->entity_category ? 2 : 0; size += ProtoSize::calc_bool(1, this->supports_pause); if (!this->supported_formats.empty()) { for (const auto &it : this->supported_formats) { @@ -2162,19 +2240,21 @@ uint32_t ListEntitiesMediaPlayerResponse::calculate_size() const { size += ProtoSize::calc_uint32(1, this->feature_flags); return size; } -void MediaPlayerStateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_uint32(2, static_cast(this->state)); - buffer.encode_float(3, this->volume); - buffer.encode_bool(4, this->muted); +uint8_t *MediaPlayerStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, static_cast(this->state)); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 3, this->volume); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 4, this->muted); #ifdef USE_DEVICES - buffer.encode_uint32(5, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 5, this->device_id); #endif + return pos; } uint32_t MediaPlayerStateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_uint32(1, static_cast(this->state)); + size += 5; + size += this->state ? 2 : 0; size += ProtoSize::calc_float(1, this->volume); size += ProtoSize::calc_bool(1, this->muted); #ifdef USE_DEVICES @@ -2248,24 +2328,35 @@ bool SubscribeBluetoothLEAdvertisementsRequest::decode_varint(uint32_t field_id, } return true; } -void BluetoothLERawAdvertisement::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint64(1, this->address, true); - buffer.encode_sint32(2, this->rssi, true); - buffer.encode_uint32(3, this->address_type); - buffer.encode_bytes(4, this->data, this->data_len, true); +uint8_t *BluetoothLERawAdvertisement::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_raw_byte(pos PROTO_ENCODE_DEBUG_ARG, 8); + ProtoEncode::encode_varint_raw_64(pos PROTO_ENCODE_DEBUG_ARG, this->address); + ProtoEncode::write_raw_byte(pos PROTO_ENCODE_DEBUG_ARG, 16); + ProtoEncode::encode_varint_raw_short(pos PROTO_ENCODE_DEBUG_ARG, encode_zigzag32(this->rssi)); + if (this->address_type) { + ProtoEncode::write_raw_byte(pos PROTO_ENCODE_DEBUG_ARG, 24); + ProtoEncode::encode_varint_raw(pos PROTO_ENCODE_DEBUG_ARG, this->address_type); + } + ProtoEncode::write_raw_byte(pos PROTO_ENCODE_DEBUG_ARG, 34); + ProtoEncode::write_raw_byte(pos PROTO_ENCODE_DEBUG_ARG, static_cast(this->data_len)); + ProtoEncode::encode_raw(pos PROTO_ENCODE_DEBUG_ARG, this->data, this->data_len); + return pos; } uint32_t BluetoothLERawAdvertisement::calculate_size() const { uint32_t size = 0; size += ProtoSize::calc_uint64_force(1, this->address); size += ProtoSize::calc_sint32_force(1, this->rssi); - size += ProtoSize::calc_uint32(1, this->address_type); - size += ProtoSize::calc_length_force(1, this->data_len); + size += this->address_type ? 2 : 0; + size += 2 + this->data_len; return size; } -void BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer &buffer) const { +uint8_t *BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); for (uint16_t i = 0; i < this->advertisements_len; i++) { - buffer.encode_sub_message(1, this->advertisements[i]); + ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 1, this->advertisements[i]); } + return pos; } uint32_t BluetoothLERawAdvertisementsResponse::calculate_size() const { uint32_t size = 0; @@ -2293,11 +2384,13 @@ bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, proto_varint_value } return true; } -void BluetoothDeviceConnectionResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint64(1, this->address); - buffer.encode_bool(2, this->connected); - buffer.encode_uint32(3, this->mtu); - buffer.encode_int32(4, this->error); +uint8_t *BluetoothDeviceConnectionResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, this->address); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, this->connected); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, this->mtu); + ProtoEncode::encode_int32(pos PROTO_ENCODE_DEBUG_ARG, 4, this->error); + return pos; } uint32_t BluetoothDeviceConnectionResponse::calculate_size() const { uint32_t size = 0; @@ -2317,13 +2410,15 @@ bool BluetoothGATTGetServicesRequest::decode_varint(uint32_t field_id, proto_var } return true; } -void BluetoothGATTDescriptor::encode(ProtoWriteBuffer &buffer) const { +uint8_t *BluetoothGATTDescriptor::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); if (this->uuid[0] != 0 || this->uuid[1] != 0) { - buffer.encode_uint64(1, this->uuid[0], true); - buffer.encode_uint64(1, this->uuid[1], true); + ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, this->uuid[0], true); + ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, this->uuid[1], true); } - buffer.encode_uint32(2, this->handle); - buffer.encode_uint32(3, this->short_uuid); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, this->handle); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, this->short_uuid); + return pos; } uint32_t BluetoothGATTDescriptor::calculate_size() const { uint32_t size = 0; @@ -2335,17 +2430,19 @@ uint32_t BluetoothGATTDescriptor::calculate_size() const { size += ProtoSize::calc_uint32(1, this->short_uuid); return size; } -void BluetoothGATTCharacteristic::encode(ProtoWriteBuffer &buffer) const { +uint8_t *BluetoothGATTCharacteristic::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); if (this->uuid[0] != 0 || this->uuid[1] != 0) { - buffer.encode_uint64(1, this->uuid[0], true); - buffer.encode_uint64(1, this->uuid[1], true); + ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, this->uuid[0], true); + ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, this->uuid[1], true); } - buffer.encode_uint32(2, this->handle); - buffer.encode_uint32(3, this->properties); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, this->handle); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, this->properties); for (auto &it : this->descriptors) { - buffer.encode_sub_message(4, it); + ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 4, it); } - buffer.encode_uint32(5, this->short_uuid); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 5, this->short_uuid); + return pos; } uint32_t BluetoothGATTCharacteristic::calculate_size() const { uint32_t size = 0; @@ -2363,16 +2460,18 @@ uint32_t BluetoothGATTCharacteristic::calculate_size() const { size += ProtoSize::calc_uint32(1, this->short_uuid); return size; } -void BluetoothGATTService::encode(ProtoWriteBuffer &buffer) const { +uint8_t *BluetoothGATTService::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); if (this->uuid[0] != 0 || this->uuid[1] != 0) { - buffer.encode_uint64(1, this->uuid[0], true); - buffer.encode_uint64(1, this->uuid[1], true); + ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, this->uuid[0], true); + ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, this->uuid[1], true); } - buffer.encode_uint32(2, this->handle); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, this->handle); for (auto &it : this->characteristics) { - buffer.encode_sub_message(3, it); + ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 3, it); } - buffer.encode_uint32(4, this->short_uuid); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 4, this->short_uuid); + return pos; } uint32_t BluetoothGATTService::calculate_size() const { uint32_t size = 0; @@ -2389,11 +2488,13 @@ uint32_t BluetoothGATTService::calculate_size() const { size += ProtoSize::calc_uint32(1, this->short_uuid); return size; } -void BluetoothGATTGetServicesResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint64(1, this->address); +uint8_t *BluetoothGATTGetServicesResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, this->address); for (auto &it : this->services) { - buffer.encode_sub_message(2, it); + ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 2, it); } + return pos; } uint32_t BluetoothGATTGetServicesResponse::calculate_size() const { uint32_t size = 0; @@ -2405,8 +2506,10 @@ uint32_t BluetoothGATTGetServicesResponse::calculate_size() const { } return size; } -void BluetoothGATTGetServicesDoneResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint64(1, this->address); +uint8_t *BluetoothGATTGetServicesDoneResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, this->address); + return pos; } uint32_t BluetoothGATTGetServicesDoneResponse::calculate_size() const { uint32_t size = 0; @@ -2426,10 +2529,12 @@ bool BluetoothGATTReadRequest::decode_varint(uint32_t field_id, proto_varint_val } return true; } -void BluetoothGATTReadResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint64(1, this->address); - buffer.encode_uint32(2, this->handle); - buffer.encode_bytes(3, this->data_ptr_, this->data_len_); +uint8_t *BluetoothGATTReadResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, this->address); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, this->handle); + ProtoEncode::encode_bytes(pos PROTO_ENCODE_DEBUG_ARG, 3, this->data_ptr_, this->data_len_); + return pos; } uint32_t BluetoothGATTReadResponse::calculate_size() const { uint32_t size = 0; @@ -2520,10 +2625,12 @@ bool BluetoothGATTNotifyRequest::decode_varint(uint32_t field_id, proto_varint_v } return true; } -void BluetoothGATTNotifyDataResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint64(1, this->address); - buffer.encode_uint32(2, this->handle); - buffer.encode_bytes(3, this->data_ptr_, this->data_len_); +uint8_t *BluetoothGATTNotifyDataResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, this->address); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, this->handle); + ProtoEncode::encode_bytes(pos PROTO_ENCODE_DEBUG_ARG, 3, this->data_ptr_, this->data_len_); + return pos; } uint32_t BluetoothGATTNotifyDataResponse::calculate_size() const { uint32_t size = 0; @@ -2532,14 +2639,16 @@ uint32_t BluetoothGATTNotifyDataResponse::calculate_size() const { size += ProtoSize::calc_length(1, this->data_len_); return size; } -void BluetoothConnectionsFreeResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint32(1, this->free); - buffer.encode_uint32(2, this->limit); +uint8_t *BluetoothConnectionsFreeResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, this->free); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, this->limit); for (const auto &it : this->allocated) { if (it != 0) { - buffer.encode_uint64(3, it, true); + ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 3, it, true); } } + return pos; } uint32_t BluetoothConnectionsFreeResponse::calculate_size() const { uint32_t size = 0; @@ -2552,10 +2661,12 @@ uint32_t BluetoothConnectionsFreeResponse::calculate_size() const { } return size; } -void BluetoothGATTErrorResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint64(1, this->address); - buffer.encode_uint32(2, this->handle); - buffer.encode_int32(3, this->error); +uint8_t *BluetoothGATTErrorResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, this->address); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, this->handle); + ProtoEncode::encode_int32(pos PROTO_ENCODE_DEBUG_ARG, 3, this->error); + return pos; } uint32_t BluetoothGATTErrorResponse::calculate_size() const { uint32_t size = 0; @@ -2564,9 +2675,11 @@ uint32_t BluetoothGATTErrorResponse::calculate_size() const { size += ProtoSize::calc_int32(1, this->error); return size; } -void BluetoothGATTWriteResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint64(1, this->address); - buffer.encode_uint32(2, this->handle); +uint8_t *BluetoothGATTWriteResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, this->address); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, this->handle); + return pos; } uint32_t BluetoothGATTWriteResponse::calculate_size() const { uint32_t size = 0; @@ -2574,9 +2687,11 @@ uint32_t BluetoothGATTWriteResponse::calculate_size() const { size += ProtoSize::calc_uint32(1, this->handle); return size; } -void BluetoothGATTNotifyResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint64(1, this->address); - buffer.encode_uint32(2, this->handle); +uint8_t *BluetoothGATTNotifyResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, this->address); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, this->handle); + return pos; } uint32_t BluetoothGATTNotifyResponse::calculate_size() const { uint32_t size = 0; @@ -2584,10 +2699,12 @@ uint32_t BluetoothGATTNotifyResponse::calculate_size() const { size += ProtoSize::calc_uint32(1, this->handle); return size; } -void BluetoothDevicePairingResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint64(1, this->address); - buffer.encode_bool(2, this->paired); - buffer.encode_int32(3, this->error); +uint8_t *BluetoothDevicePairingResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, this->address); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, this->paired); + ProtoEncode::encode_int32(pos PROTO_ENCODE_DEBUG_ARG, 3, this->error); + return pos; } uint32_t BluetoothDevicePairingResponse::calculate_size() const { uint32_t size = 0; @@ -2596,10 +2713,12 @@ uint32_t BluetoothDevicePairingResponse::calculate_size() const { size += ProtoSize::calc_int32(1, this->error); return size; } -void BluetoothDeviceUnpairingResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint64(1, this->address); - buffer.encode_bool(2, this->success); - buffer.encode_int32(3, this->error); +uint8_t *BluetoothDeviceUnpairingResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, this->address); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, this->success); + ProtoEncode::encode_int32(pos PROTO_ENCODE_DEBUG_ARG, 3, this->error); + return pos; } uint32_t BluetoothDeviceUnpairingResponse::calculate_size() const { uint32_t size = 0; @@ -2608,10 +2727,12 @@ uint32_t BluetoothDeviceUnpairingResponse::calculate_size() const { size += ProtoSize::calc_int32(1, this->error); return size; } -void BluetoothDeviceClearCacheResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint64(1, this->address); - buffer.encode_bool(2, this->success); - buffer.encode_int32(3, this->error); +uint8_t *BluetoothDeviceClearCacheResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, this->address); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, this->success); + ProtoEncode::encode_int32(pos PROTO_ENCODE_DEBUG_ARG, 3, this->error); + return pos; } uint32_t BluetoothDeviceClearCacheResponse::calculate_size() const { uint32_t size = 0; @@ -2620,16 +2741,18 @@ uint32_t BluetoothDeviceClearCacheResponse::calculate_size() const { size += ProtoSize::calc_int32(1, this->error); return size; } -void BluetoothScannerStateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint32(1, static_cast(this->state)); - buffer.encode_uint32(2, static_cast(this->mode)); - buffer.encode_uint32(3, static_cast(this->configured_mode)); +uint8_t *BluetoothScannerStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, static_cast(this->state)); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, static_cast(this->mode)); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, static_cast(this->configured_mode)); + return pos; } uint32_t BluetoothScannerStateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_uint32(1, static_cast(this->state)); - size += ProtoSize::calc_uint32(1, static_cast(this->mode)); - size += ProtoSize::calc_uint32(1, static_cast(this->configured_mode)); + size += this->state ? 2 : 0; + size += this->mode ? 2 : 0; + size += this->configured_mode ? 2 : 0; return size; } bool BluetoothScannerSetModeRequest::decode_varint(uint32_t field_id, proto_varint_value_t value) { @@ -2657,10 +2780,12 @@ bool SubscribeVoiceAssistantRequest::decode_varint(uint32_t field_id, proto_vari } return true; } -void VoiceAssistantAudioSettings::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint32(1, this->noise_suppression_level); - buffer.encode_uint32(2, this->auto_gain); - buffer.encode_float(3, this->volume_multiplier); +uint8_t *VoiceAssistantAudioSettings::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, this->noise_suppression_level); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, this->auto_gain); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 3, this->volume_multiplier); + return pos; } uint32_t VoiceAssistantAudioSettings::calculate_size() const { uint32_t size = 0; @@ -2669,12 +2794,14 @@ uint32_t VoiceAssistantAudioSettings::calculate_size() const { size += ProtoSize::calc_float(1, this->volume_multiplier); return size; } -void VoiceAssistantRequest::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_bool(1, this->start); - buffer.encode_string(2, this->conversation_id); - buffer.encode_uint32(3, this->flags); - buffer.encode_optional_sub_message(4, this->audio_settings); - buffer.encode_string(5, this->wake_word_phrase); +uint8_t *VoiceAssistantRequest::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 1, this->start); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 2, this->conversation_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, this->flags); + ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 4, this->audio_settings); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 5, this->wake_word_phrase); + return pos; } uint32_t VoiceAssistantRequest::calculate_size() const { uint32_t size = 0; @@ -2756,9 +2883,11 @@ bool VoiceAssistantAudio::decode_length(uint32_t field_id, ProtoLengthDelimited } return true; } -void VoiceAssistantAudio::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_bytes(1, this->data, this->data_len); - buffer.encode_bool(2, this->end); +uint8_t *VoiceAssistantAudio::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_bytes(pos PROTO_ENCODE_DEBUG_ARG, 1, this->data, this->data_len); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, this->end); + return pos; } uint32_t VoiceAssistantAudio::calculate_size() const { uint32_t size = 0; @@ -2829,18 +2958,24 @@ bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLength } return true; } -void VoiceAssistantAnnounceFinished::encode(ProtoWriteBuffer &buffer) const { buffer.encode_bool(1, this->success); } +uint8_t *VoiceAssistantAnnounceFinished::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 1, this->success); + return pos; +} uint32_t VoiceAssistantAnnounceFinished::calculate_size() const { uint32_t size = 0; size += ProtoSize::calc_bool(1, this->success); return size; } -void VoiceAssistantWakeWord::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->id); - buffer.encode_string(2, this->wake_word); +uint8_t *VoiceAssistantWakeWord::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 1, this->id); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 2, this->wake_word); for (auto &it : this->trained_languages) { - buffer.encode_string(3, it, true); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 3, it, true); } + return pos; } uint32_t VoiceAssistantWakeWord::calculate_size() const { uint32_t size = 0; @@ -2904,14 +3039,16 @@ bool VoiceAssistantConfigurationRequest::decode_length(uint32_t field_id, ProtoL } return true; } -void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer &buffer) const { +uint8_t *VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); for (auto &it : this->available_wake_words) { - buffer.encode_sub_message(1, it); + ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 1, it); } for (const auto &it : *this->active_wake_words) { - buffer.encode_string(2, it, true); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 2, it, true); } - buffer.encode_uint32(3, this->max_active_wake_words); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, this->max_active_wake_words); + return pos; } uint32_t VoiceAssistantConfigurationResponse::calculate_size() const { uint32_t size = 0; @@ -2940,32 +3077,34 @@ bool VoiceAssistantSetConfiguration::decode_length(uint32_t field_id, ProtoLengt } #endif #ifdef USE_ALARM_CONTROL_PANEL -void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); +uint8_t *ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 5, this->icon); #endif - buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_uint32(7, static_cast(this->entity_category)); - buffer.encode_uint32(8, this->supported_features); - buffer.encode_bool(9, this->requires_code); - buffer.encode_bool(10, this->requires_code_to_arm); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 6, this->disabled_by_default); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 7, static_cast(this->entity_category)); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 8, this->supported_features); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 9, this->requires_code); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 10, this->requires_code_to_arm); #ifdef USE_DEVICES - buffer.encode_uint32(11, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 11, this->device_id); #endif + return pos; } uint32_t ListEntitiesAlarmControlPanelResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif size += ProtoSize::calc_bool(1, this->disabled_by_default); - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); + size += this->entity_category ? 2 : 0; size += ProtoSize::calc_uint32(1, this->supported_features); size += ProtoSize::calc_bool(1, this->requires_code); size += ProtoSize::calc_bool(1, this->requires_code_to_arm); @@ -2974,17 +3113,19 @@ uint32_t ListEntitiesAlarmControlPanelResponse::calculate_size() const { #endif return size; } -void AlarmControlPanelStateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_uint32(2, static_cast(this->state)); +uint8_t *AlarmControlPanelStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, static_cast(this->state)); #ifdef USE_DEVICES - buffer.encode_uint32(3, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, this->device_id); #endif + return pos; } uint32_t AlarmControlPanelStateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_uint32(1, static_cast(this->state)); + size += 5; + size += this->state ? 2 : 0; #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); #endif @@ -3028,53 +3169,57 @@ bool AlarmControlPanelCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit } #endif #ifdef USE_TEXT -void ListEntitiesTextResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); +uint8_t *ListEntitiesTextResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 5, this->icon); #endif - buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_uint32(7, static_cast(this->entity_category)); - buffer.encode_uint32(8, this->min_length); - buffer.encode_uint32(9, this->max_length); - buffer.encode_string(10, this->pattern); - buffer.encode_uint32(11, static_cast(this->mode)); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 6, this->disabled_by_default); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 7, static_cast(this->entity_category)); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 8, this->min_length); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 9, this->max_length); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 10, this->pattern); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 11, static_cast(this->mode)); #ifdef USE_DEVICES - buffer.encode_uint32(12, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 12, this->device_id); #endif + return pos; } uint32_t ListEntitiesTextResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif size += ProtoSize::calc_bool(1, this->disabled_by_default); - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); + size += this->entity_category ? 2 : 0; size += ProtoSize::calc_uint32(1, this->min_length); size += ProtoSize::calc_uint32(1, this->max_length); size += ProtoSize::calc_length(1, this->pattern.size()); - size += ProtoSize::calc_uint32(1, static_cast(this->mode)); + size += this->mode ? 2 : 0; #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); #endif return size; } -void TextStateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_string(2, this->state); - buffer.encode_bool(3, this->missing_state); +uint8_t *TextStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 2, this->state); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 3, this->missing_state); #ifdef USE_DEVICES - buffer.encode_uint32(4, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 4, this->device_id); #endif + return pos; } uint32_t TextStateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_fixed32(1, this->key); + size += 5; size += ProtoSize::calc_length(1, this->state.size()); size += ProtoSize::calc_bool(1, this->missing_state); #ifdef USE_DEVICES @@ -3117,47 +3262,51 @@ bool TextCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { } #endif #ifdef USE_DATETIME_DATE -void ListEntitiesDateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); +uint8_t *ListEntitiesDateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 5, this->icon); #endif - buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_uint32(7, static_cast(this->entity_category)); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 6, this->disabled_by_default); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 7, static_cast(this->entity_category)); #ifdef USE_DEVICES - buffer.encode_uint32(8, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 8, this->device_id); #endif + return pos; } uint32_t ListEntitiesDateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif size += ProtoSize::calc_bool(1, this->disabled_by_default); - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); + size += this->entity_category ? 2 : 0; #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); #endif return size; } -void DateStateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_bool(2, this->missing_state); - buffer.encode_uint32(3, this->year); - buffer.encode_uint32(4, this->month); - buffer.encode_uint32(5, this->day); +uint8_t *DateStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, this->missing_state); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, this->year); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 4, this->month); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 5, this->day); #ifdef USE_DEVICES - buffer.encode_uint32(6, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 6, this->device_id); #endif + return pos; } uint32_t DateStateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_fixed32(1, this->key); + size += 5; size += ProtoSize::calc_bool(1, this->missing_state); size += ProtoSize::calc_uint32(1, this->year); size += ProtoSize::calc_uint32(1, this->month); @@ -3200,47 +3349,51 @@ bool DateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { } #endif #ifdef USE_DATETIME_TIME -void ListEntitiesTimeResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); +uint8_t *ListEntitiesTimeResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 5, this->icon); #endif - buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_uint32(7, static_cast(this->entity_category)); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 6, this->disabled_by_default); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 7, static_cast(this->entity_category)); #ifdef USE_DEVICES - buffer.encode_uint32(8, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 8, this->device_id); #endif + return pos; } uint32_t ListEntitiesTimeResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif size += ProtoSize::calc_bool(1, this->disabled_by_default); - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); + size += this->entity_category ? 2 : 0; #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); #endif return size; } -void TimeStateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_bool(2, this->missing_state); - buffer.encode_uint32(3, this->hour); - buffer.encode_uint32(4, this->minute); - buffer.encode_uint32(5, this->second); +uint8_t *TimeStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, this->missing_state); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, this->hour); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 4, this->minute); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 5, this->second); #ifdef USE_DEVICES - buffer.encode_uint32(6, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 6, this->device_id); #endif + return pos; } uint32_t TimeStateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_fixed32(1, this->key); + size += 5; size += ProtoSize::calc_bool(1, this->missing_state); size += ProtoSize::calc_uint32(1, this->hour); size += ProtoSize::calc_uint32(1, this->minute); @@ -3283,34 +3436,36 @@ bool TimeCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { } #endif #ifdef USE_EVENT -void ListEntitiesEventResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); +uint8_t *ListEntitiesEventResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 5, this->icon); #endif - buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_uint32(7, static_cast(this->entity_category)); - buffer.encode_string(8, this->device_class); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 6, this->disabled_by_default); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 7, static_cast(this->entity_category)); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 8, this->device_class); for (const char *it : *this->event_types) { - buffer.encode_string(9, it, strlen(it), true); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 9, it, strlen(it), true); } #ifdef USE_DEVICES - buffer.encode_uint32(10, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 10, this->device_id); #endif + return pos; } uint32_t ListEntitiesEventResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif size += ProtoSize::calc_bool(1, this->disabled_by_default); - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); - size += ProtoSize::calc_length(1, this->device_class.size()); + size += this->entity_category ? 2 : 0; + size += !this->device_class.empty() ? 2 + this->device_class.size() : 0; if (!this->event_types->empty()) { for (const char *it : *this->event_types) { size += ProtoSize::calc_length_force(1, strlen(it)); @@ -3321,16 +3476,18 @@ uint32_t ListEntitiesEventResponse::calculate_size() const { #endif return size; } -void EventResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_string(2, this->event_type); +uint8_t *EventResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 2, this->event_type); #ifdef USE_DEVICES - buffer.encode_uint32(3, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, this->device_id); #endif + return pos; } uint32_t EventResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_fixed32(1, this->key); + size += 5; size += ProtoSize::calc_length(1, this->event_type.size()); #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); @@ -3339,34 +3496,36 @@ uint32_t EventResponse::calculate_size() const { } #endif #ifdef USE_VALVE -void ListEntitiesValveResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); +uint8_t *ListEntitiesValveResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 5, this->icon); #endif - buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_uint32(7, static_cast(this->entity_category)); - buffer.encode_string(8, this->device_class); - buffer.encode_bool(9, this->assumed_state); - buffer.encode_bool(10, this->supports_position); - buffer.encode_bool(11, this->supports_stop); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 6, this->disabled_by_default); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 7, static_cast(this->entity_category)); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 8, this->device_class); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 9, this->assumed_state); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 10, this->supports_position); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 11, this->supports_stop); #ifdef USE_DEVICES - buffer.encode_uint32(12, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 12, this->device_id); #endif + return pos; } uint32_t ListEntitiesValveResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif size += ProtoSize::calc_bool(1, this->disabled_by_default); - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); - size += ProtoSize::calc_length(1, this->device_class.size()); + size += this->entity_category ? 2 : 0; + size += !this->device_class.empty() ? 2 + this->device_class.size() : 0; size += ProtoSize::calc_bool(1, this->assumed_state); size += ProtoSize::calc_bool(1, this->supports_position); size += ProtoSize::calc_bool(1, this->supports_stop); @@ -3375,19 +3534,21 @@ uint32_t ListEntitiesValveResponse::calculate_size() const { #endif return size; } -void ValveStateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_float(2, this->position); - buffer.encode_uint32(3, static_cast(this->current_operation)); +uint8_t *ValveStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 2, this->position); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, static_cast(this->current_operation)); #ifdef USE_DEVICES - buffer.encode_uint32(4, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 4, this->device_id); #endif + return pos; } uint32_t ValveStateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_fixed32(1, this->key); + size += 5; size += ProtoSize::calc_float(1, this->position); - size += ProtoSize::calc_uint32(1, static_cast(this->current_operation)); + size += this->current_operation ? 2 : 0; #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); #endif @@ -3426,45 +3587,49 @@ bool ValveCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { } #endif #ifdef USE_DATETIME_DATETIME -void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); +uint8_t *ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 5, this->icon); #endif - buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_uint32(7, static_cast(this->entity_category)); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 6, this->disabled_by_default); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 7, static_cast(this->entity_category)); #ifdef USE_DEVICES - buffer.encode_uint32(8, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 8, this->device_id); #endif + return pos; } uint32_t ListEntitiesDateTimeResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif size += ProtoSize::calc_bool(1, this->disabled_by_default); - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); + size += this->entity_category ? 2 : 0; #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); #endif return size; } -void DateTimeStateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_bool(2, this->missing_state); - buffer.encode_fixed32(3, this->epoch_seconds); +uint8_t *DateTimeStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, this->missing_state); + ProtoEncode::encode_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 3, this->epoch_seconds); #ifdef USE_DEVICES - buffer.encode_uint32(4, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 4, this->device_id); #endif + return pos; } uint32_t DateTimeStateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_fixed32(1, this->key); + size += 5; size += ProtoSize::calc_bool(1, this->missing_state); size += ProtoSize::calc_fixed32(1, this->epoch_seconds); #ifdef USE_DEVICES @@ -3499,54 +3664,58 @@ bool DateTimeCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { } #endif #ifdef USE_UPDATE -void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); +uint8_t *ListEntitiesUpdateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(5, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 5, this->icon); #endif - buffer.encode_bool(6, this->disabled_by_default); - buffer.encode_uint32(7, static_cast(this->entity_category)); - buffer.encode_string(8, this->device_class); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 6, this->disabled_by_default); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 7, static_cast(this->entity_category)); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 8, this->device_class); #ifdef USE_DEVICES - buffer.encode_uint32(9, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 9, this->device_id); #endif + return pos; } uint32_t ListEntitiesUpdateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif size += ProtoSize::calc_bool(1, this->disabled_by_default); - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); - size += ProtoSize::calc_length(1, this->device_class.size()); + size += this->entity_category ? 2 : 0; + size += !this->device_class.empty() ? 2 + this->device_class.size() : 0; #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); #endif return size; } -void UpdateStateResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_fixed32(1, this->key); - buffer.encode_bool(2, this->missing_state); - buffer.encode_bool(3, this->in_progress); - buffer.encode_bool(4, this->has_progress); - buffer.encode_float(5, this->progress); - buffer.encode_string(6, this->current_version); - buffer.encode_string(7, this->latest_version); - buffer.encode_string(8, this->title); - buffer.encode_string(9, this->release_summary); - buffer.encode_string(10, this->release_url); +uint8_t *UpdateStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, this->key); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, this->missing_state); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 3, this->in_progress); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 4, this->has_progress); + ProtoEncode::encode_float(pos PROTO_ENCODE_DEBUG_ARG, 5, this->progress); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 6, this->current_version); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 7, this->latest_version); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 8, this->title); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 9, this->release_summary); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 10, this->release_url); #ifdef USE_DEVICES - buffer.encode_uint32(11, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 11, this->device_id); #endif + return pos; } uint32_t UpdateStateResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_fixed32(1, this->key); + size += 5; size += ProtoSize::calc_bool(1, this->missing_state); size += ProtoSize::calc_bool(1, this->in_progress); size += ProtoSize::calc_bool(1, this->has_progress); @@ -3600,7 +3769,11 @@ bool ZWaveProxyFrame::decode_length(uint32_t field_id, ProtoLengthDelimited valu } return true; } -void ZWaveProxyFrame::encode(ProtoWriteBuffer &buffer) const { buffer.encode_bytes(1, this->data, this->data_len); } +uint8_t *ZWaveProxyFrame::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_bytes(pos PROTO_ENCODE_DEBUG_ARG, 1, this->data, this->data_len); + return pos; +} uint32_t ZWaveProxyFrame::calculate_size() const { uint32_t size = 0; size += ProtoSize::calc_length(1, this->data_len); @@ -3628,46 +3801,52 @@ bool ZWaveProxyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited va } return true; } -void ZWaveProxyRequest::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint32(1, static_cast(this->type)); - buffer.encode_bytes(2, this->data, this->data_len); +uint8_t *ZWaveProxyRequest::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, static_cast(this->type)); + ProtoEncode::encode_bytes(pos PROTO_ENCODE_DEBUG_ARG, 2, this->data, this->data_len); + return pos; } uint32_t ZWaveProxyRequest::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_uint32(1, static_cast(this->type)); + size += this->type ? 2 : 0; size += ProtoSize::calc_length(1, this->data_len); return size; } #endif #ifdef USE_INFRARED -void ListEntitiesInfraredResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_string(1, this->object_id); - buffer.encode_fixed32(2, this->key); - buffer.encode_string(3, this->name); +uint8_t *ListEntitiesInfraredResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); + ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name); #ifdef USE_ENTITY_ICON - buffer.encode_string(4, this->icon); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 4, this->icon); #endif - buffer.encode_bool(5, this->disabled_by_default); - buffer.encode_uint32(6, static_cast(this->entity_category)); + ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 5, this->disabled_by_default); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 6, static_cast(this->entity_category)); #ifdef USE_DEVICES - buffer.encode_uint32(7, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 7, this->device_id); #endif - buffer.encode_uint32(8, this->capabilities); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 8, this->capabilities); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 9, this->receiver_frequency); + return pos; } uint32_t ListEntitiesInfraredResponse::calculate_size() const { uint32_t size = 0; - size += ProtoSize::calc_length(1, this->object_id.size()); - size += ProtoSize::calc_fixed32(1, this->key); - size += ProtoSize::calc_length(1, this->name.size()); + size += 2 + this->object_id.size(); + size += 5; + size += 2 + this->name.size(); #ifdef USE_ENTITY_ICON - size += ProtoSize::calc_length(1, this->icon.size()); + size += !this->icon.empty() ? 2 + this->icon.size() : 0; #endif size += ProtoSize::calc_bool(1, this->disabled_by_default); - size += ProtoSize::calc_uint32(1, static_cast(this->entity_category)); + size += this->entity_category ? 2 : 0; #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); #endif size += ProtoSize::calc_uint32(1, this->capabilities); + size += ProtoSize::calc_uint32(1, this->receiver_frequency); return size; } #endif @@ -3713,21 +3892,23 @@ bool InfraredRFTransmitRawTimingsRequest::decode_32bit(uint32_t field_id, Proto3 } return true; } -void InfraredRFReceiveEvent::encode(ProtoWriteBuffer &buffer) const { +uint8_t *InfraredRFReceiveEvent::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); #ifdef USE_DEVICES - buffer.encode_uint32(1, this->device_id); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, this->device_id); #endif - buffer.encode_fixed32(2, this->key); + ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key); for (const auto &it : *this->timings) { - buffer.encode_sint32(3, it, true); + ProtoEncode::encode_sint32(pos PROTO_ENCODE_DEBUG_ARG, 3, it, true); } + return pos; } uint32_t InfraredRFReceiveEvent::calculate_size() const { uint32_t size = 0; #ifdef USE_DEVICES size += ProtoSize::calc_uint32(1, this->device_id); #endif - size += ProtoSize::calc_fixed32(1, this->key); + size += 5; if (!this->timings->empty()) { for (const auto &it : *this->timings) { size += ProtoSize::calc_sint32_force(1, it); @@ -3762,9 +3943,11 @@ bool SerialProxyConfigureRequest::decode_varint(uint32_t field_id, proto_varint_ } return true; } -void SerialProxyDataReceived::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint32(1, this->instance); - buffer.encode_bytes(2, this->data_ptr_, this->data_len_); +uint8_t *SerialProxyDataReceived::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, this->instance); + ProtoEncode::encode_bytes(pos PROTO_ENCODE_DEBUG_ARG, 2, this->data_ptr_, this->data_len_); + return pos; } uint32_t SerialProxyDataReceived::calculate_size() const { uint32_t size = 0; @@ -3817,9 +4000,11 @@ bool SerialProxyGetModemPinsRequest::decode_varint(uint32_t field_id, proto_vari } return true; } -void SerialProxyGetModemPinsResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint32(1, this->instance); - buffer.encode_uint32(2, this->line_states); +uint8_t *SerialProxyGetModemPinsResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, this->instance); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, this->line_states); + return pos; } uint32_t SerialProxyGetModemPinsResponse::calculate_size() const { uint32_t size = 0; @@ -3840,17 +4025,19 @@ bool SerialProxyRequest::decode_varint(uint32_t field_id, proto_varint_value_t v } return true; } -void SerialProxyRequestResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint32(1, this->instance); - buffer.encode_uint32(2, static_cast(this->type)); - buffer.encode_uint32(3, static_cast(this->status)); - buffer.encode_string(4, this->error_message); +uint8_t *SerialProxyRequestResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, this->instance); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, static_cast(this->type)); + ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, static_cast(this->status)); + ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 4, this->error_message); + return pos; } uint32_t SerialProxyRequestResponse::calculate_size() const { uint32_t size = 0; size += ProtoSize::calc_uint32(1, this->instance); - size += ProtoSize::calc_uint32(1, static_cast(this->type)); - size += ProtoSize::calc_uint32(1, static_cast(this->status)); + size += this->type ? 2 : 0; + size += this->status ? 2 : 0; size += ProtoSize::calc_length(1, this->error_message.size()); return size; } @@ -3878,9 +4065,11 @@ bool BluetoothSetConnectionParamsRequest::decode_varint(uint32_t field_id, proto } return true; } -void BluetoothSetConnectionParamsResponse::encode(ProtoWriteBuffer &buffer) const { - buffer.encode_uint64(1, this->address); - buffer.encode_int32(2, this->error); +uint8_t *BluetoothSetConnectionParamsResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + uint8_t *__restrict__ pos = buffer.get_pos(); + ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, this->address); + ProtoEncode::encode_int32(pos PROTO_ENCODE_DEBUG_ARG, 2, this->error); + return pos; } uint32_t BluetoothSetConnectionParamsResponse::calculate_size() const { uint32_t size = 0; diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index a4ee0adb8b..3b239db36c 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -388,7 +388,7 @@ class HelloRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 1; static constexpr uint8_t ESTIMATED_SIZE = 17; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "hello_request"; } + const LogString *message_name() const override { return LOG_STR("hello_request"); } #endif StringRef client_info{}; uint32_t api_version_major{0}; @@ -406,13 +406,13 @@ class HelloResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 2; static constexpr uint8_t ESTIMATED_SIZE = 26; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "hello_response"; } + const LogString *message_name() const override { return LOG_STR("hello_response"); } #endif uint32_t api_version_major{0}; uint32_t api_version_minor{0}; StringRef server_info{}; StringRef name{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -425,7 +425,7 @@ class DisconnectRequest final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 5; static constexpr uint8_t ESTIMATED_SIZE = 0; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "disconnect_request"; } + const LogString *message_name() const override { return LOG_STR("disconnect_request"); } #endif #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -438,7 +438,7 @@ class DisconnectResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 6; static constexpr uint8_t ESTIMATED_SIZE = 0; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "disconnect_response"; } + const LogString *message_name() const override { return LOG_STR("disconnect_response"); } #endif #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -451,7 +451,7 @@ class PingRequest final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 7; static constexpr uint8_t ESTIMATED_SIZE = 0; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "ping_request"; } + const LogString *message_name() const override { return LOG_STR("ping_request"); } #endif #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -464,7 +464,7 @@ class PingResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 8; static constexpr uint8_t ESTIMATED_SIZE = 0; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "ping_response"; } + const LogString *message_name() const override { return LOG_STR("ping_response"); } #endif #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -477,7 +477,7 @@ class AreaInfo final : public ProtoMessage { public: uint32_t area_id{0}; StringRef name{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -492,7 +492,7 @@ class DeviceInfo final : public ProtoMessage { uint32_t device_id{0}; StringRef name{}; uint32_t area_id{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -506,7 +506,7 @@ class SerialProxyInfo final : public ProtoMessage { public: StringRef name{}; enums::SerialProxyPortType port_type{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -520,7 +520,7 @@ class DeviceInfoResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 10; static constexpr uint16_t ESTIMATED_SIZE = 309; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "device_info_response"; } + const LogString *message_name() const override { return LOG_STR("device_info_response"); } #endif StringRef name{}; StringRef mac_address{}; @@ -574,7 +574,7 @@ class DeviceInfoResponse final : public ProtoMessage { #ifdef USE_SERIAL_PROXY std::array serial_proxies{}; #endif - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -587,7 +587,7 @@ class ListEntitiesDoneResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 19; static constexpr uint8_t ESTIMATED_SIZE = 0; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_done_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_done_response"); } #endif #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -601,11 +601,11 @@ class ListEntitiesBinarySensorResponse final : public InfoResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 12; static constexpr uint8_t ESTIMATED_SIZE = 51; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_binary_sensor_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_binary_sensor_response"); } #endif StringRef device_class{}; bool is_status_binary_sensor{false}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -618,11 +618,11 @@ class BinarySensorStateResponse final : public StateResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 21; static constexpr uint8_t ESTIMATED_SIZE = 13; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "binary_sensor_state_response"; } + const LogString *message_name() const override { return LOG_STR("binary_sensor_state_response"); } #endif bool state{false}; bool missing_state{false}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -637,14 +637,14 @@ class ListEntitiesCoverResponse final : public InfoResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 13; static constexpr uint8_t ESTIMATED_SIZE = 57; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_cover_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_cover_response"); } #endif bool assumed_state{false}; bool supports_position{false}; bool supports_tilt{false}; StringRef device_class{}; bool supports_stop{false}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -657,12 +657,12 @@ class CoverStateResponse final : public StateResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 22; static constexpr uint8_t ESTIMATED_SIZE = 21; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "cover_state_response"; } + const LogString *message_name() const override { return LOG_STR("cover_state_response"); } #endif float position{0.0f}; float tilt{0.0f}; enums::CoverOperation current_operation{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -675,7 +675,7 @@ class CoverCommandRequest final : public CommandProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 30; static constexpr uint8_t ESTIMATED_SIZE = 25; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "cover_command_request"; } + const LogString *message_name() const override { return LOG_STR("cover_command_request"); } #endif bool has_position{false}; float position{0.0f}; @@ -697,14 +697,14 @@ class ListEntitiesFanResponse final : public InfoResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 14; static constexpr uint8_t ESTIMATED_SIZE = 68; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_fan_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_fan_response"); } #endif bool supports_oscillation{false}; bool supports_speed{false}; bool supports_direction{false}; int32_t supported_speed_count{0}; const std::vector *supported_preset_modes{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -717,14 +717,14 @@ class FanStateResponse final : public StateResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 23; static constexpr uint8_t ESTIMATED_SIZE = 28; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "fan_state_response"; } + const LogString *message_name() const override { return LOG_STR("fan_state_response"); } #endif bool state{false}; bool oscillating{false}; enums::FanDirection direction{}; int32_t speed_level{0}; StringRef preset_mode{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -737,7 +737,7 @@ class FanCommandRequest final : public CommandProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 31; static constexpr uint8_t ESTIMATED_SIZE = 38; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "fan_command_request"; } + const LogString *message_name() const override { return LOG_STR("fan_command_request"); } #endif bool has_state{false}; bool state{false}; @@ -765,13 +765,13 @@ class ListEntitiesLightResponse final : public InfoResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 15; static constexpr uint8_t ESTIMATED_SIZE = 73; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_light_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_light_response"); } #endif const light::ColorModeMask *supported_color_modes{}; float min_mireds{0.0f}; float max_mireds{0.0f}; const FixedVector *effects{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -784,7 +784,7 @@ class LightStateResponse final : public StateResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 24; static constexpr uint8_t ESTIMATED_SIZE = 67; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "light_state_response"; } + const LogString *message_name() const override { return LOG_STR("light_state_response"); } #endif bool state{false}; float brightness{0.0f}; @@ -798,7 +798,7 @@ class LightStateResponse final : public StateResponseProtoMessage { float cold_white{0.0f}; float warm_white{0.0f}; StringRef effect{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -811,7 +811,7 @@ class LightCommandRequest final : public CommandProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 32; static constexpr uint8_t ESTIMATED_SIZE = 112; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "light_command_request"; } + const LogString *message_name() const override { return LOG_STR("light_command_request"); } #endif bool has_state{false}; bool state{false}; @@ -855,14 +855,14 @@ class ListEntitiesSensorResponse final : public InfoResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 16; static constexpr uint8_t ESTIMATED_SIZE = 66; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_sensor_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_sensor_response"); } #endif StringRef unit_of_measurement{}; int32_t accuracy_decimals{0}; bool force_update{false}; StringRef device_class{}; enums::SensorStateClass state_class{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -875,11 +875,11 @@ class SensorStateResponse final : public StateResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 25; static constexpr uint8_t ESTIMATED_SIZE = 16; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "sensor_state_response"; } + const LogString *message_name() const override { return LOG_STR("sensor_state_response"); } #endif float state{0.0f}; bool missing_state{false}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -894,11 +894,11 @@ class ListEntitiesSwitchResponse final : public InfoResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 17; static constexpr uint8_t ESTIMATED_SIZE = 51; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_switch_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_switch_response"); } #endif bool assumed_state{false}; StringRef device_class{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -911,10 +911,10 @@ class SwitchStateResponse final : public StateResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 26; static constexpr uint8_t ESTIMATED_SIZE = 11; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "switch_state_response"; } + const LogString *message_name() const override { return LOG_STR("switch_state_response"); } #endif bool state{false}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -927,7 +927,7 @@ class SwitchCommandRequest final : public CommandProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 33; static constexpr uint8_t ESTIMATED_SIZE = 11; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "switch_command_request"; } + const LogString *message_name() const override { return LOG_STR("switch_command_request"); } #endif bool state{false}; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -945,10 +945,10 @@ class ListEntitiesTextSensorResponse final : public InfoResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 18; static constexpr uint8_t ESTIMATED_SIZE = 49; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_text_sensor_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_text_sensor_response"); } #endif StringRef device_class{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -961,11 +961,11 @@ class TextSensorStateResponse final : public StateResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 27; static constexpr uint8_t ESTIMATED_SIZE = 20; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "text_sensor_state_response"; } + const LogString *message_name() const override { return LOG_STR("text_sensor_state_response"); } #endif StringRef state{}; bool missing_state{false}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -979,7 +979,7 @@ class SubscribeLogsRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 28; static constexpr uint8_t ESTIMATED_SIZE = 4; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "subscribe_logs_request"; } + const LogString *message_name() const override { return LOG_STR("subscribe_logs_request"); } #endif enums::LogLevel level{}; bool dump_config{false}; @@ -995,7 +995,7 @@ class SubscribeLogsResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 29; static constexpr uint8_t ESTIMATED_SIZE = 21; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "subscribe_logs_response"; } + const LogString *message_name() const override { return LOG_STR("subscribe_logs_response"); } #endif enums::LogLevel level{}; const uint8_t *message_ptr_{nullptr}; @@ -1004,7 +1004,7 @@ class SubscribeLogsResponse final : public ProtoMessage { this->message_ptr_ = data; this->message_len_ = len; } - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1018,7 +1018,7 @@ class NoiseEncryptionSetKeyRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 124; static constexpr uint8_t ESTIMATED_SIZE = 19; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "noise_encryption_set_key_request"; } + const LogString *message_name() const override { return LOG_STR("noise_encryption_set_key_request"); } #endif const uint8_t *key{nullptr}; uint16_t key_len{0}; @@ -1034,10 +1034,10 @@ class NoiseEncryptionSetKeyResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 125; static constexpr uint8_t ESTIMATED_SIZE = 2; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "noise_encryption_set_key_response"; } + const LogString *message_name() const override { return LOG_STR("noise_encryption_set_key_response"); } #endif bool success{false}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1051,7 +1051,7 @@ class HomeassistantServiceMap final : public ProtoMessage { public: StringRef key{}; StringRef value{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1064,7 +1064,7 @@ class HomeassistantActionRequest final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 35; static constexpr uint8_t ESTIMATED_SIZE = 128; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "homeassistant_action_request"; } + const LogString *message_name() const override { return LOG_STR("homeassistant_action_request"); } #endif StringRef service{}; FixedVector data{}; @@ -1080,7 +1080,7 @@ class HomeassistantActionRequest final : public ProtoMessage { #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON StringRef response_template{}; #endif - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1095,7 +1095,7 @@ class HomeassistantActionResponse final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 130; static constexpr uint8_t ESTIMATED_SIZE = 34; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "homeassistant_action_response"; } + const LogString *message_name() const override { return LOG_STR("homeassistant_action_response"); } #endif uint32_t call_id{0}; bool success{false}; @@ -1119,12 +1119,12 @@ class SubscribeHomeAssistantStateResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 39; static constexpr uint8_t ESTIMATED_SIZE = 20; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "subscribe_home_assistant_state_response"; } + const LogString *message_name() const override { return LOG_STR("subscribe_home_assistant_state_response"); } #endif StringRef entity_id{}; StringRef attribute{}; bool once{false}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1137,7 +1137,7 @@ class HomeAssistantStateResponse final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 40; static constexpr uint8_t ESTIMATED_SIZE = 27; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "home_assistant_state_response"; } + const LogString *message_name() const override { return LOG_STR("home_assistant_state_response"); } #endif StringRef entity_id{}; StringRef state{}; @@ -1155,7 +1155,7 @@ class GetTimeRequest final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 36; static constexpr uint8_t ESTIMATED_SIZE = 0; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "get_time_request"; } + const LogString *message_name() const override { return LOG_STR("get_time_request"); } #endif #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1197,7 +1197,7 @@ class GetTimeResponse final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 37; static constexpr uint8_t ESTIMATED_SIZE = 31; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "get_time_response"; } + const LogString *message_name() const override { return LOG_STR("get_time_response"); } #endif uint32_t epoch_seconds{0}; StringRef timezone{}; @@ -1215,7 +1215,7 @@ class ListEntitiesServicesArgument final : public ProtoMessage { public: StringRef name{}; enums::ServiceArgType type{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1228,13 +1228,13 @@ class ListEntitiesServicesResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 41; static constexpr uint8_t ESTIMATED_SIZE = 50; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_services_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_services_response"); } #endif StringRef name{}; uint32_t key{0}; FixedVector args{}; enums::SupportsResponseType supports_response{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1253,7 +1253,7 @@ class ExecuteServiceArgument final : public ProtoDecodableMessage { FixedVector int_array{}; FixedVector float_array{}; FixedVector string_array{}; - void decode(const uint8_t *buffer, size_t length) override; + void decode(const uint8_t *buffer, size_t length); #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif @@ -1268,7 +1268,7 @@ class ExecuteServiceRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 42; static constexpr uint8_t ESTIMATED_SIZE = 45; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "execute_service_request"; } + const LogString *message_name() const override { return LOG_STR("execute_service_request"); } #endif uint32_t key{0}; FixedVector args{}; @@ -1278,7 +1278,7 @@ class ExecuteServiceRequest final : public ProtoDecodableMessage { #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES bool return_response{false}; #endif - void decode(const uint8_t *buffer, size_t length) override; + void decode(const uint8_t *buffer, size_t length); #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif @@ -1295,7 +1295,7 @@ class ExecuteServiceResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 131; static constexpr uint8_t ESTIMATED_SIZE = 34; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "execute_service_response"; } + const LogString *message_name() const override { return LOG_STR("execute_service_response"); } #endif uint32_t call_id{0}; bool success{false}; @@ -1304,7 +1304,7 @@ class ExecuteServiceResponse final : public ProtoMessage { const uint8_t *response_data{nullptr}; uint16_t response_data_len{0}; #endif - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1319,9 +1319,9 @@ class ListEntitiesCameraResponse final : public InfoResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 43; static constexpr uint8_t ESTIMATED_SIZE = 40; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_camera_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_camera_response"); } #endif - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1334,7 +1334,7 @@ class CameraImageResponse final : public StateResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 44; static constexpr uint8_t ESTIMATED_SIZE = 30; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "camera_image_response"; } + const LogString *message_name() const override { return LOG_STR("camera_image_response"); } #endif const uint8_t *data_ptr_{nullptr}; size_t data_len_{0}; @@ -1343,7 +1343,7 @@ class CameraImageResponse final : public StateResponseProtoMessage { this->data_len_ = len; } bool done{false}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1356,7 +1356,7 @@ class CameraImageRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 45; static constexpr uint8_t ESTIMATED_SIZE = 4; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "camera_image_request"; } + const LogString *message_name() const override { return LOG_STR("camera_image_request"); } #endif bool single{false}; bool stream{false}; @@ -1374,7 +1374,7 @@ class ListEntitiesClimateResponse final : public InfoResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 46; static constexpr uint8_t ESTIMATED_SIZE = 150; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_climate_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_climate_response"); } #endif bool supports_current_temperature{false}; bool supports_two_point_target_temperature{false}; @@ -1394,7 +1394,7 @@ class ListEntitiesClimateResponse final : public InfoResponseProtoMessage { float visual_min_humidity{0.0f}; float visual_max_humidity{0.0f}; uint32_t feature_flags{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1407,7 +1407,7 @@ class ClimateStateResponse final : public StateResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 47; static constexpr uint8_t ESTIMATED_SIZE = 68; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "climate_state_response"; } + const LogString *message_name() const override { return LOG_STR("climate_state_response"); } #endif enums::ClimateMode mode{}; float current_temperature{0.0f}; @@ -1422,7 +1422,7 @@ class ClimateStateResponse final : public StateResponseProtoMessage { StringRef custom_preset{}; float current_humidity{0.0f}; float target_humidity{0.0f}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1435,7 +1435,7 @@ class ClimateCommandRequest final : public CommandProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 48; static constexpr uint8_t ESTIMATED_SIZE = 84; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "climate_command_request"; } + const LogString *message_name() const override { return LOG_STR("climate_command_request"); } #endif bool has_mode{false}; enums::ClimateMode mode{}; @@ -1473,14 +1473,14 @@ class ListEntitiesWaterHeaterResponse final : public InfoResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 132; static constexpr uint8_t ESTIMATED_SIZE = 63; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_water_heater_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_water_heater_response"); } #endif float min_temperature{0.0f}; float max_temperature{0.0f}; float target_temperature_step{0.0f}; const water_heater::WaterHeaterModeMask *supported_modes{}; uint32_t supported_features{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1493,7 +1493,7 @@ class WaterHeaterStateResponse final : public StateResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 133; static constexpr uint8_t ESTIMATED_SIZE = 35; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "water_heater_state_response"; } + const LogString *message_name() const override { return LOG_STR("water_heater_state_response"); } #endif float current_temperature{0.0f}; float target_temperature{0.0f}; @@ -1501,7 +1501,7 @@ class WaterHeaterStateResponse final : public StateResponseProtoMessage { uint32_t state{0}; float target_temperature_low{0.0f}; float target_temperature_high{0.0f}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1514,7 +1514,7 @@ class WaterHeaterCommandRequest final : public CommandProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 134; static constexpr uint8_t ESTIMATED_SIZE = 34; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "water_heater_command_request"; } + const LogString *message_name() const override { return LOG_STR("water_heater_command_request"); } #endif uint32_t has_fields{0}; enums::WaterHeaterMode mode{}; @@ -1537,7 +1537,7 @@ class ListEntitiesNumberResponse final : public InfoResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 49; static constexpr uint8_t ESTIMATED_SIZE = 75; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_number_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_number_response"); } #endif float min_value{0.0f}; float max_value{0.0f}; @@ -1545,7 +1545,7 @@ class ListEntitiesNumberResponse final : public InfoResponseProtoMessage { StringRef unit_of_measurement{}; enums::NumberMode mode{}; StringRef device_class{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1558,11 +1558,11 @@ class NumberStateResponse final : public StateResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 50; static constexpr uint8_t ESTIMATED_SIZE = 16; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "number_state_response"; } + const LogString *message_name() const override { return LOG_STR("number_state_response"); } #endif float state{0.0f}; bool missing_state{false}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1575,7 +1575,7 @@ class NumberCommandRequest final : public CommandProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 51; static constexpr uint8_t ESTIMATED_SIZE = 14; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "number_command_request"; } + const LogString *message_name() const override { return LOG_STR("number_command_request"); } #endif float state{0.0f}; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1593,10 +1593,10 @@ class ListEntitiesSelectResponse final : public InfoResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 52; static constexpr uint8_t ESTIMATED_SIZE = 58; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_select_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_select_response"); } #endif const FixedVector *options{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1609,11 +1609,11 @@ class SelectStateResponse final : public StateResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 53; static constexpr uint8_t ESTIMATED_SIZE = 20; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "select_state_response"; } + const LogString *message_name() const override { return LOG_STR("select_state_response"); } #endif StringRef state{}; bool missing_state{false}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1626,7 +1626,7 @@ class SelectCommandRequest final : public CommandProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 54; static constexpr uint8_t ESTIMATED_SIZE = 18; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "select_command_request"; } + const LogString *message_name() const override { return LOG_STR("select_command_request"); } #endif StringRef state{}; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1645,12 +1645,12 @@ class ListEntitiesSirenResponse final : public InfoResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 55; static constexpr uint8_t ESTIMATED_SIZE = 62; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_siren_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_siren_response"); } #endif const FixedVector *tones{}; bool supports_duration{false}; bool supports_volume{false}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1663,10 +1663,10 @@ class SirenStateResponse final : public StateResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 56; static constexpr uint8_t ESTIMATED_SIZE = 11; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "siren_state_response"; } + const LogString *message_name() const override { return LOG_STR("siren_state_response"); } #endif bool state{false}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1679,7 +1679,7 @@ class SirenCommandRequest final : public CommandProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 57; static constexpr uint8_t ESTIMATED_SIZE = 37; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "siren_command_request"; } + const LogString *message_name() const override { return LOG_STR("siren_command_request"); } #endif bool has_state{false}; bool state{false}; @@ -1705,13 +1705,13 @@ class ListEntitiesLockResponse final : public InfoResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 58; static constexpr uint8_t ESTIMATED_SIZE = 55; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_lock_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_lock_response"); } #endif bool assumed_state{false}; bool supports_open{false}; bool requires_code{false}; StringRef code_format{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1724,10 +1724,10 @@ class LockStateResponse final : public StateResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 59; static constexpr uint8_t ESTIMATED_SIZE = 11; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "lock_state_response"; } + const LogString *message_name() const override { return LOG_STR("lock_state_response"); } #endif enums::LockState state{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1740,7 +1740,7 @@ class LockCommandRequest final : public CommandProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 60; static constexpr uint8_t ESTIMATED_SIZE = 22; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "lock_command_request"; } + const LogString *message_name() const override { return LOG_STR("lock_command_request"); } #endif enums::LockCommand command{}; bool has_code{false}; @@ -1761,10 +1761,10 @@ class ListEntitiesButtonResponse final : public InfoResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 61; static constexpr uint8_t ESTIMATED_SIZE = 49; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_button_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_button_response"); } #endif StringRef device_class{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1777,7 +1777,7 @@ class ButtonCommandRequest final : public CommandProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 62; static constexpr uint8_t ESTIMATED_SIZE = 9; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "button_command_request"; } + const LogString *message_name() const override { return LOG_STR("button_command_request"); } #endif #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1796,7 +1796,7 @@ class MediaPlayerSupportedFormat final : public ProtoMessage { uint32_t num_channels{0}; enums::MediaPlayerFormatPurpose purpose{}; uint32_t sample_bytes{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1809,12 +1809,12 @@ class ListEntitiesMediaPlayerResponse final : public InfoResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 63; static constexpr uint8_t ESTIMATED_SIZE = 80; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_media_player_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_media_player_response"); } #endif bool supports_pause{false}; std::vector supported_formats{}; uint32_t feature_flags{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1827,12 +1827,12 @@ class MediaPlayerStateResponse final : public StateResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 64; static constexpr uint8_t ESTIMATED_SIZE = 18; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "media_player_state_response"; } + const LogString *message_name() const override { return LOG_STR("media_player_state_response"); } #endif enums::MediaPlayerState state{}; float volume{0.0f}; bool muted{false}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1845,7 +1845,7 @@ class MediaPlayerCommandRequest final : public CommandProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 65; static constexpr uint8_t ESTIMATED_SIZE = 35; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "media_player_command_request"; } + const LogString *message_name() const override { return LOG_STR("media_player_command_request"); } #endif bool has_command{false}; enums::MediaPlayerCommand command{}; @@ -1871,7 +1871,7 @@ class SubscribeBluetoothLEAdvertisementsRequest final : public ProtoDecodableMes static constexpr uint8_t MESSAGE_TYPE = 66; static constexpr uint8_t ESTIMATED_SIZE = 4; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "subscribe_bluetooth_le_advertisements_request"; } + const LogString *message_name() const override { return LOG_STR("subscribe_bluetooth_le_advertisements_request"); } #endif uint32_t flags{0}; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1888,7 +1888,7 @@ class BluetoothLERawAdvertisement final : public ProtoMessage { uint32_t address_type{0}; uint8_t data[62]{}; uint8_t data_len{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1901,11 +1901,11 @@ class BluetoothLERawAdvertisementsResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 93; static constexpr uint8_t ESTIMATED_SIZE = 136; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_le_raw_advertisements_response"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_le_raw_advertisements_response"); } #endif std::array advertisements{}; uint16_t advertisements_len{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1918,7 +1918,7 @@ class BluetoothDeviceRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 68; static constexpr uint8_t ESTIMATED_SIZE = 12; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_device_request"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_device_request"); } #endif uint64_t address{0}; enums::BluetoothDeviceRequestType request_type{}; @@ -1936,13 +1936,13 @@ class BluetoothDeviceConnectionResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 69; static constexpr uint8_t ESTIMATED_SIZE = 14; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_device_connection_response"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_device_connection_response"); } #endif uint64_t address{0}; bool connected{false}; uint32_t mtu{0}; int32_t error{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1955,7 +1955,7 @@ class BluetoothGATTGetServicesRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 70; static constexpr uint8_t ESTIMATED_SIZE = 4; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_gatt_get_services_request"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_gatt_get_services_request"); } #endif uint64_t address{0}; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1970,7 +1970,7 @@ class BluetoothGATTDescriptor final : public ProtoMessage { std::array uuid{}; uint32_t handle{0}; uint32_t short_uuid{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1985,7 +1985,7 @@ class BluetoothGATTCharacteristic final : public ProtoMessage { uint32_t properties{0}; FixedVector descriptors{}; uint32_t short_uuid{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -1999,7 +1999,7 @@ class BluetoothGATTService final : public ProtoMessage { uint32_t handle{0}; FixedVector characteristics{}; uint32_t short_uuid{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2012,11 +2012,11 @@ class BluetoothGATTGetServicesResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 71; static constexpr uint8_t ESTIMATED_SIZE = 38; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_gatt_get_services_response"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_gatt_get_services_response"); } #endif uint64_t address{0}; std::vector services{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2029,10 +2029,10 @@ class BluetoothGATTGetServicesDoneResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 72; static constexpr uint8_t ESTIMATED_SIZE = 4; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_gatt_get_services_done_response"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_gatt_get_services_done_response"); } #endif uint64_t address{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2045,7 +2045,7 @@ class BluetoothGATTReadRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 73; static constexpr uint8_t ESTIMATED_SIZE = 8; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_gatt_read_request"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_gatt_read_request"); } #endif uint64_t address{0}; uint32_t handle{0}; @@ -2061,7 +2061,7 @@ class BluetoothGATTReadResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 74; static constexpr uint8_t ESTIMATED_SIZE = 27; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_gatt_read_response"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_gatt_read_response"); } #endif uint64_t address{0}; uint32_t handle{0}; @@ -2071,7 +2071,7 @@ class BluetoothGATTReadResponse final : public ProtoMessage { this->data_ptr_ = data; this->data_len_ = len; } - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2084,7 +2084,7 @@ class BluetoothGATTWriteRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 75; static constexpr uint8_t ESTIMATED_SIZE = 29; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_gatt_write_request"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_gatt_write_request"); } #endif uint64_t address{0}; uint32_t handle{0}; @@ -2104,7 +2104,7 @@ class BluetoothGATTReadDescriptorRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 76; static constexpr uint8_t ESTIMATED_SIZE = 8; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_gatt_read_descriptor_request"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_gatt_read_descriptor_request"); } #endif uint64_t address{0}; uint32_t handle{0}; @@ -2120,7 +2120,7 @@ class BluetoothGATTWriteDescriptorRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 77; static constexpr uint8_t ESTIMATED_SIZE = 27; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_gatt_write_descriptor_request"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_gatt_write_descriptor_request"); } #endif uint64_t address{0}; uint32_t handle{0}; @@ -2139,7 +2139,7 @@ class BluetoothGATTNotifyRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 78; static constexpr uint8_t ESTIMATED_SIZE = 10; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_gatt_notify_request"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_gatt_notify_request"); } #endif uint64_t address{0}; uint32_t handle{0}; @@ -2156,7 +2156,7 @@ class BluetoothGATTNotifyDataResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 79; static constexpr uint8_t ESTIMATED_SIZE = 27; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_gatt_notify_data_response"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_gatt_notify_data_response"); } #endif uint64_t address{0}; uint32_t handle{0}; @@ -2166,7 +2166,7 @@ class BluetoothGATTNotifyDataResponse final : public ProtoMessage { this->data_ptr_ = data; this->data_len_ = len; } - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2179,12 +2179,12 @@ class BluetoothConnectionsFreeResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 81; static constexpr uint8_t ESTIMATED_SIZE = 20; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_connections_free_response"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_connections_free_response"); } #endif uint32_t free{0}; uint32_t limit{0}; std::array allocated{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2197,12 +2197,12 @@ class BluetoothGATTErrorResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 82; static constexpr uint8_t ESTIMATED_SIZE = 12; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_gatt_error_response"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_gatt_error_response"); } #endif uint64_t address{0}; uint32_t handle{0}; int32_t error{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2215,11 +2215,11 @@ class BluetoothGATTWriteResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 83; static constexpr uint8_t ESTIMATED_SIZE = 8; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_gatt_write_response"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_gatt_write_response"); } #endif uint64_t address{0}; uint32_t handle{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2232,11 +2232,11 @@ class BluetoothGATTNotifyResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 84; static constexpr uint8_t ESTIMATED_SIZE = 8; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_gatt_notify_response"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_gatt_notify_response"); } #endif uint64_t address{0}; uint32_t handle{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2249,12 +2249,12 @@ class BluetoothDevicePairingResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 85; static constexpr uint8_t ESTIMATED_SIZE = 10; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_device_pairing_response"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_device_pairing_response"); } #endif uint64_t address{0}; bool paired{false}; int32_t error{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2267,12 +2267,12 @@ class BluetoothDeviceUnpairingResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 86; static constexpr uint8_t ESTIMATED_SIZE = 10; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_device_unpairing_response"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_device_unpairing_response"); } #endif uint64_t address{0}; bool success{false}; int32_t error{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2285,12 +2285,12 @@ class BluetoothDeviceClearCacheResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 88; static constexpr uint8_t ESTIMATED_SIZE = 10; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_device_clear_cache_response"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_device_clear_cache_response"); } #endif uint64_t address{0}; bool success{false}; int32_t error{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2303,12 +2303,12 @@ class BluetoothScannerStateResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 126; static constexpr uint8_t ESTIMATED_SIZE = 6; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_scanner_state_response"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_scanner_state_response"); } #endif enums::BluetoothScannerState state{}; enums::BluetoothScannerMode mode{}; enums::BluetoothScannerMode configured_mode{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2321,7 +2321,7 @@ class BluetoothScannerSetModeRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 127; static constexpr uint8_t ESTIMATED_SIZE = 2; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_scanner_set_mode_request"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_scanner_set_mode_request"); } #endif enums::BluetoothScannerMode mode{}; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2338,7 +2338,7 @@ class SubscribeVoiceAssistantRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 89; static constexpr uint8_t ESTIMATED_SIZE = 6; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "subscribe_voice_assistant_request"; } + const LogString *message_name() const override { return LOG_STR("subscribe_voice_assistant_request"); } #endif bool subscribe{false}; uint32_t flags{0}; @@ -2354,7 +2354,7 @@ class VoiceAssistantAudioSettings final : public ProtoMessage { uint32_t noise_suppression_level{0}; uint32_t auto_gain{0}; float volume_multiplier{0.0f}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2367,14 +2367,14 @@ class VoiceAssistantRequest final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 90; static constexpr uint8_t ESTIMATED_SIZE = 41; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "voice_assistant_request"; } + const LogString *message_name() const override { return LOG_STR("voice_assistant_request"); } #endif bool start{false}; StringRef conversation_id{}; uint32_t flags{0}; VoiceAssistantAudioSettings audio_settings{}; StringRef wake_word_phrase{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2387,7 +2387,7 @@ class VoiceAssistantResponse final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 91; static constexpr uint8_t ESTIMATED_SIZE = 6; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "voice_assistant_response"; } + const LogString *message_name() const override { return LOG_STR("voice_assistant_response"); } #endif uint32_t port{0}; bool error{false}; @@ -2414,7 +2414,7 @@ class VoiceAssistantEventResponse final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 92; static constexpr uint8_t ESTIMATED_SIZE = 36; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "voice_assistant_event_response"; } + const LogString *message_name() const override { return LOG_STR("voice_assistant_event_response"); } #endif enums::VoiceAssistantEvent event_type{}; std::vector data{}; @@ -2431,12 +2431,12 @@ class VoiceAssistantAudio final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 106; static constexpr uint8_t ESTIMATED_SIZE = 21; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "voice_assistant_audio"; } + const LogString *message_name() const override { return LOG_STR("voice_assistant_audio"); } #endif const uint8_t *data{nullptr}; uint16_t data_len{0}; bool end{false}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2451,7 +2451,7 @@ class VoiceAssistantTimerEventResponse final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 115; static constexpr uint8_t ESTIMATED_SIZE = 30; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "voice_assistant_timer_event_response"; } + const LogString *message_name() const override { return LOG_STR("voice_assistant_timer_event_response"); } #endif enums::VoiceAssistantTimerEvent event_type{}; StringRef timer_id{}; @@ -2472,7 +2472,7 @@ class VoiceAssistantAnnounceRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 119; static constexpr uint8_t ESTIMATED_SIZE = 29; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "voice_assistant_announce_request"; } + const LogString *message_name() const override { return LOG_STR("voice_assistant_announce_request"); } #endif StringRef media_id{}; StringRef text{}; @@ -2491,10 +2491,10 @@ class VoiceAssistantAnnounceFinished final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 120; static constexpr uint8_t ESTIMATED_SIZE = 2; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "voice_assistant_announce_finished"; } + const LogString *message_name() const override { return LOG_STR("voice_assistant_announce_finished"); } #endif bool success{false}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2507,7 +2507,7 @@ class VoiceAssistantWakeWord final : public ProtoMessage { StringRef id{}; StringRef wake_word{}; std::vector trained_languages{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2537,7 +2537,7 @@ class VoiceAssistantConfigurationRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 121; static constexpr uint8_t ESTIMATED_SIZE = 34; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "voice_assistant_configuration_request"; } + const LogString *message_name() const override { return LOG_STR("voice_assistant_configuration_request"); } #endif std::vector external_wake_words{}; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2552,12 +2552,12 @@ class VoiceAssistantConfigurationResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 122; static constexpr uint8_t ESTIMATED_SIZE = 56; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "voice_assistant_configuration_response"; } + const LogString *message_name() const override { return LOG_STR("voice_assistant_configuration_response"); } #endif std::vector available_wake_words{}; const std::vector *active_wake_words{}; uint32_t max_active_wake_words{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2570,7 +2570,7 @@ class VoiceAssistantSetConfiguration final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 123; static constexpr uint8_t ESTIMATED_SIZE = 18; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "voice_assistant_set_configuration"; } + const LogString *message_name() const override { return LOG_STR("voice_assistant_set_configuration"); } #endif std::vector active_wake_words{}; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2587,12 +2587,12 @@ class ListEntitiesAlarmControlPanelResponse final : public InfoResponseProtoMess static constexpr uint8_t MESSAGE_TYPE = 94; static constexpr uint8_t ESTIMATED_SIZE = 48; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_alarm_control_panel_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_alarm_control_panel_response"); } #endif uint32_t supported_features{0}; bool requires_code{false}; bool requires_code_to_arm{false}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2605,10 +2605,10 @@ class AlarmControlPanelStateResponse final : public StateResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 95; static constexpr uint8_t ESTIMATED_SIZE = 11; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "alarm_control_panel_state_response"; } + const LogString *message_name() const override { return LOG_STR("alarm_control_panel_state_response"); } #endif enums::AlarmControlPanelState state{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2621,7 +2621,7 @@ class AlarmControlPanelCommandRequest final : public CommandProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 96; static constexpr uint8_t ESTIMATED_SIZE = 20; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "alarm_control_panel_command_request"; } + const LogString *message_name() const override { return LOG_STR("alarm_control_panel_command_request"); } #endif enums::AlarmControlPanelStateCommand command{}; StringRef code{}; @@ -2641,13 +2641,13 @@ class ListEntitiesTextResponse final : public InfoResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 97; static constexpr uint8_t ESTIMATED_SIZE = 59; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_text_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_text_response"); } #endif uint32_t min_length{0}; uint32_t max_length{0}; StringRef pattern{}; enums::TextMode mode{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2660,11 +2660,11 @@ class TextStateResponse final : public StateResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 98; static constexpr uint8_t ESTIMATED_SIZE = 20; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "text_state_response"; } + const LogString *message_name() const override { return LOG_STR("text_state_response"); } #endif StringRef state{}; bool missing_state{false}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2677,7 +2677,7 @@ class TextCommandRequest final : public CommandProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 99; static constexpr uint8_t ESTIMATED_SIZE = 18; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "text_command_request"; } + const LogString *message_name() const override { return LOG_STR("text_command_request"); } #endif StringRef state{}; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2696,9 +2696,9 @@ class ListEntitiesDateResponse final : public InfoResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 100; static constexpr uint8_t ESTIMATED_SIZE = 40; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_date_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_date_response"); } #endif - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2711,13 +2711,13 @@ class DateStateResponse final : public StateResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 101; static constexpr uint8_t ESTIMATED_SIZE = 23; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "date_state_response"; } + const LogString *message_name() const override { return LOG_STR("date_state_response"); } #endif bool missing_state{false}; uint32_t year{0}; uint32_t month{0}; uint32_t day{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2730,7 +2730,7 @@ class DateCommandRequest final : public CommandProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 102; static constexpr uint8_t ESTIMATED_SIZE = 21; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "date_command_request"; } + const LogString *message_name() const override { return LOG_STR("date_command_request"); } #endif uint32_t year{0}; uint32_t month{0}; @@ -2750,9 +2750,9 @@ class ListEntitiesTimeResponse final : public InfoResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 103; static constexpr uint8_t ESTIMATED_SIZE = 40; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_time_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_time_response"); } #endif - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2765,13 +2765,13 @@ class TimeStateResponse final : public StateResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 104; static constexpr uint8_t ESTIMATED_SIZE = 23; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "time_state_response"; } + const LogString *message_name() const override { return LOG_STR("time_state_response"); } #endif bool missing_state{false}; uint32_t hour{0}; uint32_t minute{0}; uint32_t second{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2784,7 +2784,7 @@ class TimeCommandRequest final : public CommandProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 105; static constexpr uint8_t ESTIMATED_SIZE = 21; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "time_command_request"; } + const LogString *message_name() const override { return LOG_STR("time_command_request"); } #endif uint32_t hour{0}; uint32_t minute{0}; @@ -2804,11 +2804,11 @@ class ListEntitiesEventResponse final : public InfoResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 107; static constexpr uint8_t ESTIMATED_SIZE = 67; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_event_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_event_response"); } #endif StringRef device_class{}; const FixedVector *event_types{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2821,10 +2821,10 @@ class EventResponse final : public StateResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 108; static constexpr uint8_t ESTIMATED_SIZE = 18; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "event_response"; } + const LogString *message_name() const override { return LOG_STR("event_response"); } #endif StringRef event_type{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2839,13 +2839,13 @@ class ListEntitiesValveResponse final : public InfoResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 109; static constexpr uint8_t ESTIMATED_SIZE = 55; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_valve_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_valve_response"); } #endif StringRef device_class{}; bool assumed_state{false}; bool supports_position{false}; bool supports_stop{false}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2858,11 +2858,11 @@ class ValveStateResponse final : public StateResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 110; static constexpr uint8_t ESTIMATED_SIZE = 16; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "valve_state_response"; } + const LogString *message_name() const override { return LOG_STR("valve_state_response"); } #endif float position{0.0f}; enums::ValveOperation current_operation{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2875,7 +2875,7 @@ class ValveCommandRequest final : public CommandProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 111; static constexpr uint8_t ESTIMATED_SIZE = 18; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "valve_command_request"; } + const LogString *message_name() const override { return LOG_STR("valve_command_request"); } #endif bool has_position{false}; float position{0.0f}; @@ -2895,9 +2895,9 @@ class ListEntitiesDateTimeResponse final : public InfoResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 112; static constexpr uint8_t ESTIMATED_SIZE = 40; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_date_time_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_date_time_response"); } #endif - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2910,11 +2910,11 @@ class DateTimeStateResponse final : public StateResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 113; static constexpr uint8_t ESTIMATED_SIZE = 16; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "date_time_state_response"; } + const LogString *message_name() const override { return LOG_STR("date_time_state_response"); } #endif bool missing_state{false}; uint32_t epoch_seconds{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2927,7 +2927,7 @@ class DateTimeCommandRequest final : public CommandProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 114; static constexpr uint8_t ESTIMATED_SIZE = 14; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "date_time_command_request"; } + const LogString *message_name() const override { return LOG_STR("date_time_command_request"); } #endif uint32_t epoch_seconds{0}; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -2945,10 +2945,10 @@ class ListEntitiesUpdateResponse final : public InfoResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 116; static constexpr uint8_t ESTIMATED_SIZE = 49; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_update_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_update_response"); } #endif StringRef device_class{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2961,7 +2961,7 @@ class UpdateStateResponse final : public StateResponseProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 117; static constexpr uint8_t ESTIMATED_SIZE = 65; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "update_state_response"; } + const LogString *message_name() const override { return LOG_STR("update_state_response"); } #endif bool missing_state{false}; bool in_progress{false}; @@ -2972,7 +2972,7 @@ class UpdateStateResponse final : public StateResponseProtoMessage { StringRef title{}; StringRef release_summary{}; StringRef release_url{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -2985,7 +2985,7 @@ class UpdateCommandRequest final : public CommandProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 118; static constexpr uint8_t ESTIMATED_SIZE = 11; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "update_command_request"; } + const LogString *message_name() const override { return LOG_STR("update_command_request"); } #endif enums::UpdateCommand command{}; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -3003,11 +3003,11 @@ class ZWaveProxyFrame final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 128; static constexpr uint8_t ESTIMATED_SIZE = 19; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "z_wave_proxy_frame"; } + const LogString *message_name() const override { return LOG_STR("z_wave_proxy_frame"); } #endif const uint8_t *data{nullptr}; uint16_t data_len{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -3021,12 +3021,12 @@ class ZWaveProxyRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 129; static constexpr uint8_t ESTIMATED_SIZE = 21; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "z_wave_proxy_request"; } + const LogString *message_name() const override { return LOG_STR("z_wave_proxy_request"); } #endif enums::ZWaveProxyRequestType type{}; const uint8_t *data{nullptr}; uint16_t data_len{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -3041,12 +3041,13 @@ class ZWaveProxyRequest final : public ProtoDecodableMessage { class ListEntitiesInfraredResponse final : public InfoResponseProtoMessage { public: static constexpr uint8_t MESSAGE_TYPE = 135; - static constexpr uint8_t ESTIMATED_SIZE = 44; + static constexpr uint8_t ESTIMATED_SIZE = 48; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "list_entities_infrared_response"; } + const LogString *message_name() const override { return LOG_STR("list_entities_infrared_response"); } #endif uint32_t capabilities{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint32_t receiver_frequency{0}; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -3061,7 +3062,7 @@ class InfraredRFTransmitRawTimingsRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 136; static constexpr uint8_t ESTIMATED_SIZE = 220; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "infrared_rf_transmit_raw_timings_request"; } + const LogString *message_name() const override { return LOG_STR("infrared_rf_transmit_raw_timings_request"); } #endif #ifdef USE_DEVICES uint32_t device_id{0}; @@ -3086,14 +3087,14 @@ class InfraredRFReceiveEvent final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 137; static constexpr uint8_t ESTIMATED_SIZE = 17; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "infrared_rf_receive_event"; } + const LogString *message_name() const override { return LOG_STR("infrared_rf_receive_event"); } #endif #ifdef USE_DEVICES uint32_t device_id{0}; #endif uint32_t key{0}; const std::vector *timings{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -3108,7 +3109,7 @@ class SerialProxyConfigureRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 138; static constexpr uint8_t ESTIMATED_SIZE = 20; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "serial_proxy_configure_request"; } + const LogString *message_name() const override { return LOG_STR("serial_proxy_configure_request"); } #endif uint32_t instance{0}; uint32_t baudrate{0}; @@ -3128,7 +3129,7 @@ class SerialProxyDataReceived final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 139; static constexpr uint8_t ESTIMATED_SIZE = 23; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "serial_proxy_data_received"; } + const LogString *message_name() const override { return LOG_STR("serial_proxy_data_received"); } #endif uint32_t instance{0}; const uint8_t *data_ptr_{nullptr}; @@ -3137,7 +3138,7 @@ class SerialProxyDataReceived final : public ProtoMessage { this->data_ptr_ = data; this->data_len_ = len; } - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -3150,7 +3151,7 @@ class SerialProxyWriteRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 140; static constexpr uint8_t ESTIMATED_SIZE = 23; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "serial_proxy_write_request"; } + const LogString *message_name() const override { return LOG_STR("serial_proxy_write_request"); } #endif uint32_t instance{0}; const uint8_t *data{nullptr}; @@ -3168,7 +3169,7 @@ class SerialProxySetModemPinsRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 141; static constexpr uint8_t ESTIMATED_SIZE = 8; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "serial_proxy_set_modem_pins_request"; } + const LogString *message_name() const override { return LOG_STR("serial_proxy_set_modem_pins_request"); } #endif uint32_t instance{0}; uint32_t line_states{0}; @@ -3184,7 +3185,7 @@ class SerialProxyGetModemPinsRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 142; static constexpr uint8_t ESTIMATED_SIZE = 4; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "serial_proxy_get_modem_pins_request"; } + const LogString *message_name() const override { return LOG_STR("serial_proxy_get_modem_pins_request"); } #endif uint32_t instance{0}; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -3199,11 +3200,11 @@ class SerialProxyGetModemPinsResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 143; static constexpr uint8_t ESTIMATED_SIZE = 8; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "serial_proxy_get_modem_pins_response"; } + const LogString *message_name() const override { return LOG_STR("serial_proxy_get_modem_pins_response"); } #endif uint32_t instance{0}; uint32_t line_states{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -3216,7 +3217,7 @@ class SerialProxyRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 144; static constexpr uint8_t ESTIMATED_SIZE = 6; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "serial_proxy_request"; } + const LogString *message_name() const override { return LOG_STR("serial_proxy_request"); } #endif uint32_t instance{0}; enums::SerialProxyRequestType type{}; @@ -3232,13 +3233,13 @@ class SerialProxyRequestResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 147; static constexpr uint8_t ESTIMATED_SIZE = 17; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "serial_proxy_request_response"; } + const LogString *message_name() const override { return LOG_STR("serial_proxy_request_response"); } #endif uint32_t instance{0}; enums::SerialProxyRequestType type{}; enums::SerialProxyStatus status{}; StringRef error_message{}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; @@ -3253,7 +3254,7 @@ class BluetoothSetConnectionParamsRequest final : public ProtoDecodableMessage { static constexpr uint8_t MESSAGE_TYPE = 145; static constexpr uint8_t ESTIMATED_SIZE = 20; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_set_connection_params_request"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_set_connection_params_request"); } #endif uint64_t address{0}; uint32_t min_interval{0}; @@ -3272,11 +3273,11 @@ class BluetoothSetConnectionParamsResponse final : public ProtoMessage { static constexpr uint8_t MESSAGE_TYPE = 146; static constexpr uint8_t ESTIMATED_SIZE = 8; #ifdef HAS_PROTO_MESSAGE_DUMP - const char *message_name() const override { return "bluetooth_set_connection_params_response"; } + const LogString *message_name() const override { return LOG_STR("bluetooth_set_connection_params_response"); } #endif uint64_t address{0}; int32_t error{0}; - void encode(ProtoWriteBuffer &buffer) const; + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const; uint32_t calculate_size() const; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 5a53f0281f..640c347371 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -2,6 +2,7 @@ // See script/api_protobuf/api_protobuf.py #include "api_pb2.h" #include "esphome/core/helpers.h" +#include "esphome/core/progmem.h" #include @@ -9,6 +10,21 @@ namespace esphome::api { +#ifdef USE_ESP8266 +// Out-of-line to avoid inlining strlen_P/memcpy_P at every call site +void DumpBuffer::append_p_esp8266(const char *str) { + size_t len = strlen_P(str); + size_t space = CAPACITY - 1 - pos_; + if (len > space) + len = space; + if (len > 0) { + memcpy_P(buf_ + pos_, str, len); + pos_ += len; + buf_[pos_] = '\0'; + } +} +#endif + // Helper function to append a quoted string, handling empty StringRef static inline void append_quoted_string(DumpBuffer &out, const StringRef &ref) { out.append("'"); @@ -19,8 +35,9 @@ static inline void append_quoted_string(DumpBuffer &out, const StringRef &ref) { } // Common helpers for dump_field functions +// field_name is a PROGMEM pointer (flash on ESP8266, regular pointer on other platforms) static inline void append_field_prefix(DumpBuffer &out, const char *field_name, int indent) { - out.append(indent, ' ').append(field_name).append(": "); + out.append(indent, ' ').append_p(field_name).append(": "); } static inline void append_uint(DumpBuffer &out, uint32_t value) { @@ -28,10 +45,11 @@ static inline void append_uint(DumpBuffer &out, uint32_t value) { } // RAII helper for message dump formatting +// message_name is a PROGMEM pointer (flash on ESP8266, regular pointer on other platforms) class MessageDumpHelper { public: MessageDumpHelper(DumpBuffer &out, const char *message_name) : out_(out) { - out_.append(message_name); + out_.append_p(message_name); out_.append(" {\n"); } ~MessageDumpHelper() { out_.append(" }"); } @@ -41,6 +59,10 @@ class MessageDumpHelper { }; // Helper functions to reduce code duplication in dump methods +// field_name parameters are PROGMEM pointers (flash on ESP8266, regular pointers on other platforms) +// Not all overloads are used in every build (depends on enabled components) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" static void dump_field(DumpBuffer &out, const char *field_name, int32_t value, int indent = 2) { append_field_prefix(out, field_name, indent); out.set_pos(buf_append_printf(out.data(), DumpBuffer::CAPACITY, out.pos(), "%" PRId32 "\n", value)); @@ -85,56 +107,59 @@ static void dump_field(DumpBuffer &out, const char *field_name, const char *valu out.append("\n"); } +// proto_enum_to_string returns PROGMEM pointers, so use append_p template static void dump_field(DumpBuffer &out, const char *field_name, T value, int indent = 2) { append_field_prefix(out, field_name, indent); - out.append(proto_enum_to_string(value)); + out.append_p(proto_enum_to_string(value)); out.append("\n"); } // Helper for bytes fields - uses stack buffer to avoid heap allocation // Buffer sized for 160 bytes of data (480 chars with separators) to fit typical log buffer +// field_name is a PROGMEM pointer (flash on ESP8266, regular pointer on other platforms) static void dump_bytes_field(DumpBuffer &out, const char *field_name, const uint8_t *data, size_t len, int indent = 2) { char hex_buf[format_hex_pretty_size(160)]; append_field_prefix(out, field_name, indent); format_hex_pretty_to(hex_buf, data, len); out.append(hex_buf).append("\n"); } +#pragma GCC diagnostic pop template<> const char *proto_enum_to_string(enums::SerialProxyPortType value) { switch (value) { case enums::SERIAL_PROXY_PORT_TYPE_TTL: - return "SERIAL_PROXY_PORT_TYPE_TTL"; + return ESPHOME_PSTR("SERIAL_PROXY_PORT_TYPE_TTL"); case enums::SERIAL_PROXY_PORT_TYPE_RS232: - return "SERIAL_PROXY_PORT_TYPE_RS232"; + return ESPHOME_PSTR("SERIAL_PROXY_PORT_TYPE_RS232"); case enums::SERIAL_PROXY_PORT_TYPE_RS485: - return "SERIAL_PROXY_PORT_TYPE_RS485"; + return ESPHOME_PSTR("SERIAL_PROXY_PORT_TYPE_RS485"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } template<> const char *proto_enum_to_string(enums::EntityCategory value) { switch (value) { case enums::ENTITY_CATEGORY_NONE: - return "ENTITY_CATEGORY_NONE"; + return ESPHOME_PSTR("ENTITY_CATEGORY_NONE"); case enums::ENTITY_CATEGORY_CONFIG: - return "ENTITY_CATEGORY_CONFIG"; + return ESPHOME_PSTR("ENTITY_CATEGORY_CONFIG"); case enums::ENTITY_CATEGORY_DIAGNOSTIC: - return "ENTITY_CATEGORY_DIAGNOSTIC"; + return ESPHOME_PSTR("ENTITY_CATEGORY_DIAGNOSTIC"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } #ifdef USE_COVER template<> const char *proto_enum_to_string(enums::CoverOperation value) { switch (value) { case enums::COVER_OPERATION_IDLE: - return "COVER_OPERATION_IDLE"; + return ESPHOME_PSTR("COVER_OPERATION_IDLE"); case enums::COVER_OPERATION_IS_OPENING: - return "COVER_OPERATION_IS_OPENING"; + return ESPHOME_PSTR("COVER_OPERATION_IS_OPENING"); case enums::COVER_OPERATION_IS_CLOSING: - return "COVER_OPERATION_IS_CLOSING"; + return ESPHOME_PSTR("COVER_OPERATION_IS_CLOSING"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } #endif @@ -142,11 +167,11 @@ template<> const char *proto_enum_to_string(enums::CoverO template<> const char *proto_enum_to_string(enums::FanDirection value) { switch (value) { case enums::FAN_DIRECTION_FORWARD: - return "FAN_DIRECTION_FORWARD"; + return ESPHOME_PSTR("FAN_DIRECTION_FORWARD"); case enums::FAN_DIRECTION_REVERSE: - return "FAN_DIRECTION_REVERSE"; + return ESPHOME_PSTR("FAN_DIRECTION_REVERSE"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } #endif @@ -154,29 +179,29 @@ template<> const char *proto_enum_to_string(enums::FanDirec template<> const char *proto_enum_to_string(enums::ColorMode value) { switch (value) { case enums::COLOR_MODE_UNKNOWN: - return "COLOR_MODE_UNKNOWN"; + return ESPHOME_PSTR("COLOR_MODE_UNKNOWN"); case enums::COLOR_MODE_ON_OFF: - return "COLOR_MODE_ON_OFF"; + return ESPHOME_PSTR("COLOR_MODE_ON_OFF"); case enums::COLOR_MODE_LEGACY_BRIGHTNESS: - return "COLOR_MODE_LEGACY_BRIGHTNESS"; + return ESPHOME_PSTR("COLOR_MODE_LEGACY_BRIGHTNESS"); case enums::COLOR_MODE_BRIGHTNESS: - return "COLOR_MODE_BRIGHTNESS"; + return ESPHOME_PSTR("COLOR_MODE_BRIGHTNESS"); case enums::COLOR_MODE_WHITE: - return "COLOR_MODE_WHITE"; + return ESPHOME_PSTR("COLOR_MODE_WHITE"); case enums::COLOR_MODE_COLOR_TEMPERATURE: - return "COLOR_MODE_COLOR_TEMPERATURE"; + return ESPHOME_PSTR("COLOR_MODE_COLOR_TEMPERATURE"); case enums::COLOR_MODE_COLD_WARM_WHITE: - return "COLOR_MODE_COLD_WARM_WHITE"; + return ESPHOME_PSTR("COLOR_MODE_COLD_WARM_WHITE"); case enums::COLOR_MODE_RGB: - return "COLOR_MODE_RGB"; + return ESPHOME_PSTR("COLOR_MODE_RGB"); case enums::COLOR_MODE_RGB_WHITE: - return "COLOR_MODE_RGB_WHITE"; + return ESPHOME_PSTR("COLOR_MODE_RGB_WHITE"); case enums::COLOR_MODE_RGB_COLOR_TEMPERATURE: - return "COLOR_MODE_RGB_COLOR_TEMPERATURE"; + return ESPHOME_PSTR("COLOR_MODE_RGB_COLOR_TEMPERATURE"); case enums::COLOR_MODE_RGB_COLD_WARM_WHITE: - return "COLOR_MODE_RGB_COLD_WARM_WHITE"; + return ESPHOME_PSTR("COLOR_MODE_RGB_COLD_WARM_WHITE"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } #endif @@ -184,91 +209,91 @@ template<> const char *proto_enum_to_string(enums::ColorMode v template<> const char *proto_enum_to_string(enums::SensorStateClass value) { switch (value) { case enums::STATE_CLASS_NONE: - return "STATE_CLASS_NONE"; + return ESPHOME_PSTR("STATE_CLASS_NONE"); case enums::STATE_CLASS_MEASUREMENT: - return "STATE_CLASS_MEASUREMENT"; + return ESPHOME_PSTR("STATE_CLASS_MEASUREMENT"); case enums::STATE_CLASS_TOTAL_INCREASING: - return "STATE_CLASS_TOTAL_INCREASING"; + return ESPHOME_PSTR("STATE_CLASS_TOTAL_INCREASING"); case enums::STATE_CLASS_TOTAL: - return "STATE_CLASS_TOTAL"; + return ESPHOME_PSTR("STATE_CLASS_TOTAL"); case enums::STATE_CLASS_MEASUREMENT_ANGLE: - return "STATE_CLASS_MEASUREMENT_ANGLE"; + return ESPHOME_PSTR("STATE_CLASS_MEASUREMENT_ANGLE"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } #endif template<> const char *proto_enum_to_string(enums::LogLevel value) { switch (value) { case enums::LOG_LEVEL_NONE: - return "LOG_LEVEL_NONE"; + return ESPHOME_PSTR("LOG_LEVEL_NONE"); case enums::LOG_LEVEL_ERROR: - return "LOG_LEVEL_ERROR"; + return ESPHOME_PSTR("LOG_LEVEL_ERROR"); case enums::LOG_LEVEL_WARN: - return "LOG_LEVEL_WARN"; + return ESPHOME_PSTR("LOG_LEVEL_WARN"); case enums::LOG_LEVEL_INFO: - return "LOG_LEVEL_INFO"; + return ESPHOME_PSTR("LOG_LEVEL_INFO"); case enums::LOG_LEVEL_CONFIG: - return "LOG_LEVEL_CONFIG"; + return ESPHOME_PSTR("LOG_LEVEL_CONFIG"); case enums::LOG_LEVEL_DEBUG: - return "LOG_LEVEL_DEBUG"; + return ESPHOME_PSTR("LOG_LEVEL_DEBUG"); case enums::LOG_LEVEL_VERBOSE: - return "LOG_LEVEL_VERBOSE"; + return ESPHOME_PSTR("LOG_LEVEL_VERBOSE"); case enums::LOG_LEVEL_VERY_VERBOSE: - return "LOG_LEVEL_VERY_VERBOSE"; + return ESPHOME_PSTR("LOG_LEVEL_VERY_VERBOSE"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } template<> const char *proto_enum_to_string(enums::DSTRuleType value) { switch (value) { case enums::DST_RULE_TYPE_NONE: - return "DST_RULE_TYPE_NONE"; + return ESPHOME_PSTR("DST_RULE_TYPE_NONE"); case enums::DST_RULE_TYPE_MONTH_WEEK_DAY: - return "DST_RULE_TYPE_MONTH_WEEK_DAY"; + return ESPHOME_PSTR("DST_RULE_TYPE_MONTH_WEEK_DAY"); case enums::DST_RULE_TYPE_JULIAN_NO_LEAP: - return "DST_RULE_TYPE_JULIAN_NO_LEAP"; + return ESPHOME_PSTR("DST_RULE_TYPE_JULIAN_NO_LEAP"); case enums::DST_RULE_TYPE_DAY_OF_YEAR: - return "DST_RULE_TYPE_DAY_OF_YEAR"; + return ESPHOME_PSTR("DST_RULE_TYPE_DAY_OF_YEAR"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } #ifdef USE_API_USER_DEFINED_ACTIONS template<> const char *proto_enum_to_string(enums::ServiceArgType value) { switch (value) { case enums::SERVICE_ARG_TYPE_BOOL: - return "SERVICE_ARG_TYPE_BOOL"; + return ESPHOME_PSTR("SERVICE_ARG_TYPE_BOOL"); case enums::SERVICE_ARG_TYPE_INT: - return "SERVICE_ARG_TYPE_INT"; + return ESPHOME_PSTR("SERVICE_ARG_TYPE_INT"); case enums::SERVICE_ARG_TYPE_FLOAT: - return "SERVICE_ARG_TYPE_FLOAT"; + return ESPHOME_PSTR("SERVICE_ARG_TYPE_FLOAT"); case enums::SERVICE_ARG_TYPE_STRING: - return "SERVICE_ARG_TYPE_STRING"; + return ESPHOME_PSTR("SERVICE_ARG_TYPE_STRING"); case enums::SERVICE_ARG_TYPE_BOOL_ARRAY: - return "SERVICE_ARG_TYPE_BOOL_ARRAY"; + return ESPHOME_PSTR("SERVICE_ARG_TYPE_BOOL_ARRAY"); case enums::SERVICE_ARG_TYPE_INT_ARRAY: - return "SERVICE_ARG_TYPE_INT_ARRAY"; + return ESPHOME_PSTR("SERVICE_ARG_TYPE_INT_ARRAY"); case enums::SERVICE_ARG_TYPE_FLOAT_ARRAY: - return "SERVICE_ARG_TYPE_FLOAT_ARRAY"; + return ESPHOME_PSTR("SERVICE_ARG_TYPE_FLOAT_ARRAY"); case enums::SERVICE_ARG_TYPE_STRING_ARRAY: - return "SERVICE_ARG_TYPE_STRING_ARRAY"; + return ESPHOME_PSTR("SERVICE_ARG_TYPE_STRING_ARRAY"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } template<> const char *proto_enum_to_string(enums::SupportsResponseType value) { switch (value) { case enums::SUPPORTS_RESPONSE_NONE: - return "SUPPORTS_RESPONSE_NONE"; + return ESPHOME_PSTR("SUPPORTS_RESPONSE_NONE"); case enums::SUPPORTS_RESPONSE_OPTIONAL: - return "SUPPORTS_RESPONSE_OPTIONAL"; + return ESPHOME_PSTR("SUPPORTS_RESPONSE_OPTIONAL"); case enums::SUPPORTS_RESPONSE_ONLY: - return "SUPPORTS_RESPONSE_ONLY"; + return ESPHOME_PSTR("SUPPORTS_RESPONSE_ONLY"); case enums::SUPPORTS_RESPONSE_STATUS: - return "SUPPORTS_RESPONSE_STATUS"; + return ESPHOME_PSTR("SUPPORTS_RESPONSE_STATUS"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } #endif @@ -276,103 +301,103 @@ template<> const char *proto_enum_to_string(enums:: template<> const char *proto_enum_to_string(enums::ClimateMode value) { switch (value) { case enums::CLIMATE_MODE_OFF: - return "CLIMATE_MODE_OFF"; + return ESPHOME_PSTR("CLIMATE_MODE_OFF"); case enums::CLIMATE_MODE_HEAT_COOL: - return "CLIMATE_MODE_HEAT_COOL"; + return ESPHOME_PSTR("CLIMATE_MODE_HEAT_COOL"); case enums::CLIMATE_MODE_COOL: - return "CLIMATE_MODE_COOL"; + return ESPHOME_PSTR("CLIMATE_MODE_COOL"); case enums::CLIMATE_MODE_HEAT: - return "CLIMATE_MODE_HEAT"; + return ESPHOME_PSTR("CLIMATE_MODE_HEAT"); case enums::CLIMATE_MODE_FAN_ONLY: - return "CLIMATE_MODE_FAN_ONLY"; + return ESPHOME_PSTR("CLIMATE_MODE_FAN_ONLY"); case enums::CLIMATE_MODE_DRY: - return "CLIMATE_MODE_DRY"; + return ESPHOME_PSTR("CLIMATE_MODE_DRY"); case enums::CLIMATE_MODE_AUTO: - return "CLIMATE_MODE_AUTO"; + return ESPHOME_PSTR("CLIMATE_MODE_AUTO"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } template<> const char *proto_enum_to_string(enums::ClimateFanMode value) { switch (value) { case enums::CLIMATE_FAN_ON: - return "CLIMATE_FAN_ON"; + return ESPHOME_PSTR("CLIMATE_FAN_ON"); case enums::CLIMATE_FAN_OFF: - return "CLIMATE_FAN_OFF"; + return ESPHOME_PSTR("CLIMATE_FAN_OFF"); case enums::CLIMATE_FAN_AUTO: - return "CLIMATE_FAN_AUTO"; + return ESPHOME_PSTR("CLIMATE_FAN_AUTO"); case enums::CLIMATE_FAN_LOW: - return "CLIMATE_FAN_LOW"; + return ESPHOME_PSTR("CLIMATE_FAN_LOW"); case enums::CLIMATE_FAN_MEDIUM: - return "CLIMATE_FAN_MEDIUM"; + return ESPHOME_PSTR("CLIMATE_FAN_MEDIUM"); case enums::CLIMATE_FAN_HIGH: - return "CLIMATE_FAN_HIGH"; + return ESPHOME_PSTR("CLIMATE_FAN_HIGH"); case enums::CLIMATE_FAN_MIDDLE: - return "CLIMATE_FAN_MIDDLE"; + return ESPHOME_PSTR("CLIMATE_FAN_MIDDLE"); case enums::CLIMATE_FAN_FOCUS: - return "CLIMATE_FAN_FOCUS"; + return ESPHOME_PSTR("CLIMATE_FAN_FOCUS"); case enums::CLIMATE_FAN_DIFFUSE: - return "CLIMATE_FAN_DIFFUSE"; + return ESPHOME_PSTR("CLIMATE_FAN_DIFFUSE"); case enums::CLIMATE_FAN_QUIET: - return "CLIMATE_FAN_QUIET"; + return ESPHOME_PSTR("CLIMATE_FAN_QUIET"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } template<> const char *proto_enum_to_string(enums::ClimateSwingMode value) { switch (value) { case enums::CLIMATE_SWING_OFF: - return "CLIMATE_SWING_OFF"; + return ESPHOME_PSTR("CLIMATE_SWING_OFF"); case enums::CLIMATE_SWING_BOTH: - return "CLIMATE_SWING_BOTH"; + return ESPHOME_PSTR("CLIMATE_SWING_BOTH"); case enums::CLIMATE_SWING_VERTICAL: - return "CLIMATE_SWING_VERTICAL"; + return ESPHOME_PSTR("CLIMATE_SWING_VERTICAL"); case enums::CLIMATE_SWING_HORIZONTAL: - return "CLIMATE_SWING_HORIZONTAL"; + return ESPHOME_PSTR("CLIMATE_SWING_HORIZONTAL"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } template<> const char *proto_enum_to_string(enums::ClimateAction value) { switch (value) { case enums::CLIMATE_ACTION_OFF: - return "CLIMATE_ACTION_OFF"; + return ESPHOME_PSTR("CLIMATE_ACTION_OFF"); case enums::CLIMATE_ACTION_COOLING: - return "CLIMATE_ACTION_COOLING"; + return ESPHOME_PSTR("CLIMATE_ACTION_COOLING"); case enums::CLIMATE_ACTION_HEATING: - return "CLIMATE_ACTION_HEATING"; + return ESPHOME_PSTR("CLIMATE_ACTION_HEATING"); case enums::CLIMATE_ACTION_IDLE: - return "CLIMATE_ACTION_IDLE"; + return ESPHOME_PSTR("CLIMATE_ACTION_IDLE"); case enums::CLIMATE_ACTION_DRYING: - return "CLIMATE_ACTION_DRYING"; + return ESPHOME_PSTR("CLIMATE_ACTION_DRYING"); case enums::CLIMATE_ACTION_FAN: - return "CLIMATE_ACTION_FAN"; + return ESPHOME_PSTR("CLIMATE_ACTION_FAN"); case enums::CLIMATE_ACTION_DEFROSTING: - return "CLIMATE_ACTION_DEFROSTING"; + return ESPHOME_PSTR("CLIMATE_ACTION_DEFROSTING"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } template<> const char *proto_enum_to_string(enums::ClimatePreset value) { switch (value) { case enums::CLIMATE_PRESET_NONE: - return "CLIMATE_PRESET_NONE"; + return ESPHOME_PSTR("CLIMATE_PRESET_NONE"); case enums::CLIMATE_PRESET_HOME: - return "CLIMATE_PRESET_HOME"; + return ESPHOME_PSTR("CLIMATE_PRESET_HOME"); case enums::CLIMATE_PRESET_AWAY: - return "CLIMATE_PRESET_AWAY"; + return ESPHOME_PSTR("CLIMATE_PRESET_AWAY"); case enums::CLIMATE_PRESET_BOOST: - return "CLIMATE_PRESET_BOOST"; + return ESPHOME_PSTR("CLIMATE_PRESET_BOOST"); case enums::CLIMATE_PRESET_COMFORT: - return "CLIMATE_PRESET_COMFORT"; + return ESPHOME_PSTR("CLIMATE_PRESET_COMFORT"); case enums::CLIMATE_PRESET_ECO: - return "CLIMATE_PRESET_ECO"; + return ESPHOME_PSTR("CLIMATE_PRESET_ECO"); case enums::CLIMATE_PRESET_SLEEP: - return "CLIMATE_PRESET_SLEEP"; + return ESPHOME_PSTR("CLIMATE_PRESET_SLEEP"); case enums::CLIMATE_PRESET_ACTIVITY: - return "CLIMATE_PRESET_ACTIVITY"; + return ESPHOME_PSTR("CLIMATE_PRESET_ACTIVITY"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } #endif @@ -380,21 +405,21 @@ template<> const char *proto_enum_to_string(enums::Climate template<> const char *proto_enum_to_string(enums::WaterHeaterMode value) { switch (value) { case enums::WATER_HEATER_MODE_OFF: - return "WATER_HEATER_MODE_OFF"; + return ESPHOME_PSTR("WATER_HEATER_MODE_OFF"); case enums::WATER_HEATER_MODE_ECO: - return "WATER_HEATER_MODE_ECO"; + return ESPHOME_PSTR("WATER_HEATER_MODE_ECO"); case enums::WATER_HEATER_MODE_ELECTRIC: - return "WATER_HEATER_MODE_ELECTRIC"; + return ESPHOME_PSTR("WATER_HEATER_MODE_ELECTRIC"); case enums::WATER_HEATER_MODE_PERFORMANCE: - return "WATER_HEATER_MODE_PERFORMANCE"; + return ESPHOME_PSTR("WATER_HEATER_MODE_PERFORMANCE"); case enums::WATER_HEATER_MODE_HIGH_DEMAND: - return "WATER_HEATER_MODE_HIGH_DEMAND"; + return ESPHOME_PSTR("WATER_HEATER_MODE_HIGH_DEMAND"); case enums::WATER_HEATER_MODE_HEAT_PUMP: - return "WATER_HEATER_MODE_HEAT_PUMP"; + return ESPHOME_PSTR("WATER_HEATER_MODE_HEAT_PUMP"); case enums::WATER_HEATER_MODE_GAS: - return "WATER_HEATER_MODE_GAS"; + return ESPHOME_PSTR("WATER_HEATER_MODE_GAS"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } #endif @@ -402,36 +427,36 @@ template<> const char *proto_enum_to_string(enums::WaterHeaterCommandHasField value) { switch (value) { case enums::WATER_HEATER_COMMAND_HAS_NONE: - return "WATER_HEATER_COMMAND_HAS_NONE"; + return ESPHOME_PSTR("WATER_HEATER_COMMAND_HAS_NONE"); case enums::WATER_HEATER_COMMAND_HAS_MODE: - return "WATER_HEATER_COMMAND_HAS_MODE"; + return ESPHOME_PSTR("WATER_HEATER_COMMAND_HAS_MODE"); case enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE: - return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE"; + return ESPHOME_PSTR("WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE"); case enums::WATER_HEATER_COMMAND_HAS_STATE: - return "WATER_HEATER_COMMAND_HAS_STATE"; + return ESPHOME_PSTR("WATER_HEATER_COMMAND_HAS_STATE"); case enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW: - return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW"; + return ESPHOME_PSTR("WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW"); case enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH: - return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH"; + return ESPHOME_PSTR("WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH"); case enums::WATER_HEATER_COMMAND_HAS_ON_STATE: - return "WATER_HEATER_COMMAND_HAS_ON_STATE"; + return ESPHOME_PSTR("WATER_HEATER_COMMAND_HAS_ON_STATE"); case enums::WATER_HEATER_COMMAND_HAS_AWAY_STATE: - return "WATER_HEATER_COMMAND_HAS_AWAY_STATE"; + return ESPHOME_PSTR("WATER_HEATER_COMMAND_HAS_AWAY_STATE"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } #ifdef USE_NUMBER template<> const char *proto_enum_to_string(enums::NumberMode value) { switch (value) { case enums::NUMBER_MODE_AUTO: - return "NUMBER_MODE_AUTO"; + return ESPHOME_PSTR("NUMBER_MODE_AUTO"); case enums::NUMBER_MODE_BOX: - return "NUMBER_MODE_BOX"; + return ESPHOME_PSTR("NUMBER_MODE_BOX"); case enums::NUMBER_MODE_SLIDER: - return "NUMBER_MODE_SLIDER"; + return ESPHOME_PSTR("NUMBER_MODE_SLIDER"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } #endif @@ -439,31 +464,31 @@ template<> const char *proto_enum_to_string(enums::NumberMode template<> const char *proto_enum_to_string(enums::LockState value) { switch (value) { case enums::LOCK_STATE_NONE: - return "LOCK_STATE_NONE"; + return ESPHOME_PSTR("LOCK_STATE_NONE"); case enums::LOCK_STATE_LOCKED: - return "LOCK_STATE_LOCKED"; + return ESPHOME_PSTR("LOCK_STATE_LOCKED"); case enums::LOCK_STATE_UNLOCKED: - return "LOCK_STATE_UNLOCKED"; + return ESPHOME_PSTR("LOCK_STATE_UNLOCKED"); case enums::LOCK_STATE_JAMMED: - return "LOCK_STATE_JAMMED"; + return ESPHOME_PSTR("LOCK_STATE_JAMMED"); case enums::LOCK_STATE_LOCKING: - return "LOCK_STATE_LOCKING"; + return ESPHOME_PSTR("LOCK_STATE_LOCKING"); case enums::LOCK_STATE_UNLOCKING: - return "LOCK_STATE_UNLOCKING"; + return ESPHOME_PSTR("LOCK_STATE_UNLOCKING"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } template<> const char *proto_enum_to_string(enums::LockCommand value) { switch (value) { case enums::LOCK_UNLOCK: - return "LOCK_UNLOCK"; + return ESPHOME_PSTR("LOCK_UNLOCK"); case enums::LOCK_LOCK: - return "LOCK_LOCK"; + return ESPHOME_PSTR("LOCK_LOCK"); case enums::LOCK_OPEN: - return "LOCK_OPEN"; + return ESPHOME_PSTR("LOCK_OPEN"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } #endif @@ -471,65 +496,65 @@ template<> const char *proto_enum_to_string(enums::LockComma template<> const char *proto_enum_to_string(enums::MediaPlayerState value) { switch (value) { case enums::MEDIA_PLAYER_STATE_NONE: - return "MEDIA_PLAYER_STATE_NONE"; + return ESPHOME_PSTR("MEDIA_PLAYER_STATE_NONE"); case enums::MEDIA_PLAYER_STATE_IDLE: - return "MEDIA_PLAYER_STATE_IDLE"; + return ESPHOME_PSTR("MEDIA_PLAYER_STATE_IDLE"); case enums::MEDIA_PLAYER_STATE_PLAYING: - return "MEDIA_PLAYER_STATE_PLAYING"; + return ESPHOME_PSTR("MEDIA_PLAYER_STATE_PLAYING"); case enums::MEDIA_PLAYER_STATE_PAUSED: - return "MEDIA_PLAYER_STATE_PAUSED"; + return ESPHOME_PSTR("MEDIA_PLAYER_STATE_PAUSED"); case enums::MEDIA_PLAYER_STATE_ANNOUNCING: - return "MEDIA_PLAYER_STATE_ANNOUNCING"; + return ESPHOME_PSTR("MEDIA_PLAYER_STATE_ANNOUNCING"); case enums::MEDIA_PLAYER_STATE_OFF: - return "MEDIA_PLAYER_STATE_OFF"; + return ESPHOME_PSTR("MEDIA_PLAYER_STATE_OFF"); case enums::MEDIA_PLAYER_STATE_ON: - return "MEDIA_PLAYER_STATE_ON"; + return ESPHOME_PSTR("MEDIA_PLAYER_STATE_ON"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } template<> const char *proto_enum_to_string(enums::MediaPlayerCommand value) { switch (value) { case enums::MEDIA_PLAYER_COMMAND_PLAY: - return "MEDIA_PLAYER_COMMAND_PLAY"; + return ESPHOME_PSTR("MEDIA_PLAYER_COMMAND_PLAY"); case enums::MEDIA_PLAYER_COMMAND_PAUSE: - return "MEDIA_PLAYER_COMMAND_PAUSE"; + return ESPHOME_PSTR("MEDIA_PLAYER_COMMAND_PAUSE"); case enums::MEDIA_PLAYER_COMMAND_STOP: - return "MEDIA_PLAYER_COMMAND_STOP"; + return ESPHOME_PSTR("MEDIA_PLAYER_COMMAND_STOP"); case enums::MEDIA_PLAYER_COMMAND_MUTE: - return "MEDIA_PLAYER_COMMAND_MUTE"; + return ESPHOME_PSTR("MEDIA_PLAYER_COMMAND_MUTE"); case enums::MEDIA_PLAYER_COMMAND_UNMUTE: - return "MEDIA_PLAYER_COMMAND_UNMUTE"; + return ESPHOME_PSTR("MEDIA_PLAYER_COMMAND_UNMUTE"); case enums::MEDIA_PLAYER_COMMAND_TOGGLE: - return "MEDIA_PLAYER_COMMAND_TOGGLE"; + return ESPHOME_PSTR("MEDIA_PLAYER_COMMAND_TOGGLE"); case enums::MEDIA_PLAYER_COMMAND_VOLUME_UP: - return "MEDIA_PLAYER_COMMAND_VOLUME_UP"; + return ESPHOME_PSTR("MEDIA_PLAYER_COMMAND_VOLUME_UP"); case enums::MEDIA_PLAYER_COMMAND_VOLUME_DOWN: - return "MEDIA_PLAYER_COMMAND_VOLUME_DOWN"; + return ESPHOME_PSTR("MEDIA_PLAYER_COMMAND_VOLUME_DOWN"); case enums::MEDIA_PLAYER_COMMAND_ENQUEUE: - return "MEDIA_PLAYER_COMMAND_ENQUEUE"; + return ESPHOME_PSTR("MEDIA_PLAYER_COMMAND_ENQUEUE"); case enums::MEDIA_PLAYER_COMMAND_REPEAT_ONE: - return "MEDIA_PLAYER_COMMAND_REPEAT_ONE"; + return ESPHOME_PSTR("MEDIA_PLAYER_COMMAND_REPEAT_ONE"); case enums::MEDIA_PLAYER_COMMAND_REPEAT_OFF: - return "MEDIA_PLAYER_COMMAND_REPEAT_OFF"; + return ESPHOME_PSTR("MEDIA_PLAYER_COMMAND_REPEAT_OFF"); case enums::MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST: - return "MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST"; + return ESPHOME_PSTR("MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST"); case enums::MEDIA_PLAYER_COMMAND_TURN_ON: - return "MEDIA_PLAYER_COMMAND_TURN_ON"; + return ESPHOME_PSTR("MEDIA_PLAYER_COMMAND_TURN_ON"); case enums::MEDIA_PLAYER_COMMAND_TURN_OFF: - return "MEDIA_PLAYER_COMMAND_TURN_OFF"; + return ESPHOME_PSTR("MEDIA_PLAYER_COMMAND_TURN_OFF"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } template<> const char *proto_enum_to_string(enums::MediaPlayerFormatPurpose value) { switch (value) { case enums::MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT: - return "MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT"; + return ESPHOME_PSTR("MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT"); case enums::MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT: - return "MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT"; + return ESPHOME_PSTR("MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } #endif @@ -538,49 +563,49 @@ template<> const char *proto_enum_to_string(enums::BluetoothDeviceRequestType value) { switch (value) { case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: - return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT"; + return ESPHOME_PSTR("BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT"); case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: - return "BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT"; + return ESPHOME_PSTR("BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT"); case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR: - return "BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR"; + return ESPHOME_PSTR("BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR"); case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR: - return "BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR"; + return ESPHOME_PSTR("BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR"); case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE: - return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE"; + return ESPHOME_PSTR("BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE"); case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE: - return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE"; + return ESPHOME_PSTR("BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE"); case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE: - return "BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE"; + return ESPHOME_PSTR("BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } template<> const char *proto_enum_to_string(enums::BluetoothScannerState value) { switch (value) { case enums::BLUETOOTH_SCANNER_STATE_IDLE: - return "BLUETOOTH_SCANNER_STATE_IDLE"; + return ESPHOME_PSTR("BLUETOOTH_SCANNER_STATE_IDLE"); case enums::BLUETOOTH_SCANNER_STATE_STARTING: - return "BLUETOOTH_SCANNER_STATE_STARTING"; + return ESPHOME_PSTR("BLUETOOTH_SCANNER_STATE_STARTING"); case enums::BLUETOOTH_SCANNER_STATE_RUNNING: - return "BLUETOOTH_SCANNER_STATE_RUNNING"; + return ESPHOME_PSTR("BLUETOOTH_SCANNER_STATE_RUNNING"); case enums::BLUETOOTH_SCANNER_STATE_FAILED: - return "BLUETOOTH_SCANNER_STATE_FAILED"; + return ESPHOME_PSTR("BLUETOOTH_SCANNER_STATE_FAILED"); case enums::BLUETOOTH_SCANNER_STATE_STOPPING: - return "BLUETOOTH_SCANNER_STATE_STOPPING"; + return ESPHOME_PSTR("BLUETOOTH_SCANNER_STATE_STOPPING"); case enums::BLUETOOTH_SCANNER_STATE_STOPPED: - return "BLUETOOTH_SCANNER_STATE_STOPPED"; + return ESPHOME_PSTR("BLUETOOTH_SCANNER_STATE_STOPPED"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } template<> const char *proto_enum_to_string(enums::BluetoothScannerMode value) { switch (value) { case enums::BLUETOOTH_SCANNER_MODE_PASSIVE: - return "BLUETOOTH_SCANNER_MODE_PASSIVE"; + return ESPHOME_PSTR("BLUETOOTH_SCANNER_MODE_PASSIVE"); case enums::BLUETOOTH_SCANNER_MODE_ACTIVE: - return "BLUETOOTH_SCANNER_MODE_ACTIVE"; + return ESPHOME_PSTR("BLUETOOTH_SCANNER_MODE_ACTIVE"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } #endif @@ -588,76 +613,76 @@ template<> const char *proto_enum_to_string(enums::VoiceAssistantSubscribeFlag value) { switch (value) { case enums::VOICE_ASSISTANT_SUBSCRIBE_NONE: - return "VOICE_ASSISTANT_SUBSCRIBE_NONE"; + return ESPHOME_PSTR("VOICE_ASSISTANT_SUBSCRIBE_NONE"); case enums::VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO: - return "VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO"; + return ESPHOME_PSTR("VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } template<> const char *proto_enum_to_string(enums::VoiceAssistantRequestFlag value) { switch (value) { case enums::VOICE_ASSISTANT_REQUEST_NONE: - return "VOICE_ASSISTANT_REQUEST_NONE"; + return ESPHOME_PSTR("VOICE_ASSISTANT_REQUEST_NONE"); case enums::VOICE_ASSISTANT_REQUEST_USE_VAD: - return "VOICE_ASSISTANT_REQUEST_USE_VAD"; + return ESPHOME_PSTR("VOICE_ASSISTANT_REQUEST_USE_VAD"); case enums::VOICE_ASSISTANT_REQUEST_USE_WAKE_WORD: - return "VOICE_ASSISTANT_REQUEST_USE_WAKE_WORD"; + return ESPHOME_PSTR("VOICE_ASSISTANT_REQUEST_USE_WAKE_WORD"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } #ifdef USE_VOICE_ASSISTANT template<> const char *proto_enum_to_string(enums::VoiceAssistantEvent value) { switch (value) { case enums::VOICE_ASSISTANT_ERROR: - return "VOICE_ASSISTANT_ERROR"; + return ESPHOME_PSTR("VOICE_ASSISTANT_ERROR"); case enums::VOICE_ASSISTANT_RUN_START: - return "VOICE_ASSISTANT_RUN_START"; + return ESPHOME_PSTR("VOICE_ASSISTANT_RUN_START"); case enums::VOICE_ASSISTANT_RUN_END: - return "VOICE_ASSISTANT_RUN_END"; + return ESPHOME_PSTR("VOICE_ASSISTANT_RUN_END"); case enums::VOICE_ASSISTANT_STT_START: - return "VOICE_ASSISTANT_STT_START"; + return ESPHOME_PSTR("VOICE_ASSISTANT_STT_START"); case enums::VOICE_ASSISTANT_STT_END: - return "VOICE_ASSISTANT_STT_END"; + return ESPHOME_PSTR("VOICE_ASSISTANT_STT_END"); case enums::VOICE_ASSISTANT_INTENT_START: - return "VOICE_ASSISTANT_INTENT_START"; + return ESPHOME_PSTR("VOICE_ASSISTANT_INTENT_START"); case enums::VOICE_ASSISTANT_INTENT_END: - return "VOICE_ASSISTANT_INTENT_END"; + return ESPHOME_PSTR("VOICE_ASSISTANT_INTENT_END"); case enums::VOICE_ASSISTANT_TTS_START: - return "VOICE_ASSISTANT_TTS_START"; + return ESPHOME_PSTR("VOICE_ASSISTANT_TTS_START"); case enums::VOICE_ASSISTANT_TTS_END: - return "VOICE_ASSISTANT_TTS_END"; + return ESPHOME_PSTR("VOICE_ASSISTANT_TTS_END"); case enums::VOICE_ASSISTANT_WAKE_WORD_START: - return "VOICE_ASSISTANT_WAKE_WORD_START"; + return ESPHOME_PSTR("VOICE_ASSISTANT_WAKE_WORD_START"); case enums::VOICE_ASSISTANT_WAKE_WORD_END: - return "VOICE_ASSISTANT_WAKE_WORD_END"; + return ESPHOME_PSTR("VOICE_ASSISTANT_WAKE_WORD_END"); case enums::VOICE_ASSISTANT_STT_VAD_START: - return "VOICE_ASSISTANT_STT_VAD_START"; + return ESPHOME_PSTR("VOICE_ASSISTANT_STT_VAD_START"); case enums::VOICE_ASSISTANT_STT_VAD_END: - return "VOICE_ASSISTANT_STT_VAD_END"; + return ESPHOME_PSTR("VOICE_ASSISTANT_STT_VAD_END"); case enums::VOICE_ASSISTANT_TTS_STREAM_START: - return "VOICE_ASSISTANT_TTS_STREAM_START"; + return ESPHOME_PSTR("VOICE_ASSISTANT_TTS_STREAM_START"); case enums::VOICE_ASSISTANT_TTS_STREAM_END: - return "VOICE_ASSISTANT_TTS_STREAM_END"; + return ESPHOME_PSTR("VOICE_ASSISTANT_TTS_STREAM_END"); case enums::VOICE_ASSISTANT_INTENT_PROGRESS: - return "VOICE_ASSISTANT_INTENT_PROGRESS"; + return ESPHOME_PSTR("VOICE_ASSISTANT_INTENT_PROGRESS"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } template<> const char *proto_enum_to_string(enums::VoiceAssistantTimerEvent value) { switch (value) { case enums::VOICE_ASSISTANT_TIMER_STARTED: - return "VOICE_ASSISTANT_TIMER_STARTED"; + return ESPHOME_PSTR("VOICE_ASSISTANT_TIMER_STARTED"); case enums::VOICE_ASSISTANT_TIMER_UPDATED: - return "VOICE_ASSISTANT_TIMER_UPDATED"; + return ESPHOME_PSTR("VOICE_ASSISTANT_TIMER_UPDATED"); case enums::VOICE_ASSISTANT_TIMER_CANCELLED: - return "VOICE_ASSISTANT_TIMER_CANCELLED"; + return ESPHOME_PSTR("VOICE_ASSISTANT_TIMER_CANCELLED"); case enums::VOICE_ASSISTANT_TIMER_FINISHED: - return "VOICE_ASSISTANT_TIMER_FINISHED"; + return ESPHOME_PSTR("VOICE_ASSISTANT_TIMER_FINISHED"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } #endif @@ -665,48 +690,48 @@ template<> const char *proto_enum_to_string(enu template<> const char *proto_enum_to_string(enums::AlarmControlPanelState value) { switch (value) { case enums::ALARM_STATE_DISARMED: - return "ALARM_STATE_DISARMED"; + return ESPHOME_PSTR("ALARM_STATE_DISARMED"); case enums::ALARM_STATE_ARMED_HOME: - return "ALARM_STATE_ARMED_HOME"; + return ESPHOME_PSTR("ALARM_STATE_ARMED_HOME"); case enums::ALARM_STATE_ARMED_AWAY: - return "ALARM_STATE_ARMED_AWAY"; + return ESPHOME_PSTR("ALARM_STATE_ARMED_AWAY"); case enums::ALARM_STATE_ARMED_NIGHT: - return "ALARM_STATE_ARMED_NIGHT"; + return ESPHOME_PSTR("ALARM_STATE_ARMED_NIGHT"); case enums::ALARM_STATE_ARMED_VACATION: - return "ALARM_STATE_ARMED_VACATION"; + return ESPHOME_PSTR("ALARM_STATE_ARMED_VACATION"); case enums::ALARM_STATE_ARMED_CUSTOM_BYPASS: - return "ALARM_STATE_ARMED_CUSTOM_BYPASS"; + return ESPHOME_PSTR("ALARM_STATE_ARMED_CUSTOM_BYPASS"); case enums::ALARM_STATE_PENDING: - return "ALARM_STATE_PENDING"; + return ESPHOME_PSTR("ALARM_STATE_PENDING"); case enums::ALARM_STATE_ARMING: - return "ALARM_STATE_ARMING"; + return ESPHOME_PSTR("ALARM_STATE_ARMING"); case enums::ALARM_STATE_DISARMING: - return "ALARM_STATE_DISARMING"; + return ESPHOME_PSTR("ALARM_STATE_DISARMING"); case enums::ALARM_STATE_TRIGGERED: - return "ALARM_STATE_TRIGGERED"; + return ESPHOME_PSTR("ALARM_STATE_TRIGGERED"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } template<> const char *proto_enum_to_string(enums::AlarmControlPanelStateCommand value) { switch (value) { case enums::ALARM_CONTROL_PANEL_DISARM: - return "ALARM_CONTROL_PANEL_DISARM"; + return ESPHOME_PSTR("ALARM_CONTROL_PANEL_DISARM"); case enums::ALARM_CONTROL_PANEL_ARM_AWAY: - return "ALARM_CONTROL_PANEL_ARM_AWAY"; + return ESPHOME_PSTR("ALARM_CONTROL_PANEL_ARM_AWAY"); case enums::ALARM_CONTROL_PANEL_ARM_HOME: - return "ALARM_CONTROL_PANEL_ARM_HOME"; + return ESPHOME_PSTR("ALARM_CONTROL_PANEL_ARM_HOME"); case enums::ALARM_CONTROL_PANEL_ARM_NIGHT: - return "ALARM_CONTROL_PANEL_ARM_NIGHT"; + return ESPHOME_PSTR("ALARM_CONTROL_PANEL_ARM_NIGHT"); case enums::ALARM_CONTROL_PANEL_ARM_VACATION: - return "ALARM_CONTROL_PANEL_ARM_VACATION"; + return ESPHOME_PSTR("ALARM_CONTROL_PANEL_ARM_VACATION"); case enums::ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS: - return "ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS"; + return ESPHOME_PSTR("ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS"); case enums::ALARM_CONTROL_PANEL_TRIGGER: - return "ALARM_CONTROL_PANEL_TRIGGER"; + return ESPHOME_PSTR("ALARM_CONTROL_PANEL_TRIGGER"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } #endif @@ -714,11 +739,11 @@ const char *proto_enum_to_string(enums::Al template<> const char *proto_enum_to_string(enums::TextMode value) { switch (value) { case enums::TEXT_MODE_TEXT: - return "TEXT_MODE_TEXT"; + return ESPHOME_PSTR("TEXT_MODE_TEXT"); case enums::TEXT_MODE_PASSWORD: - return "TEXT_MODE_PASSWORD"; + return ESPHOME_PSTR("TEXT_MODE_PASSWORD"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } #endif @@ -726,13 +751,13 @@ template<> const char *proto_enum_to_string(enums::TextMode val template<> const char *proto_enum_to_string(enums::ValveOperation value) { switch (value) { case enums::VALVE_OPERATION_IDLE: - return "VALVE_OPERATION_IDLE"; + return ESPHOME_PSTR("VALVE_OPERATION_IDLE"); case enums::VALVE_OPERATION_IS_OPENING: - return "VALVE_OPERATION_IS_OPENING"; + return ESPHOME_PSTR("VALVE_OPERATION_IS_OPENING"); case enums::VALVE_OPERATION_IS_CLOSING: - return "VALVE_OPERATION_IS_CLOSING"; + return ESPHOME_PSTR("VALVE_OPERATION_IS_CLOSING"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } #endif @@ -740,13 +765,13 @@ template<> const char *proto_enum_to_string(enums::ValveO template<> const char *proto_enum_to_string(enums::UpdateCommand value) { switch (value) { case enums::UPDATE_COMMAND_NONE: - return "UPDATE_COMMAND_NONE"; + return ESPHOME_PSTR("UPDATE_COMMAND_NONE"); case enums::UPDATE_COMMAND_UPDATE: - return "UPDATE_COMMAND_UPDATE"; + return ESPHOME_PSTR("UPDATE_COMMAND_UPDATE"); case enums::UPDATE_COMMAND_CHECK: - return "UPDATE_COMMAND_CHECK"; + return ESPHOME_PSTR("UPDATE_COMMAND_CHECK"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } #endif @@ -754,13 +779,13 @@ template<> const char *proto_enum_to_string(enums::UpdateC template<> const char *proto_enum_to_string(enums::ZWaveProxyRequestType value) { switch (value) { case enums::ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE: - return "ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE"; + return ESPHOME_PSTR("ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE"); case enums::ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE: - return "ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE"; + return ESPHOME_PSTR("ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE"); case enums::ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE: - return "ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE"; + return ESPHOME_PSTR("ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } #endif @@ -768,165 +793,165 @@ template<> const char *proto_enum_to_string(enums: template<> const char *proto_enum_to_string(enums::SerialProxyParity value) { switch (value) { case enums::SERIAL_PROXY_PARITY_NONE: - return "SERIAL_PROXY_PARITY_NONE"; + return ESPHOME_PSTR("SERIAL_PROXY_PARITY_NONE"); case enums::SERIAL_PROXY_PARITY_EVEN: - return "SERIAL_PROXY_PARITY_EVEN"; + return ESPHOME_PSTR("SERIAL_PROXY_PARITY_EVEN"); case enums::SERIAL_PROXY_PARITY_ODD: - return "SERIAL_PROXY_PARITY_ODD"; + return ESPHOME_PSTR("SERIAL_PROXY_PARITY_ODD"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } template<> const char *proto_enum_to_string(enums::SerialProxyRequestType value) { switch (value) { case enums::SERIAL_PROXY_REQUEST_TYPE_SUBSCRIBE: - return "SERIAL_PROXY_REQUEST_TYPE_SUBSCRIBE"; + return ESPHOME_PSTR("SERIAL_PROXY_REQUEST_TYPE_SUBSCRIBE"); case enums::SERIAL_PROXY_REQUEST_TYPE_UNSUBSCRIBE: - return "SERIAL_PROXY_REQUEST_TYPE_UNSUBSCRIBE"; + return ESPHOME_PSTR("SERIAL_PROXY_REQUEST_TYPE_UNSUBSCRIBE"); case enums::SERIAL_PROXY_REQUEST_TYPE_FLUSH: - return "SERIAL_PROXY_REQUEST_TYPE_FLUSH"; + return ESPHOME_PSTR("SERIAL_PROXY_REQUEST_TYPE_FLUSH"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } template<> const char *proto_enum_to_string(enums::SerialProxyStatus value) { switch (value) { case enums::SERIAL_PROXY_STATUS_OK: - return "SERIAL_PROXY_STATUS_OK"; + return ESPHOME_PSTR("SERIAL_PROXY_STATUS_OK"); case enums::SERIAL_PROXY_STATUS_ASSUMED_SUCCESS: - return "SERIAL_PROXY_STATUS_ASSUMED_SUCCESS"; + return ESPHOME_PSTR("SERIAL_PROXY_STATUS_ASSUMED_SUCCESS"); case enums::SERIAL_PROXY_STATUS_ERROR: - return "SERIAL_PROXY_STATUS_ERROR"; + return ESPHOME_PSTR("SERIAL_PROXY_STATUS_ERROR"); case enums::SERIAL_PROXY_STATUS_TIMEOUT: - return "SERIAL_PROXY_STATUS_TIMEOUT"; + return ESPHOME_PSTR("SERIAL_PROXY_STATUS_TIMEOUT"); case enums::SERIAL_PROXY_STATUS_NOT_SUPPORTED: - return "SERIAL_PROXY_STATUS_NOT_SUPPORTED"; + return ESPHOME_PSTR("SERIAL_PROXY_STATUS_NOT_SUPPORTED"); default: - return "UNKNOWN"; + return ESPHOME_PSTR("UNKNOWN"); } } #endif const char *HelloRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "HelloRequest"); - dump_field(out, "client_info", this->client_info); - dump_field(out, "api_version_major", this->api_version_major); - dump_field(out, "api_version_minor", this->api_version_minor); + MessageDumpHelper helper(out, ESPHOME_PSTR("HelloRequest")); + dump_field(out, ESPHOME_PSTR("client_info"), this->client_info); + dump_field(out, ESPHOME_PSTR("api_version_major"), this->api_version_major); + dump_field(out, ESPHOME_PSTR("api_version_minor"), this->api_version_minor); return out.c_str(); } const char *HelloResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "HelloResponse"); - dump_field(out, "api_version_major", this->api_version_major); - dump_field(out, "api_version_minor", this->api_version_minor); - dump_field(out, "server_info", this->server_info); - dump_field(out, "name", this->name); + MessageDumpHelper helper(out, ESPHOME_PSTR("HelloResponse")); + dump_field(out, ESPHOME_PSTR("api_version_major"), this->api_version_major); + dump_field(out, ESPHOME_PSTR("api_version_minor"), this->api_version_minor); + dump_field(out, ESPHOME_PSTR("server_info"), this->server_info); + dump_field(out, ESPHOME_PSTR("name"), this->name); return out.c_str(); } const char *DisconnectRequest::dump_to(DumpBuffer &out) const { - out.append("DisconnectRequest {}"); + out.append_p(ESPHOME_PSTR("DisconnectRequest {}")); return out.c_str(); } const char *DisconnectResponse::dump_to(DumpBuffer &out) const { - out.append("DisconnectResponse {}"); + out.append_p(ESPHOME_PSTR("DisconnectResponse {}")); return out.c_str(); } const char *PingRequest::dump_to(DumpBuffer &out) const { - out.append("PingRequest {}"); + out.append_p(ESPHOME_PSTR("PingRequest {}")); return out.c_str(); } const char *PingResponse::dump_to(DumpBuffer &out) const { - out.append("PingResponse {}"); + out.append_p(ESPHOME_PSTR("PingResponse {}")); return out.c_str(); } #ifdef USE_AREAS const char *AreaInfo::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "AreaInfo"); - dump_field(out, "area_id", this->area_id); - dump_field(out, "name", this->name); + MessageDumpHelper helper(out, ESPHOME_PSTR("AreaInfo")); + dump_field(out, ESPHOME_PSTR("area_id"), this->area_id); + dump_field(out, ESPHOME_PSTR("name"), this->name); return out.c_str(); } #endif #ifdef USE_DEVICES const char *DeviceInfo::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "DeviceInfo"); - dump_field(out, "device_id", this->device_id); - dump_field(out, "name", this->name); - dump_field(out, "area_id", this->area_id); + MessageDumpHelper helper(out, ESPHOME_PSTR("DeviceInfo")); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); + dump_field(out, ESPHOME_PSTR("name"), this->name); + dump_field(out, ESPHOME_PSTR("area_id"), this->area_id); return out.c_str(); } #endif #ifdef USE_SERIAL_PROXY const char *SerialProxyInfo::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "SerialProxyInfo"); - dump_field(out, "name", this->name); - dump_field(out, "port_type", static_cast(this->port_type)); + MessageDumpHelper helper(out, ESPHOME_PSTR("SerialProxyInfo")); + dump_field(out, ESPHOME_PSTR("name"), this->name); + dump_field(out, ESPHOME_PSTR("port_type"), static_cast(this->port_type)); return out.c_str(); } #endif const char *DeviceInfoResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "DeviceInfoResponse"); - dump_field(out, "name", this->name); - dump_field(out, "mac_address", this->mac_address); - dump_field(out, "esphome_version", this->esphome_version); - dump_field(out, "compilation_time", this->compilation_time); - dump_field(out, "model", this->model); + MessageDumpHelper helper(out, ESPHOME_PSTR("DeviceInfoResponse")); + dump_field(out, ESPHOME_PSTR("name"), this->name); + dump_field(out, ESPHOME_PSTR("mac_address"), this->mac_address); + dump_field(out, ESPHOME_PSTR("esphome_version"), this->esphome_version); + dump_field(out, ESPHOME_PSTR("compilation_time"), this->compilation_time); + dump_field(out, ESPHOME_PSTR("model"), this->model); #ifdef USE_DEEP_SLEEP - dump_field(out, "has_deep_sleep", this->has_deep_sleep); + dump_field(out, ESPHOME_PSTR("has_deep_sleep"), this->has_deep_sleep); #endif #ifdef ESPHOME_PROJECT_NAME - dump_field(out, "project_name", this->project_name); + dump_field(out, ESPHOME_PSTR("project_name"), this->project_name); #endif #ifdef ESPHOME_PROJECT_NAME - dump_field(out, "project_version", this->project_version); + dump_field(out, ESPHOME_PSTR("project_version"), this->project_version); #endif #ifdef USE_WEBSERVER - dump_field(out, "webserver_port", this->webserver_port); + dump_field(out, ESPHOME_PSTR("webserver_port"), this->webserver_port); #endif #ifdef USE_BLUETOOTH_PROXY - dump_field(out, "bluetooth_proxy_feature_flags", this->bluetooth_proxy_feature_flags); + dump_field(out, ESPHOME_PSTR("bluetooth_proxy_feature_flags"), this->bluetooth_proxy_feature_flags); #endif - dump_field(out, "manufacturer", this->manufacturer); - dump_field(out, "friendly_name", this->friendly_name); + dump_field(out, ESPHOME_PSTR("manufacturer"), this->manufacturer); + dump_field(out, ESPHOME_PSTR("friendly_name"), this->friendly_name); #ifdef USE_VOICE_ASSISTANT - dump_field(out, "voice_assistant_feature_flags", this->voice_assistant_feature_flags); + dump_field(out, ESPHOME_PSTR("voice_assistant_feature_flags"), this->voice_assistant_feature_flags); #endif #ifdef USE_AREAS - dump_field(out, "suggested_area", this->suggested_area); + dump_field(out, ESPHOME_PSTR("suggested_area"), this->suggested_area); #endif #ifdef USE_BLUETOOTH_PROXY - dump_field(out, "bluetooth_mac_address", this->bluetooth_mac_address); + dump_field(out, ESPHOME_PSTR("bluetooth_mac_address"), this->bluetooth_mac_address); #endif #ifdef USE_API_NOISE - dump_field(out, "api_encryption_supported", this->api_encryption_supported); + dump_field(out, ESPHOME_PSTR("api_encryption_supported"), this->api_encryption_supported); #endif #ifdef USE_DEVICES for (const auto &it : this->devices) { - out.append(" devices: "); + out.append(4, ' ').append_p(ESPHOME_PSTR("devices")).append(": "); it.dump_to(out); out.append("\n"); } #endif #ifdef USE_AREAS for (const auto &it : this->areas) { - out.append(" areas: "); + out.append(4, ' ').append_p(ESPHOME_PSTR("areas")).append(": "); it.dump_to(out); out.append("\n"); } #endif #ifdef USE_AREAS - out.append(" area: "); + out.append(2, ' ').append_p(ESPHOME_PSTR("area")).append(": "); this->area.dump_to(out); out.append("\n"); #endif #ifdef USE_ZWAVE_PROXY - dump_field(out, "zwave_proxy_feature_flags", this->zwave_proxy_feature_flags); + dump_field(out, ESPHOME_PSTR("zwave_proxy_feature_flags"), this->zwave_proxy_feature_flags); #endif #ifdef USE_ZWAVE_PROXY - dump_field(out, "zwave_home_id", this->zwave_home_id); + dump_field(out, ESPHOME_PSTR("zwave_home_id"), this->zwave_home_id); #endif #ifdef USE_SERIAL_PROXY for (const auto &it : this->serial_proxies) { - out.append(" serial_proxies: "); + out.append(4, ' ').append_p(ESPHOME_PSTR("serial_proxies")).append(": "); it.dump_to(out); out.append("\n"); } @@ -934,1720 +959,1721 @@ const char *DeviceInfoResponse::dump_to(DumpBuffer &out) const { return out.c_str(); } const char *ListEntitiesDoneResponse::dump_to(DumpBuffer &out) const { - out.append("ListEntitiesDoneResponse {}"); + out.append_p(ESPHOME_PSTR("ListEntitiesDoneResponse {}")); return out.c_str(); } #ifdef USE_BINARY_SENSOR const char *ListEntitiesBinarySensorResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesBinarySensorResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); - dump_field(out, "device_class", this->device_class); - dump_field(out, "is_status_binary_sensor", this->is_status_binary_sensor); - dump_field(out, "disabled_by_default", this->disabled_by_default); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesBinarySensorResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); + dump_field(out, ESPHOME_PSTR("device_class"), this->device_class); + dump_field(out, ESPHOME_PSTR("is_status_binary_sensor"), this->is_status_binary_sensor); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "entity_category", static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *BinarySensorStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BinarySensorStateResponse"); - dump_field(out, "key", this->key); - dump_field(out, "state", this->state); - dump_field(out, "missing_state", this->missing_state); + MessageDumpHelper helper(out, ESPHOME_PSTR("BinarySensorStateResponse")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("state"), this->state); + dump_field(out, ESPHOME_PSTR("missing_state"), this->missing_state); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } #endif #ifdef USE_COVER const char *ListEntitiesCoverResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesCoverResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); - dump_field(out, "assumed_state", this->assumed_state); - dump_field(out, "supports_position", this->supports_position); - dump_field(out, "supports_tilt", this->supports_tilt); - dump_field(out, "device_class", this->device_class); - dump_field(out, "disabled_by_default", this->disabled_by_default); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesCoverResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); + dump_field(out, ESPHOME_PSTR("assumed_state"), this->assumed_state); + dump_field(out, ESPHOME_PSTR("supports_position"), this->supports_position); + dump_field(out, ESPHOME_PSTR("supports_tilt"), this->supports_tilt); + dump_field(out, ESPHOME_PSTR("device_class"), this->device_class); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "supports_stop", this->supports_stop); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("supports_stop"), this->supports_stop); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *CoverStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "CoverStateResponse"); - dump_field(out, "key", this->key); - dump_field(out, "position", this->position); - dump_field(out, "tilt", this->tilt); - dump_field(out, "current_operation", static_cast(this->current_operation)); + MessageDumpHelper helper(out, ESPHOME_PSTR("CoverStateResponse")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("position"), this->position); + dump_field(out, ESPHOME_PSTR("tilt"), this->tilt); + dump_field(out, ESPHOME_PSTR("current_operation"), static_cast(this->current_operation)); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *CoverCommandRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "CoverCommandRequest"); - dump_field(out, "key", this->key); - dump_field(out, "has_position", this->has_position); - dump_field(out, "position", this->position); - dump_field(out, "has_tilt", this->has_tilt); - dump_field(out, "tilt", this->tilt); - dump_field(out, "stop", this->stop); + MessageDumpHelper helper(out, ESPHOME_PSTR("CoverCommandRequest")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("has_position"), this->has_position); + dump_field(out, ESPHOME_PSTR("position"), this->position); + dump_field(out, ESPHOME_PSTR("has_tilt"), this->has_tilt); + dump_field(out, ESPHOME_PSTR("tilt"), this->tilt); + dump_field(out, ESPHOME_PSTR("stop"), this->stop); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } #endif #ifdef USE_FAN const char *ListEntitiesFanResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesFanResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); - dump_field(out, "supports_oscillation", this->supports_oscillation); - dump_field(out, "supports_speed", this->supports_speed); - dump_field(out, "supports_direction", this->supports_direction); - dump_field(out, "supported_speed_count", this->supported_speed_count); - dump_field(out, "disabled_by_default", this->disabled_by_default); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesFanResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); + dump_field(out, ESPHOME_PSTR("supports_oscillation"), this->supports_oscillation); + dump_field(out, ESPHOME_PSTR("supports_speed"), this->supports_speed); + dump_field(out, ESPHOME_PSTR("supports_direction"), this->supports_direction); + dump_field(out, ESPHOME_PSTR("supported_speed_count"), this->supported_speed_count); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "entity_category", static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); for (const auto &it : *this->supported_preset_modes) { - dump_field(out, "supported_preset_modes", it, 4); + dump_field(out, ESPHOME_PSTR("supported_preset_modes"), it, 4); } #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *FanStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "FanStateResponse"); - dump_field(out, "key", this->key); - dump_field(out, "state", this->state); - dump_field(out, "oscillating", this->oscillating); - dump_field(out, "direction", static_cast(this->direction)); - dump_field(out, "speed_level", this->speed_level); - dump_field(out, "preset_mode", this->preset_mode); + MessageDumpHelper helper(out, ESPHOME_PSTR("FanStateResponse")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("state"), this->state); + dump_field(out, ESPHOME_PSTR("oscillating"), this->oscillating); + dump_field(out, ESPHOME_PSTR("direction"), static_cast(this->direction)); + dump_field(out, ESPHOME_PSTR("speed_level"), this->speed_level); + dump_field(out, ESPHOME_PSTR("preset_mode"), this->preset_mode); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *FanCommandRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "FanCommandRequest"); - dump_field(out, "key", this->key); - dump_field(out, "has_state", this->has_state); - dump_field(out, "state", this->state); - dump_field(out, "has_oscillating", this->has_oscillating); - dump_field(out, "oscillating", this->oscillating); - dump_field(out, "has_direction", this->has_direction); - dump_field(out, "direction", static_cast(this->direction)); - dump_field(out, "has_speed_level", this->has_speed_level); - dump_field(out, "speed_level", this->speed_level); - dump_field(out, "has_preset_mode", this->has_preset_mode); - dump_field(out, "preset_mode", this->preset_mode); + MessageDumpHelper helper(out, ESPHOME_PSTR("FanCommandRequest")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("has_state"), this->has_state); + dump_field(out, ESPHOME_PSTR("state"), this->state); + dump_field(out, ESPHOME_PSTR("has_oscillating"), this->has_oscillating); + dump_field(out, ESPHOME_PSTR("oscillating"), this->oscillating); + dump_field(out, ESPHOME_PSTR("has_direction"), this->has_direction); + dump_field(out, ESPHOME_PSTR("direction"), static_cast(this->direction)); + dump_field(out, ESPHOME_PSTR("has_speed_level"), this->has_speed_level); + dump_field(out, ESPHOME_PSTR("speed_level"), this->speed_level); + dump_field(out, ESPHOME_PSTR("has_preset_mode"), this->has_preset_mode); + dump_field(out, ESPHOME_PSTR("preset_mode"), this->preset_mode); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } #endif #ifdef USE_LIGHT const char *ListEntitiesLightResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesLightResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesLightResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); for (const auto &it : *this->supported_color_modes) { - dump_field(out, "supported_color_modes", static_cast(it), 4); + dump_field(out, ESPHOME_PSTR("supported_color_modes"), static_cast(it), 4); } - dump_field(out, "min_mireds", this->min_mireds); - dump_field(out, "max_mireds", this->max_mireds); + dump_field(out, ESPHOME_PSTR("min_mireds"), this->min_mireds); + dump_field(out, ESPHOME_PSTR("max_mireds"), this->max_mireds); for (const auto &it : *this->effects) { - dump_field(out, "effects", it, 4); + dump_field(out, ESPHOME_PSTR("effects"), it, 4); } - dump_field(out, "disabled_by_default", this->disabled_by_default); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "entity_category", static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *LightStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "LightStateResponse"); - dump_field(out, "key", this->key); - dump_field(out, "state", this->state); - dump_field(out, "brightness", this->brightness); - dump_field(out, "color_mode", static_cast(this->color_mode)); - dump_field(out, "color_brightness", this->color_brightness); - dump_field(out, "red", this->red); - dump_field(out, "green", this->green); - dump_field(out, "blue", this->blue); - dump_field(out, "white", this->white); - dump_field(out, "color_temperature", this->color_temperature); - dump_field(out, "cold_white", this->cold_white); - dump_field(out, "warm_white", this->warm_white); - dump_field(out, "effect", this->effect); + MessageDumpHelper helper(out, ESPHOME_PSTR("LightStateResponse")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("state"), this->state); + dump_field(out, ESPHOME_PSTR("brightness"), this->brightness); + dump_field(out, ESPHOME_PSTR("color_mode"), static_cast(this->color_mode)); + dump_field(out, ESPHOME_PSTR("color_brightness"), this->color_brightness); + dump_field(out, ESPHOME_PSTR("red"), this->red); + dump_field(out, ESPHOME_PSTR("green"), this->green); + dump_field(out, ESPHOME_PSTR("blue"), this->blue); + dump_field(out, ESPHOME_PSTR("white"), this->white); + dump_field(out, ESPHOME_PSTR("color_temperature"), this->color_temperature); + dump_field(out, ESPHOME_PSTR("cold_white"), this->cold_white); + dump_field(out, ESPHOME_PSTR("warm_white"), this->warm_white); + dump_field(out, ESPHOME_PSTR("effect"), this->effect); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *LightCommandRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "LightCommandRequest"); - dump_field(out, "key", this->key); - dump_field(out, "has_state", this->has_state); - dump_field(out, "state", this->state); - dump_field(out, "has_brightness", this->has_brightness); - dump_field(out, "brightness", this->brightness); - dump_field(out, "has_color_mode", this->has_color_mode); - dump_field(out, "color_mode", static_cast(this->color_mode)); - dump_field(out, "has_color_brightness", this->has_color_brightness); - dump_field(out, "color_brightness", this->color_brightness); - dump_field(out, "has_rgb", this->has_rgb); - dump_field(out, "red", this->red); - dump_field(out, "green", this->green); - dump_field(out, "blue", this->blue); - dump_field(out, "has_white", this->has_white); - dump_field(out, "white", this->white); - dump_field(out, "has_color_temperature", this->has_color_temperature); - dump_field(out, "color_temperature", this->color_temperature); - dump_field(out, "has_cold_white", this->has_cold_white); - dump_field(out, "cold_white", this->cold_white); - dump_field(out, "has_warm_white", this->has_warm_white); - dump_field(out, "warm_white", this->warm_white); - dump_field(out, "has_transition_length", this->has_transition_length); - dump_field(out, "transition_length", this->transition_length); - dump_field(out, "has_flash_length", this->has_flash_length); - dump_field(out, "flash_length", this->flash_length); - dump_field(out, "has_effect", this->has_effect); - dump_field(out, "effect", this->effect); + MessageDumpHelper helper(out, ESPHOME_PSTR("LightCommandRequest")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("has_state"), this->has_state); + dump_field(out, ESPHOME_PSTR("state"), this->state); + dump_field(out, ESPHOME_PSTR("has_brightness"), this->has_brightness); + dump_field(out, ESPHOME_PSTR("brightness"), this->brightness); + dump_field(out, ESPHOME_PSTR("has_color_mode"), this->has_color_mode); + dump_field(out, ESPHOME_PSTR("color_mode"), static_cast(this->color_mode)); + dump_field(out, ESPHOME_PSTR("has_color_brightness"), this->has_color_brightness); + dump_field(out, ESPHOME_PSTR("color_brightness"), this->color_brightness); + dump_field(out, ESPHOME_PSTR("has_rgb"), this->has_rgb); + dump_field(out, ESPHOME_PSTR("red"), this->red); + dump_field(out, ESPHOME_PSTR("green"), this->green); + dump_field(out, ESPHOME_PSTR("blue"), this->blue); + dump_field(out, ESPHOME_PSTR("has_white"), this->has_white); + dump_field(out, ESPHOME_PSTR("white"), this->white); + dump_field(out, ESPHOME_PSTR("has_color_temperature"), this->has_color_temperature); + dump_field(out, ESPHOME_PSTR("color_temperature"), this->color_temperature); + dump_field(out, ESPHOME_PSTR("has_cold_white"), this->has_cold_white); + dump_field(out, ESPHOME_PSTR("cold_white"), this->cold_white); + dump_field(out, ESPHOME_PSTR("has_warm_white"), this->has_warm_white); + dump_field(out, ESPHOME_PSTR("warm_white"), this->warm_white); + dump_field(out, ESPHOME_PSTR("has_transition_length"), this->has_transition_length); + dump_field(out, ESPHOME_PSTR("transition_length"), this->transition_length); + dump_field(out, ESPHOME_PSTR("has_flash_length"), this->has_flash_length); + dump_field(out, ESPHOME_PSTR("flash_length"), this->flash_length); + dump_field(out, ESPHOME_PSTR("has_effect"), this->has_effect); + dump_field(out, ESPHOME_PSTR("effect"), this->effect); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } #endif #ifdef USE_SENSOR const char *ListEntitiesSensorResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesSensorResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesSensorResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "unit_of_measurement", this->unit_of_measurement); - dump_field(out, "accuracy_decimals", this->accuracy_decimals); - dump_field(out, "force_update", this->force_update); - dump_field(out, "device_class", this->device_class); - dump_field(out, "state_class", static_cast(this->state_class)); - dump_field(out, "disabled_by_default", this->disabled_by_default); - dump_field(out, "entity_category", static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("unit_of_measurement"), this->unit_of_measurement); + dump_field(out, ESPHOME_PSTR("accuracy_decimals"), this->accuracy_decimals); + dump_field(out, ESPHOME_PSTR("force_update"), this->force_update); + dump_field(out, ESPHOME_PSTR("device_class"), this->device_class); + dump_field(out, ESPHOME_PSTR("state_class"), static_cast(this->state_class)); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *SensorStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "SensorStateResponse"); - dump_field(out, "key", this->key); - dump_field(out, "state", this->state); - dump_field(out, "missing_state", this->missing_state); + MessageDumpHelper helper(out, ESPHOME_PSTR("SensorStateResponse")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("state"), this->state); + dump_field(out, ESPHOME_PSTR("missing_state"), this->missing_state); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } #endif #ifdef USE_SWITCH const char *ListEntitiesSwitchResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesSwitchResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesSwitchResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "assumed_state", this->assumed_state); - dump_field(out, "disabled_by_default", this->disabled_by_default); - dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "device_class", this->device_class); + dump_field(out, ESPHOME_PSTR("assumed_state"), this->assumed_state); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("device_class"), this->device_class); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *SwitchStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "SwitchStateResponse"); - dump_field(out, "key", this->key); - dump_field(out, "state", this->state); + MessageDumpHelper helper(out, ESPHOME_PSTR("SwitchStateResponse")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("state"), this->state); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *SwitchCommandRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "SwitchCommandRequest"); - dump_field(out, "key", this->key); - dump_field(out, "state", this->state); + MessageDumpHelper helper(out, ESPHOME_PSTR("SwitchCommandRequest")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("state"), this->state); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } #endif #ifdef USE_TEXT_SENSOR const char *ListEntitiesTextSensorResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesTextSensorResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesTextSensorResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "disabled_by_default", this->disabled_by_default); - dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "device_class", this->device_class); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("device_class"), this->device_class); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *TextSensorStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "TextSensorStateResponse"); - dump_field(out, "key", this->key); - dump_field(out, "state", this->state); - dump_field(out, "missing_state", this->missing_state); + MessageDumpHelper helper(out, ESPHOME_PSTR("TextSensorStateResponse")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("state"), this->state); + dump_field(out, ESPHOME_PSTR("missing_state"), this->missing_state); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } #endif const char *SubscribeLogsRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "SubscribeLogsRequest"); - dump_field(out, "level", static_cast(this->level)); - dump_field(out, "dump_config", this->dump_config); + MessageDumpHelper helper(out, ESPHOME_PSTR("SubscribeLogsRequest")); + dump_field(out, ESPHOME_PSTR("level"), static_cast(this->level)); + dump_field(out, ESPHOME_PSTR("dump_config"), this->dump_config); return out.c_str(); } const char *SubscribeLogsResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "SubscribeLogsResponse"); - dump_field(out, "level", static_cast(this->level)); - dump_bytes_field(out, "message", this->message_ptr_, this->message_len_); + MessageDumpHelper helper(out, ESPHOME_PSTR("SubscribeLogsResponse")); + dump_field(out, ESPHOME_PSTR("level"), static_cast(this->level)); + dump_bytes_field(out, ESPHOME_PSTR("message"), this->message_ptr_, this->message_len_); return out.c_str(); } #ifdef USE_API_NOISE const char *NoiseEncryptionSetKeyRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "NoiseEncryptionSetKeyRequest"); - dump_bytes_field(out, "key", this->key, this->key_len); + MessageDumpHelper helper(out, ESPHOME_PSTR("NoiseEncryptionSetKeyRequest")); + dump_bytes_field(out, ESPHOME_PSTR("key"), this->key, this->key_len); return out.c_str(); } const char *NoiseEncryptionSetKeyResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "NoiseEncryptionSetKeyResponse"); - dump_field(out, "success", this->success); + MessageDumpHelper helper(out, ESPHOME_PSTR("NoiseEncryptionSetKeyResponse")); + dump_field(out, ESPHOME_PSTR("success"), this->success); return out.c_str(); } #endif #ifdef USE_API_HOMEASSISTANT_SERVICES const char *HomeassistantServiceMap::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "HomeassistantServiceMap"); - dump_field(out, "key", this->key); - dump_field(out, "value", this->value); + MessageDumpHelper helper(out, ESPHOME_PSTR("HomeassistantServiceMap")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("value"), this->value); return out.c_str(); } const char *HomeassistantActionRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "HomeassistantActionRequest"); - dump_field(out, "service", this->service); + MessageDumpHelper helper(out, ESPHOME_PSTR("HomeassistantActionRequest")); + dump_field(out, ESPHOME_PSTR("service"), this->service); for (const auto &it : this->data) { - out.append(" data: "); + out.append(4, ' ').append_p(ESPHOME_PSTR("data")).append(": "); it.dump_to(out); out.append("\n"); } for (const auto &it : this->data_template) { - out.append(" data_template: "); + out.append(4, ' ').append_p(ESPHOME_PSTR("data_template")).append(": "); it.dump_to(out); out.append("\n"); } for (const auto &it : this->variables) { - out.append(" variables: "); + out.append(4, ' ').append_p(ESPHOME_PSTR("variables")).append(": "); it.dump_to(out); out.append("\n"); } - dump_field(out, "is_event", this->is_event); + dump_field(out, ESPHOME_PSTR("is_event"), this->is_event); #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES - dump_field(out, "call_id", this->call_id); + dump_field(out, ESPHOME_PSTR("call_id"), this->call_id); #endif #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON - dump_field(out, "wants_response", this->wants_response); + dump_field(out, ESPHOME_PSTR("wants_response"), this->wants_response); #endif #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON - dump_field(out, "response_template", this->response_template); + dump_field(out, ESPHOME_PSTR("response_template"), this->response_template); #endif return out.c_str(); } #endif #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES const char *HomeassistantActionResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "HomeassistantActionResponse"); - dump_field(out, "call_id", this->call_id); - dump_field(out, "success", this->success); - dump_field(out, "error_message", this->error_message); + MessageDumpHelper helper(out, ESPHOME_PSTR("HomeassistantActionResponse")); + dump_field(out, ESPHOME_PSTR("call_id"), this->call_id); + dump_field(out, ESPHOME_PSTR("success"), this->success); + dump_field(out, ESPHOME_PSTR("error_message"), this->error_message); #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON - dump_bytes_field(out, "response_data", this->response_data, this->response_data_len); + dump_bytes_field(out, ESPHOME_PSTR("response_data"), this->response_data, this->response_data_len); #endif return out.c_str(); } #endif #ifdef USE_API_HOMEASSISTANT_STATES const char *SubscribeHomeAssistantStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "SubscribeHomeAssistantStateResponse"); - dump_field(out, "entity_id", this->entity_id); - dump_field(out, "attribute", this->attribute); - dump_field(out, "once", this->once); + MessageDumpHelper helper(out, ESPHOME_PSTR("SubscribeHomeAssistantStateResponse")); + dump_field(out, ESPHOME_PSTR("entity_id"), this->entity_id); + dump_field(out, ESPHOME_PSTR("attribute"), this->attribute); + dump_field(out, ESPHOME_PSTR("once"), this->once); return out.c_str(); } const char *HomeAssistantStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "HomeAssistantStateResponse"); - dump_field(out, "entity_id", this->entity_id); - dump_field(out, "state", this->state); - dump_field(out, "attribute", this->attribute); + MessageDumpHelper helper(out, ESPHOME_PSTR("HomeAssistantStateResponse")); + dump_field(out, ESPHOME_PSTR("entity_id"), this->entity_id); + dump_field(out, ESPHOME_PSTR("state"), this->state); + dump_field(out, ESPHOME_PSTR("attribute"), this->attribute); return out.c_str(); } #endif const char *GetTimeRequest::dump_to(DumpBuffer &out) const { - out.append("GetTimeRequest {}"); + out.append_p(ESPHOME_PSTR("GetTimeRequest {}")); return out.c_str(); } const char *DSTRule::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "DSTRule"); - dump_field(out, "time_seconds", this->time_seconds); - dump_field(out, "day", this->day); - dump_field(out, "type", static_cast(this->type)); - dump_field(out, "month", this->month); - dump_field(out, "week", this->week); - dump_field(out, "day_of_week", this->day_of_week); + MessageDumpHelper helper(out, ESPHOME_PSTR("DSTRule")); + dump_field(out, ESPHOME_PSTR("time_seconds"), this->time_seconds); + dump_field(out, ESPHOME_PSTR("day"), this->day); + dump_field(out, ESPHOME_PSTR("type"), static_cast(this->type)); + dump_field(out, ESPHOME_PSTR("month"), this->month); + dump_field(out, ESPHOME_PSTR("week"), this->week); + dump_field(out, ESPHOME_PSTR("day_of_week"), this->day_of_week); return out.c_str(); } const char *ParsedTimezone::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ParsedTimezone"); - dump_field(out, "std_offset_seconds", this->std_offset_seconds); - dump_field(out, "dst_offset_seconds", this->dst_offset_seconds); - out.append(" dst_start: "); + MessageDumpHelper helper(out, ESPHOME_PSTR("ParsedTimezone")); + dump_field(out, ESPHOME_PSTR("std_offset_seconds"), this->std_offset_seconds); + dump_field(out, ESPHOME_PSTR("dst_offset_seconds"), this->dst_offset_seconds); + out.append(2, ' ').append_p(ESPHOME_PSTR("dst_start")).append(": "); this->dst_start.dump_to(out); out.append("\n"); - out.append(" dst_end: "); + out.append(2, ' ').append_p(ESPHOME_PSTR("dst_end")).append(": "); this->dst_end.dump_to(out); out.append("\n"); return out.c_str(); } const char *GetTimeResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "GetTimeResponse"); - dump_field(out, "epoch_seconds", this->epoch_seconds); - dump_field(out, "timezone", this->timezone); - out.append(" parsed_timezone: "); + MessageDumpHelper helper(out, ESPHOME_PSTR("GetTimeResponse")); + dump_field(out, ESPHOME_PSTR("epoch_seconds"), this->epoch_seconds); + dump_field(out, ESPHOME_PSTR("timezone"), this->timezone); + out.append(2, ' ').append_p(ESPHOME_PSTR("parsed_timezone")).append(": "); this->parsed_timezone.dump_to(out); out.append("\n"); return out.c_str(); } #ifdef USE_API_USER_DEFINED_ACTIONS const char *ListEntitiesServicesArgument::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesServicesArgument"); - dump_field(out, "name", this->name); - dump_field(out, "type", static_cast(this->type)); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesServicesArgument")); + dump_field(out, ESPHOME_PSTR("name"), this->name); + dump_field(out, ESPHOME_PSTR("type"), static_cast(this->type)); return out.c_str(); } const char *ListEntitiesServicesResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesServicesResponse"); - dump_field(out, "name", this->name); - dump_field(out, "key", this->key); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesServicesResponse")); + dump_field(out, ESPHOME_PSTR("name"), this->name); + dump_field(out, ESPHOME_PSTR("key"), this->key); for (const auto &it : this->args) { - out.append(" args: "); + out.append(4, ' ').append_p(ESPHOME_PSTR("args")).append(": "); it.dump_to(out); out.append("\n"); } - dump_field(out, "supports_response", static_cast(this->supports_response)); + dump_field(out, ESPHOME_PSTR("supports_response"), static_cast(this->supports_response)); return out.c_str(); } const char *ExecuteServiceArgument::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ExecuteServiceArgument"); - dump_field(out, "bool_", this->bool_); - dump_field(out, "legacy_int", this->legacy_int); - dump_field(out, "float_", this->float_); - dump_field(out, "string_", this->string_); - dump_field(out, "int_", this->int_); + MessageDumpHelper helper(out, ESPHOME_PSTR("ExecuteServiceArgument")); + dump_field(out, ESPHOME_PSTR("bool_"), this->bool_); + dump_field(out, ESPHOME_PSTR("legacy_int"), this->legacy_int); + dump_field(out, ESPHOME_PSTR("float_"), this->float_); + dump_field(out, ESPHOME_PSTR("string_"), this->string_); + dump_field(out, ESPHOME_PSTR("int_"), this->int_); for (const auto it : this->bool_array) { - dump_field(out, "bool_array", static_cast(it), 4); + dump_field(out, ESPHOME_PSTR("bool_array"), static_cast(it), 4); } for (const auto &it : this->int_array) { - dump_field(out, "int_array", it, 4); + dump_field(out, ESPHOME_PSTR("int_array"), it, 4); } for (const auto &it : this->float_array) { - dump_field(out, "float_array", it, 4); + dump_field(out, ESPHOME_PSTR("float_array"), it, 4); } for (const auto &it : this->string_array) { - dump_field(out, "string_array", it, 4); + dump_field(out, ESPHOME_PSTR("string_array"), it, 4); } return out.c_str(); } const char *ExecuteServiceRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ExecuteServiceRequest"); - dump_field(out, "key", this->key); + MessageDumpHelper helper(out, ESPHOME_PSTR("ExecuteServiceRequest")); + dump_field(out, ESPHOME_PSTR("key"), this->key); for (const auto &it : this->args) { - out.append(" args: "); + out.append(4, ' ').append_p(ESPHOME_PSTR("args")).append(": "); it.dump_to(out); out.append("\n"); } #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES - dump_field(out, "call_id", this->call_id); + dump_field(out, ESPHOME_PSTR("call_id"), this->call_id); #endif #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES - dump_field(out, "return_response", this->return_response); + dump_field(out, ESPHOME_PSTR("return_response"), this->return_response); #endif return out.c_str(); } #endif #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES const char *ExecuteServiceResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ExecuteServiceResponse"); - dump_field(out, "call_id", this->call_id); - dump_field(out, "success", this->success); - dump_field(out, "error_message", this->error_message); + MessageDumpHelper helper(out, ESPHOME_PSTR("ExecuteServiceResponse")); + dump_field(out, ESPHOME_PSTR("call_id"), this->call_id); + dump_field(out, ESPHOME_PSTR("success"), this->success); + dump_field(out, ESPHOME_PSTR("error_message"), this->error_message); #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON - dump_bytes_field(out, "response_data", this->response_data, this->response_data_len); + dump_bytes_field(out, ESPHOME_PSTR("response_data"), this->response_data, this->response_data_len); #endif return out.c_str(); } #endif #ifdef USE_CAMERA const char *ListEntitiesCameraResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesCameraResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); - dump_field(out, "disabled_by_default", this->disabled_by_default); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesCameraResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "entity_category", static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *CameraImageResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "CameraImageResponse"); - dump_field(out, "key", this->key); - dump_bytes_field(out, "data", this->data_ptr_, this->data_len_); - dump_field(out, "done", this->done); + MessageDumpHelper helper(out, ESPHOME_PSTR("CameraImageResponse")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_bytes_field(out, ESPHOME_PSTR("data"), this->data_ptr_, this->data_len_); + dump_field(out, ESPHOME_PSTR("done"), this->done); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *CameraImageRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "CameraImageRequest"); - dump_field(out, "single", this->single); - dump_field(out, "stream", this->stream); + MessageDumpHelper helper(out, ESPHOME_PSTR("CameraImageRequest")); + dump_field(out, ESPHOME_PSTR("single"), this->single); + dump_field(out, ESPHOME_PSTR("stream"), this->stream); return out.c_str(); } #endif #ifdef USE_CLIMATE const char *ListEntitiesClimateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesClimateResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); - dump_field(out, "supports_current_temperature", this->supports_current_temperature); - dump_field(out, "supports_two_point_target_temperature", this->supports_two_point_target_temperature); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesClimateResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); + dump_field(out, ESPHOME_PSTR("supports_current_temperature"), this->supports_current_temperature); + dump_field(out, ESPHOME_PSTR("supports_two_point_target_temperature"), this->supports_two_point_target_temperature); for (const auto &it : *this->supported_modes) { - dump_field(out, "supported_modes", static_cast(it), 4); + dump_field(out, ESPHOME_PSTR("supported_modes"), static_cast(it), 4); } - dump_field(out, "visual_min_temperature", this->visual_min_temperature); - dump_field(out, "visual_max_temperature", this->visual_max_temperature); - dump_field(out, "visual_target_temperature_step", this->visual_target_temperature_step); - dump_field(out, "supports_action", this->supports_action); + dump_field(out, ESPHOME_PSTR("visual_min_temperature"), this->visual_min_temperature); + dump_field(out, ESPHOME_PSTR("visual_max_temperature"), this->visual_max_temperature); + dump_field(out, ESPHOME_PSTR("visual_target_temperature_step"), this->visual_target_temperature_step); + dump_field(out, ESPHOME_PSTR("supports_action"), this->supports_action); for (const auto &it : *this->supported_fan_modes) { - dump_field(out, "supported_fan_modes", static_cast(it), 4); + dump_field(out, ESPHOME_PSTR("supported_fan_modes"), static_cast(it), 4); } for (const auto &it : *this->supported_swing_modes) { - dump_field(out, "supported_swing_modes", static_cast(it), 4); + dump_field(out, ESPHOME_PSTR("supported_swing_modes"), static_cast(it), 4); } for (const auto &it : *this->supported_custom_fan_modes) { - dump_field(out, "supported_custom_fan_modes", it, 4); + dump_field(out, ESPHOME_PSTR("supported_custom_fan_modes"), it, 4); } for (const auto &it : *this->supported_presets) { - dump_field(out, "supported_presets", static_cast(it), 4); + dump_field(out, ESPHOME_PSTR("supported_presets"), static_cast(it), 4); } for (const auto &it : *this->supported_custom_presets) { - dump_field(out, "supported_custom_presets", it, 4); + dump_field(out, ESPHOME_PSTR("supported_custom_presets"), it, 4); } - dump_field(out, "disabled_by_default", this->disabled_by_default); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "visual_current_temperature_step", this->visual_current_temperature_step); - dump_field(out, "supports_current_humidity", this->supports_current_humidity); - dump_field(out, "supports_target_humidity", this->supports_target_humidity); - dump_field(out, "visual_min_humidity", this->visual_min_humidity); - dump_field(out, "visual_max_humidity", this->visual_max_humidity); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("visual_current_temperature_step"), this->visual_current_temperature_step); + dump_field(out, ESPHOME_PSTR("supports_current_humidity"), this->supports_current_humidity); + dump_field(out, ESPHOME_PSTR("supports_target_humidity"), this->supports_target_humidity); + dump_field(out, ESPHOME_PSTR("visual_min_humidity"), this->visual_min_humidity); + dump_field(out, ESPHOME_PSTR("visual_max_humidity"), this->visual_max_humidity); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif - dump_field(out, "feature_flags", this->feature_flags); + dump_field(out, ESPHOME_PSTR("feature_flags"), this->feature_flags); return out.c_str(); } const char *ClimateStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ClimateStateResponse"); - dump_field(out, "key", this->key); - dump_field(out, "mode", static_cast(this->mode)); - dump_field(out, "current_temperature", this->current_temperature); - dump_field(out, "target_temperature", this->target_temperature); - dump_field(out, "target_temperature_low", this->target_temperature_low); - dump_field(out, "target_temperature_high", this->target_temperature_high); - dump_field(out, "action", static_cast(this->action)); - dump_field(out, "fan_mode", static_cast(this->fan_mode)); - dump_field(out, "swing_mode", static_cast(this->swing_mode)); - dump_field(out, "custom_fan_mode", this->custom_fan_mode); - dump_field(out, "preset", static_cast(this->preset)); - dump_field(out, "custom_preset", this->custom_preset); - dump_field(out, "current_humidity", this->current_humidity); - dump_field(out, "target_humidity", this->target_humidity); + MessageDumpHelper helper(out, ESPHOME_PSTR("ClimateStateResponse")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("mode"), static_cast(this->mode)); + dump_field(out, ESPHOME_PSTR("current_temperature"), this->current_temperature); + dump_field(out, ESPHOME_PSTR("target_temperature"), this->target_temperature); + dump_field(out, ESPHOME_PSTR("target_temperature_low"), this->target_temperature_low); + dump_field(out, ESPHOME_PSTR("target_temperature_high"), this->target_temperature_high); + dump_field(out, ESPHOME_PSTR("action"), static_cast(this->action)); + dump_field(out, ESPHOME_PSTR("fan_mode"), static_cast(this->fan_mode)); + dump_field(out, ESPHOME_PSTR("swing_mode"), static_cast(this->swing_mode)); + dump_field(out, ESPHOME_PSTR("custom_fan_mode"), this->custom_fan_mode); + dump_field(out, ESPHOME_PSTR("preset"), static_cast(this->preset)); + dump_field(out, ESPHOME_PSTR("custom_preset"), this->custom_preset); + dump_field(out, ESPHOME_PSTR("current_humidity"), this->current_humidity); + dump_field(out, ESPHOME_PSTR("target_humidity"), this->target_humidity); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *ClimateCommandRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ClimateCommandRequest"); - dump_field(out, "key", this->key); - dump_field(out, "has_mode", this->has_mode); - dump_field(out, "mode", static_cast(this->mode)); - dump_field(out, "has_target_temperature", this->has_target_temperature); - dump_field(out, "target_temperature", this->target_temperature); - dump_field(out, "has_target_temperature_low", this->has_target_temperature_low); - dump_field(out, "target_temperature_low", this->target_temperature_low); - dump_field(out, "has_target_temperature_high", this->has_target_temperature_high); - dump_field(out, "target_temperature_high", this->target_temperature_high); - dump_field(out, "has_fan_mode", this->has_fan_mode); - dump_field(out, "fan_mode", static_cast(this->fan_mode)); - dump_field(out, "has_swing_mode", this->has_swing_mode); - dump_field(out, "swing_mode", static_cast(this->swing_mode)); - dump_field(out, "has_custom_fan_mode", this->has_custom_fan_mode); - dump_field(out, "custom_fan_mode", this->custom_fan_mode); - dump_field(out, "has_preset", this->has_preset); - dump_field(out, "preset", static_cast(this->preset)); - dump_field(out, "has_custom_preset", this->has_custom_preset); - dump_field(out, "custom_preset", this->custom_preset); - dump_field(out, "has_target_humidity", this->has_target_humidity); - dump_field(out, "target_humidity", this->target_humidity); + MessageDumpHelper helper(out, ESPHOME_PSTR("ClimateCommandRequest")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("has_mode"), this->has_mode); + dump_field(out, ESPHOME_PSTR("mode"), static_cast(this->mode)); + dump_field(out, ESPHOME_PSTR("has_target_temperature"), this->has_target_temperature); + dump_field(out, ESPHOME_PSTR("target_temperature"), this->target_temperature); + dump_field(out, ESPHOME_PSTR("has_target_temperature_low"), this->has_target_temperature_low); + dump_field(out, ESPHOME_PSTR("target_temperature_low"), this->target_temperature_low); + dump_field(out, ESPHOME_PSTR("has_target_temperature_high"), this->has_target_temperature_high); + dump_field(out, ESPHOME_PSTR("target_temperature_high"), this->target_temperature_high); + dump_field(out, ESPHOME_PSTR("has_fan_mode"), this->has_fan_mode); + dump_field(out, ESPHOME_PSTR("fan_mode"), static_cast(this->fan_mode)); + dump_field(out, ESPHOME_PSTR("has_swing_mode"), this->has_swing_mode); + dump_field(out, ESPHOME_PSTR("swing_mode"), static_cast(this->swing_mode)); + dump_field(out, ESPHOME_PSTR("has_custom_fan_mode"), this->has_custom_fan_mode); + dump_field(out, ESPHOME_PSTR("custom_fan_mode"), this->custom_fan_mode); + dump_field(out, ESPHOME_PSTR("has_preset"), this->has_preset); + dump_field(out, ESPHOME_PSTR("preset"), static_cast(this->preset)); + dump_field(out, ESPHOME_PSTR("has_custom_preset"), this->has_custom_preset); + dump_field(out, ESPHOME_PSTR("custom_preset"), this->custom_preset); + dump_field(out, ESPHOME_PSTR("has_target_humidity"), this->has_target_humidity); + dump_field(out, ESPHOME_PSTR("target_humidity"), this->target_humidity); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } #endif #ifdef USE_WATER_HEATER const char *ListEntitiesWaterHeaterResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesWaterHeaterResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesWaterHeaterResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "disabled_by_default", this->disabled_by_default); - dump_field(out, "entity_category", static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif - dump_field(out, "min_temperature", this->min_temperature); - dump_field(out, "max_temperature", this->max_temperature); - dump_field(out, "target_temperature_step", this->target_temperature_step); + dump_field(out, ESPHOME_PSTR("min_temperature"), this->min_temperature); + dump_field(out, ESPHOME_PSTR("max_temperature"), this->max_temperature); + dump_field(out, ESPHOME_PSTR("target_temperature_step"), this->target_temperature_step); for (const auto &it : *this->supported_modes) { - dump_field(out, "supported_modes", static_cast(it), 4); + dump_field(out, ESPHOME_PSTR("supported_modes"), static_cast(it), 4); } - dump_field(out, "supported_features", this->supported_features); + dump_field(out, ESPHOME_PSTR("supported_features"), this->supported_features); return out.c_str(); } const char *WaterHeaterStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "WaterHeaterStateResponse"); - dump_field(out, "key", this->key); - dump_field(out, "current_temperature", this->current_temperature); - dump_field(out, "target_temperature", this->target_temperature); - dump_field(out, "mode", static_cast(this->mode)); + MessageDumpHelper helper(out, ESPHOME_PSTR("WaterHeaterStateResponse")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("current_temperature"), this->current_temperature); + dump_field(out, ESPHOME_PSTR("target_temperature"), this->target_temperature); + dump_field(out, ESPHOME_PSTR("mode"), static_cast(this->mode)); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif - dump_field(out, "state", this->state); - dump_field(out, "target_temperature_low", this->target_temperature_low); - dump_field(out, "target_temperature_high", this->target_temperature_high); + dump_field(out, ESPHOME_PSTR("state"), this->state); + dump_field(out, ESPHOME_PSTR("target_temperature_low"), this->target_temperature_low); + dump_field(out, ESPHOME_PSTR("target_temperature_high"), this->target_temperature_high); return out.c_str(); } const char *WaterHeaterCommandRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "WaterHeaterCommandRequest"); - dump_field(out, "key", this->key); - dump_field(out, "has_fields", this->has_fields); - dump_field(out, "mode", static_cast(this->mode)); - dump_field(out, "target_temperature", this->target_temperature); + MessageDumpHelper helper(out, ESPHOME_PSTR("WaterHeaterCommandRequest")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("has_fields"), this->has_fields); + dump_field(out, ESPHOME_PSTR("mode"), static_cast(this->mode)); + dump_field(out, ESPHOME_PSTR("target_temperature"), this->target_temperature); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif - dump_field(out, "state", this->state); - dump_field(out, "target_temperature_low", this->target_temperature_low); - dump_field(out, "target_temperature_high", this->target_temperature_high); + dump_field(out, ESPHOME_PSTR("state"), this->state); + dump_field(out, ESPHOME_PSTR("target_temperature_low"), this->target_temperature_low); + dump_field(out, ESPHOME_PSTR("target_temperature_high"), this->target_temperature_high); return out.c_str(); } #endif #ifdef USE_NUMBER const char *ListEntitiesNumberResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesNumberResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesNumberResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "min_value", this->min_value); - dump_field(out, "max_value", this->max_value); - dump_field(out, "step", this->step); - dump_field(out, "disabled_by_default", this->disabled_by_default); - dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "unit_of_measurement", this->unit_of_measurement); - dump_field(out, "mode", static_cast(this->mode)); - dump_field(out, "device_class", this->device_class); + dump_field(out, ESPHOME_PSTR("min_value"), this->min_value); + dump_field(out, ESPHOME_PSTR("max_value"), this->max_value); + dump_field(out, ESPHOME_PSTR("step"), this->step); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("unit_of_measurement"), this->unit_of_measurement); + dump_field(out, ESPHOME_PSTR("mode"), static_cast(this->mode)); + dump_field(out, ESPHOME_PSTR("device_class"), this->device_class); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *NumberStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "NumberStateResponse"); - dump_field(out, "key", this->key); - dump_field(out, "state", this->state); - dump_field(out, "missing_state", this->missing_state); + MessageDumpHelper helper(out, ESPHOME_PSTR("NumberStateResponse")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("state"), this->state); + dump_field(out, ESPHOME_PSTR("missing_state"), this->missing_state); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *NumberCommandRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "NumberCommandRequest"); - dump_field(out, "key", this->key); - dump_field(out, "state", this->state); + MessageDumpHelper helper(out, ESPHOME_PSTR("NumberCommandRequest")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("state"), this->state); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } #endif #ifdef USE_SELECT const char *ListEntitiesSelectResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesSelectResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesSelectResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif for (const auto &it : *this->options) { - dump_field(out, "options", it, 4); + dump_field(out, ESPHOME_PSTR("options"), it, 4); } - dump_field(out, "disabled_by_default", this->disabled_by_default); - dump_field(out, "entity_category", static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *SelectStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "SelectStateResponse"); - dump_field(out, "key", this->key); - dump_field(out, "state", this->state); - dump_field(out, "missing_state", this->missing_state); + MessageDumpHelper helper(out, ESPHOME_PSTR("SelectStateResponse")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("state"), this->state); + dump_field(out, ESPHOME_PSTR("missing_state"), this->missing_state); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *SelectCommandRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "SelectCommandRequest"); - dump_field(out, "key", this->key); - dump_field(out, "state", this->state); + MessageDumpHelper helper(out, ESPHOME_PSTR("SelectCommandRequest")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("state"), this->state); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } #endif #ifdef USE_SIREN const char *ListEntitiesSirenResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesSirenResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesSirenResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "disabled_by_default", this->disabled_by_default); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); for (const auto &it : *this->tones) { - dump_field(out, "tones", it, 4); + dump_field(out, ESPHOME_PSTR("tones"), it, 4); } - dump_field(out, "supports_duration", this->supports_duration); - dump_field(out, "supports_volume", this->supports_volume); - dump_field(out, "entity_category", static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("supports_duration"), this->supports_duration); + dump_field(out, ESPHOME_PSTR("supports_volume"), this->supports_volume); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *SirenStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "SirenStateResponse"); - dump_field(out, "key", this->key); - dump_field(out, "state", this->state); + MessageDumpHelper helper(out, ESPHOME_PSTR("SirenStateResponse")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("state"), this->state); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *SirenCommandRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "SirenCommandRequest"); - dump_field(out, "key", this->key); - dump_field(out, "has_state", this->has_state); - dump_field(out, "state", this->state); - dump_field(out, "has_tone", this->has_tone); - dump_field(out, "tone", this->tone); - dump_field(out, "has_duration", this->has_duration); - dump_field(out, "duration", this->duration); - dump_field(out, "has_volume", this->has_volume); - dump_field(out, "volume", this->volume); + MessageDumpHelper helper(out, ESPHOME_PSTR("SirenCommandRequest")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("has_state"), this->has_state); + dump_field(out, ESPHOME_PSTR("state"), this->state); + dump_field(out, ESPHOME_PSTR("has_tone"), this->has_tone); + dump_field(out, ESPHOME_PSTR("tone"), this->tone); + dump_field(out, ESPHOME_PSTR("has_duration"), this->has_duration); + dump_field(out, ESPHOME_PSTR("duration"), this->duration); + dump_field(out, ESPHOME_PSTR("has_volume"), this->has_volume); + dump_field(out, ESPHOME_PSTR("volume"), this->volume); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } #endif #ifdef USE_LOCK const char *ListEntitiesLockResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesLockResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesLockResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "disabled_by_default", this->disabled_by_default); - dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "assumed_state", this->assumed_state); - dump_field(out, "supports_open", this->supports_open); - dump_field(out, "requires_code", this->requires_code); - dump_field(out, "code_format", this->code_format); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("assumed_state"), this->assumed_state); + dump_field(out, ESPHOME_PSTR("supports_open"), this->supports_open); + dump_field(out, ESPHOME_PSTR("requires_code"), this->requires_code); + dump_field(out, ESPHOME_PSTR("code_format"), this->code_format); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *LockStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "LockStateResponse"); - dump_field(out, "key", this->key); - dump_field(out, "state", static_cast(this->state)); + MessageDumpHelper helper(out, ESPHOME_PSTR("LockStateResponse")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("state"), static_cast(this->state)); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *LockCommandRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "LockCommandRequest"); - dump_field(out, "key", this->key); - dump_field(out, "command", static_cast(this->command)); - dump_field(out, "has_code", this->has_code); - dump_field(out, "code", this->code); + MessageDumpHelper helper(out, ESPHOME_PSTR("LockCommandRequest")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("command"), static_cast(this->command)); + dump_field(out, ESPHOME_PSTR("has_code"), this->has_code); + dump_field(out, ESPHOME_PSTR("code"), this->code); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } #endif #ifdef USE_BUTTON const char *ListEntitiesButtonResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesButtonResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesButtonResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "disabled_by_default", this->disabled_by_default); - dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "device_class", this->device_class); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("device_class"), this->device_class); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *ButtonCommandRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ButtonCommandRequest"); - dump_field(out, "key", this->key); + MessageDumpHelper helper(out, ESPHOME_PSTR("ButtonCommandRequest")); + dump_field(out, ESPHOME_PSTR("key"), this->key); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } #endif #ifdef USE_MEDIA_PLAYER const char *MediaPlayerSupportedFormat::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "MediaPlayerSupportedFormat"); - dump_field(out, "format", this->format); - dump_field(out, "sample_rate", this->sample_rate); - dump_field(out, "num_channels", this->num_channels); - dump_field(out, "purpose", static_cast(this->purpose)); - dump_field(out, "sample_bytes", this->sample_bytes); + MessageDumpHelper helper(out, ESPHOME_PSTR("MediaPlayerSupportedFormat")); + dump_field(out, ESPHOME_PSTR("format"), this->format); + dump_field(out, ESPHOME_PSTR("sample_rate"), this->sample_rate); + dump_field(out, ESPHOME_PSTR("num_channels"), this->num_channels); + dump_field(out, ESPHOME_PSTR("purpose"), static_cast(this->purpose)); + dump_field(out, ESPHOME_PSTR("sample_bytes"), this->sample_bytes); return out.c_str(); } const char *ListEntitiesMediaPlayerResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesMediaPlayerResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesMediaPlayerResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "disabled_by_default", this->disabled_by_default); - dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "supports_pause", this->supports_pause); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("supports_pause"), this->supports_pause); for (const auto &it : this->supported_formats) { - out.append(" supported_formats: "); + out.append(4, ' ').append_p(ESPHOME_PSTR("supported_formats")).append(": "); it.dump_to(out); out.append("\n"); } #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif - dump_field(out, "feature_flags", this->feature_flags); + dump_field(out, ESPHOME_PSTR("feature_flags"), this->feature_flags); return out.c_str(); } const char *MediaPlayerStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "MediaPlayerStateResponse"); - dump_field(out, "key", this->key); - dump_field(out, "state", static_cast(this->state)); - dump_field(out, "volume", this->volume); - dump_field(out, "muted", this->muted); + MessageDumpHelper helper(out, ESPHOME_PSTR("MediaPlayerStateResponse")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("state"), static_cast(this->state)); + dump_field(out, ESPHOME_PSTR("volume"), this->volume); + dump_field(out, ESPHOME_PSTR("muted"), this->muted); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *MediaPlayerCommandRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "MediaPlayerCommandRequest"); - dump_field(out, "key", this->key); - dump_field(out, "has_command", this->has_command); - dump_field(out, "command", static_cast(this->command)); - dump_field(out, "has_volume", this->has_volume); - dump_field(out, "volume", this->volume); - dump_field(out, "has_media_url", this->has_media_url); - dump_field(out, "media_url", this->media_url); - dump_field(out, "has_announcement", this->has_announcement); - dump_field(out, "announcement", this->announcement); + MessageDumpHelper helper(out, ESPHOME_PSTR("MediaPlayerCommandRequest")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("has_command"), this->has_command); + dump_field(out, ESPHOME_PSTR("command"), static_cast(this->command)); + dump_field(out, ESPHOME_PSTR("has_volume"), this->has_volume); + dump_field(out, ESPHOME_PSTR("volume"), this->volume); + dump_field(out, ESPHOME_PSTR("has_media_url"), this->has_media_url); + dump_field(out, ESPHOME_PSTR("media_url"), this->media_url); + dump_field(out, ESPHOME_PSTR("has_announcement"), this->has_announcement); + dump_field(out, ESPHOME_PSTR("announcement"), this->announcement); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } #endif #ifdef USE_BLUETOOTH_PROXY const char *SubscribeBluetoothLEAdvertisementsRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "SubscribeBluetoothLEAdvertisementsRequest"); - dump_field(out, "flags", this->flags); + MessageDumpHelper helper(out, ESPHOME_PSTR("SubscribeBluetoothLEAdvertisementsRequest")); + dump_field(out, ESPHOME_PSTR("flags"), this->flags); return out.c_str(); } const char *BluetoothLERawAdvertisement::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothLERawAdvertisement"); - dump_field(out, "address", this->address); - dump_field(out, "rssi", this->rssi); - dump_field(out, "address_type", this->address_type); - dump_bytes_field(out, "data", this->data, this->data_len); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothLERawAdvertisement")); + dump_field(out, ESPHOME_PSTR("address"), this->address); + dump_field(out, ESPHOME_PSTR("rssi"), this->rssi); + dump_field(out, ESPHOME_PSTR("address_type"), this->address_type); + dump_bytes_field(out, ESPHOME_PSTR("data"), this->data, this->data_len); return out.c_str(); } const char *BluetoothLERawAdvertisementsResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothLERawAdvertisementsResponse"); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothLERawAdvertisementsResponse")); for (uint16_t i = 0; i < this->advertisements_len; i++) { - out.append(" advertisements: "); + out.append(4, ' ').append_p(ESPHOME_PSTR("advertisements")).append(": "); this->advertisements[i].dump_to(out); out.append("\n"); } return out.c_str(); } const char *BluetoothDeviceRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothDeviceRequest"); - dump_field(out, "address", this->address); - dump_field(out, "request_type", static_cast(this->request_type)); - dump_field(out, "has_address_type", this->has_address_type); - dump_field(out, "address_type", this->address_type); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothDeviceRequest")); + dump_field(out, ESPHOME_PSTR("address"), this->address); + dump_field(out, ESPHOME_PSTR("request_type"), static_cast(this->request_type)); + dump_field(out, ESPHOME_PSTR("has_address_type"), this->has_address_type); + dump_field(out, ESPHOME_PSTR("address_type"), this->address_type); return out.c_str(); } const char *BluetoothDeviceConnectionResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothDeviceConnectionResponse"); - dump_field(out, "address", this->address); - dump_field(out, "connected", this->connected); - dump_field(out, "mtu", this->mtu); - dump_field(out, "error", this->error); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothDeviceConnectionResponse")); + dump_field(out, ESPHOME_PSTR("address"), this->address); + dump_field(out, ESPHOME_PSTR("connected"), this->connected); + dump_field(out, ESPHOME_PSTR("mtu"), this->mtu); + dump_field(out, ESPHOME_PSTR("error"), this->error); return out.c_str(); } const char *BluetoothGATTGetServicesRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothGATTGetServicesRequest"); - dump_field(out, "address", this->address); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothGATTGetServicesRequest")); + dump_field(out, ESPHOME_PSTR("address"), this->address); return out.c_str(); } const char *BluetoothGATTDescriptor::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothGATTDescriptor"); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothGATTDescriptor")); for (const auto &it : this->uuid) { - dump_field(out, "uuid", it, 4); + dump_field(out, ESPHOME_PSTR("uuid"), it, 4); } - dump_field(out, "handle", this->handle); - dump_field(out, "short_uuid", this->short_uuid); + dump_field(out, ESPHOME_PSTR("handle"), this->handle); + dump_field(out, ESPHOME_PSTR("short_uuid"), this->short_uuid); return out.c_str(); } const char *BluetoothGATTCharacteristic::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothGATTCharacteristic"); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothGATTCharacteristic")); for (const auto &it : this->uuid) { - dump_field(out, "uuid", it, 4); + dump_field(out, ESPHOME_PSTR("uuid"), it, 4); } - dump_field(out, "handle", this->handle); - dump_field(out, "properties", this->properties); + dump_field(out, ESPHOME_PSTR("handle"), this->handle); + dump_field(out, ESPHOME_PSTR("properties"), this->properties); for (const auto &it : this->descriptors) { - out.append(" descriptors: "); + out.append(4, ' ').append_p(ESPHOME_PSTR("descriptors")).append(": "); it.dump_to(out); out.append("\n"); } - dump_field(out, "short_uuid", this->short_uuid); + dump_field(out, ESPHOME_PSTR("short_uuid"), this->short_uuid); return out.c_str(); } const char *BluetoothGATTService::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothGATTService"); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothGATTService")); for (const auto &it : this->uuid) { - dump_field(out, "uuid", it, 4); + dump_field(out, ESPHOME_PSTR("uuid"), it, 4); } - dump_field(out, "handle", this->handle); + dump_field(out, ESPHOME_PSTR("handle"), this->handle); for (const auto &it : this->characteristics) { - out.append(" characteristics: "); + out.append(4, ' ').append_p(ESPHOME_PSTR("characteristics")).append(": "); it.dump_to(out); out.append("\n"); } - dump_field(out, "short_uuid", this->short_uuid); + dump_field(out, ESPHOME_PSTR("short_uuid"), this->short_uuid); return out.c_str(); } const char *BluetoothGATTGetServicesResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothGATTGetServicesResponse"); - dump_field(out, "address", this->address); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothGATTGetServicesResponse")); + dump_field(out, ESPHOME_PSTR("address"), this->address); for (const auto &it : this->services) { - out.append(" services: "); + out.append(4, ' ').append_p(ESPHOME_PSTR("services")).append(": "); it.dump_to(out); out.append("\n"); } return out.c_str(); } const char *BluetoothGATTGetServicesDoneResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothGATTGetServicesDoneResponse"); - dump_field(out, "address", this->address); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothGATTGetServicesDoneResponse")); + dump_field(out, ESPHOME_PSTR("address"), this->address); return out.c_str(); } const char *BluetoothGATTReadRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothGATTReadRequest"); - dump_field(out, "address", this->address); - dump_field(out, "handle", this->handle); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothGATTReadRequest")); + dump_field(out, ESPHOME_PSTR("address"), this->address); + dump_field(out, ESPHOME_PSTR("handle"), this->handle); return out.c_str(); } const char *BluetoothGATTReadResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothGATTReadResponse"); - dump_field(out, "address", this->address); - dump_field(out, "handle", this->handle); - dump_bytes_field(out, "data", this->data_ptr_, this->data_len_); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothGATTReadResponse")); + dump_field(out, ESPHOME_PSTR("address"), this->address); + dump_field(out, ESPHOME_PSTR("handle"), this->handle); + dump_bytes_field(out, ESPHOME_PSTR("data"), this->data_ptr_, this->data_len_); return out.c_str(); } const char *BluetoothGATTWriteRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothGATTWriteRequest"); - dump_field(out, "address", this->address); - dump_field(out, "handle", this->handle); - dump_field(out, "response", this->response); - dump_bytes_field(out, "data", this->data, this->data_len); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothGATTWriteRequest")); + dump_field(out, ESPHOME_PSTR("address"), this->address); + dump_field(out, ESPHOME_PSTR("handle"), this->handle); + dump_field(out, ESPHOME_PSTR("response"), this->response); + dump_bytes_field(out, ESPHOME_PSTR("data"), this->data, this->data_len); return out.c_str(); } const char *BluetoothGATTReadDescriptorRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothGATTReadDescriptorRequest"); - dump_field(out, "address", this->address); - dump_field(out, "handle", this->handle); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothGATTReadDescriptorRequest")); + dump_field(out, ESPHOME_PSTR("address"), this->address); + dump_field(out, ESPHOME_PSTR("handle"), this->handle); return out.c_str(); } const char *BluetoothGATTWriteDescriptorRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothGATTWriteDescriptorRequest"); - dump_field(out, "address", this->address); - dump_field(out, "handle", this->handle); - dump_bytes_field(out, "data", this->data, this->data_len); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothGATTWriteDescriptorRequest")); + dump_field(out, ESPHOME_PSTR("address"), this->address); + dump_field(out, ESPHOME_PSTR("handle"), this->handle); + dump_bytes_field(out, ESPHOME_PSTR("data"), this->data, this->data_len); return out.c_str(); } const char *BluetoothGATTNotifyRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothGATTNotifyRequest"); - dump_field(out, "address", this->address); - dump_field(out, "handle", this->handle); - dump_field(out, "enable", this->enable); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothGATTNotifyRequest")); + dump_field(out, ESPHOME_PSTR("address"), this->address); + dump_field(out, ESPHOME_PSTR("handle"), this->handle); + dump_field(out, ESPHOME_PSTR("enable"), this->enable); return out.c_str(); } const char *BluetoothGATTNotifyDataResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothGATTNotifyDataResponse"); - dump_field(out, "address", this->address); - dump_field(out, "handle", this->handle); - dump_bytes_field(out, "data", this->data_ptr_, this->data_len_); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothGATTNotifyDataResponse")); + dump_field(out, ESPHOME_PSTR("address"), this->address); + dump_field(out, ESPHOME_PSTR("handle"), this->handle); + dump_bytes_field(out, ESPHOME_PSTR("data"), this->data_ptr_, this->data_len_); return out.c_str(); } const char *BluetoothConnectionsFreeResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothConnectionsFreeResponse"); - dump_field(out, "free", this->free); - dump_field(out, "limit", this->limit); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothConnectionsFreeResponse")); + dump_field(out, ESPHOME_PSTR("free"), this->free); + dump_field(out, ESPHOME_PSTR("limit"), this->limit); for (const auto &it : this->allocated) { - dump_field(out, "allocated", it, 4); + dump_field(out, ESPHOME_PSTR("allocated"), it, 4); } return out.c_str(); } const char *BluetoothGATTErrorResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothGATTErrorResponse"); - dump_field(out, "address", this->address); - dump_field(out, "handle", this->handle); - dump_field(out, "error", this->error); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothGATTErrorResponse")); + dump_field(out, ESPHOME_PSTR("address"), this->address); + dump_field(out, ESPHOME_PSTR("handle"), this->handle); + dump_field(out, ESPHOME_PSTR("error"), this->error); return out.c_str(); } const char *BluetoothGATTWriteResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothGATTWriteResponse"); - dump_field(out, "address", this->address); - dump_field(out, "handle", this->handle); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothGATTWriteResponse")); + dump_field(out, ESPHOME_PSTR("address"), this->address); + dump_field(out, ESPHOME_PSTR("handle"), this->handle); return out.c_str(); } const char *BluetoothGATTNotifyResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothGATTNotifyResponse"); - dump_field(out, "address", this->address); - dump_field(out, "handle", this->handle); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothGATTNotifyResponse")); + dump_field(out, ESPHOME_PSTR("address"), this->address); + dump_field(out, ESPHOME_PSTR("handle"), this->handle); return out.c_str(); } const char *BluetoothDevicePairingResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothDevicePairingResponse"); - dump_field(out, "address", this->address); - dump_field(out, "paired", this->paired); - dump_field(out, "error", this->error); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothDevicePairingResponse")); + dump_field(out, ESPHOME_PSTR("address"), this->address); + dump_field(out, ESPHOME_PSTR("paired"), this->paired); + dump_field(out, ESPHOME_PSTR("error"), this->error); return out.c_str(); } const char *BluetoothDeviceUnpairingResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothDeviceUnpairingResponse"); - dump_field(out, "address", this->address); - dump_field(out, "success", this->success); - dump_field(out, "error", this->error); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothDeviceUnpairingResponse")); + dump_field(out, ESPHOME_PSTR("address"), this->address); + dump_field(out, ESPHOME_PSTR("success"), this->success); + dump_field(out, ESPHOME_PSTR("error"), this->error); return out.c_str(); } const char *BluetoothDeviceClearCacheResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothDeviceClearCacheResponse"); - dump_field(out, "address", this->address); - dump_field(out, "success", this->success); - dump_field(out, "error", this->error); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothDeviceClearCacheResponse")); + dump_field(out, ESPHOME_PSTR("address"), this->address); + dump_field(out, ESPHOME_PSTR("success"), this->success); + dump_field(out, ESPHOME_PSTR("error"), this->error); return out.c_str(); } const char *BluetoothScannerStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothScannerStateResponse"); - dump_field(out, "state", static_cast(this->state)); - dump_field(out, "mode", static_cast(this->mode)); - dump_field(out, "configured_mode", static_cast(this->configured_mode)); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothScannerStateResponse")); + dump_field(out, ESPHOME_PSTR("state"), static_cast(this->state)); + dump_field(out, ESPHOME_PSTR("mode"), static_cast(this->mode)); + dump_field(out, ESPHOME_PSTR("configured_mode"), static_cast(this->configured_mode)); return out.c_str(); } const char *BluetoothScannerSetModeRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothScannerSetModeRequest"); - dump_field(out, "mode", static_cast(this->mode)); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothScannerSetModeRequest")); + dump_field(out, ESPHOME_PSTR("mode"), static_cast(this->mode)); return out.c_str(); } #endif #ifdef USE_VOICE_ASSISTANT const char *SubscribeVoiceAssistantRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "SubscribeVoiceAssistantRequest"); - dump_field(out, "subscribe", this->subscribe); - dump_field(out, "flags", this->flags); + MessageDumpHelper helper(out, ESPHOME_PSTR("SubscribeVoiceAssistantRequest")); + dump_field(out, ESPHOME_PSTR("subscribe"), this->subscribe); + dump_field(out, ESPHOME_PSTR("flags"), this->flags); return out.c_str(); } const char *VoiceAssistantAudioSettings::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "VoiceAssistantAudioSettings"); - dump_field(out, "noise_suppression_level", this->noise_suppression_level); - dump_field(out, "auto_gain", this->auto_gain); - dump_field(out, "volume_multiplier", this->volume_multiplier); + MessageDumpHelper helper(out, ESPHOME_PSTR("VoiceAssistantAudioSettings")); + dump_field(out, ESPHOME_PSTR("noise_suppression_level"), this->noise_suppression_level); + dump_field(out, ESPHOME_PSTR("auto_gain"), this->auto_gain); + dump_field(out, ESPHOME_PSTR("volume_multiplier"), this->volume_multiplier); return out.c_str(); } const char *VoiceAssistantRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "VoiceAssistantRequest"); - dump_field(out, "start", this->start); - dump_field(out, "conversation_id", this->conversation_id); - dump_field(out, "flags", this->flags); - out.append(" audio_settings: "); + MessageDumpHelper helper(out, ESPHOME_PSTR("VoiceAssistantRequest")); + dump_field(out, ESPHOME_PSTR("start"), this->start); + dump_field(out, ESPHOME_PSTR("conversation_id"), this->conversation_id); + dump_field(out, ESPHOME_PSTR("flags"), this->flags); + out.append(2, ' ').append_p(ESPHOME_PSTR("audio_settings")).append(": "); this->audio_settings.dump_to(out); out.append("\n"); - dump_field(out, "wake_word_phrase", this->wake_word_phrase); + dump_field(out, ESPHOME_PSTR("wake_word_phrase"), this->wake_word_phrase); return out.c_str(); } const char *VoiceAssistantResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "VoiceAssistantResponse"); - dump_field(out, "port", this->port); - dump_field(out, "error", this->error); + MessageDumpHelper helper(out, ESPHOME_PSTR("VoiceAssistantResponse")); + dump_field(out, ESPHOME_PSTR("port"), this->port); + dump_field(out, ESPHOME_PSTR("error"), this->error); return out.c_str(); } const char *VoiceAssistantEventData::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "VoiceAssistantEventData"); - dump_field(out, "name", this->name); - dump_field(out, "value", this->value); + MessageDumpHelper helper(out, ESPHOME_PSTR("VoiceAssistantEventData")); + dump_field(out, ESPHOME_PSTR("name"), this->name); + dump_field(out, ESPHOME_PSTR("value"), this->value); return out.c_str(); } const char *VoiceAssistantEventResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "VoiceAssistantEventResponse"); - dump_field(out, "event_type", static_cast(this->event_type)); + MessageDumpHelper helper(out, ESPHOME_PSTR("VoiceAssistantEventResponse")); + dump_field(out, ESPHOME_PSTR("event_type"), static_cast(this->event_type)); for (const auto &it : this->data) { - out.append(" data: "); + out.append(4, ' ').append_p(ESPHOME_PSTR("data")).append(": "); it.dump_to(out); out.append("\n"); } return out.c_str(); } const char *VoiceAssistantAudio::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "VoiceAssistantAudio"); - dump_bytes_field(out, "data", this->data, this->data_len); - dump_field(out, "end", this->end); + MessageDumpHelper helper(out, ESPHOME_PSTR("VoiceAssistantAudio")); + dump_bytes_field(out, ESPHOME_PSTR("data"), this->data, this->data_len); + dump_field(out, ESPHOME_PSTR("end"), this->end); return out.c_str(); } const char *VoiceAssistantTimerEventResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "VoiceAssistantTimerEventResponse"); - dump_field(out, "event_type", static_cast(this->event_type)); - dump_field(out, "timer_id", this->timer_id); - dump_field(out, "name", this->name); - dump_field(out, "total_seconds", this->total_seconds); - dump_field(out, "seconds_left", this->seconds_left); - dump_field(out, "is_active", this->is_active); + MessageDumpHelper helper(out, ESPHOME_PSTR("VoiceAssistantTimerEventResponse")); + dump_field(out, ESPHOME_PSTR("event_type"), static_cast(this->event_type)); + dump_field(out, ESPHOME_PSTR("timer_id"), this->timer_id); + dump_field(out, ESPHOME_PSTR("name"), this->name); + dump_field(out, ESPHOME_PSTR("total_seconds"), this->total_seconds); + dump_field(out, ESPHOME_PSTR("seconds_left"), this->seconds_left); + dump_field(out, ESPHOME_PSTR("is_active"), this->is_active); return out.c_str(); } const char *VoiceAssistantAnnounceRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "VoiceAssistantAnnounceRequest"); - dump_field(out, "media_id", this->media_id); - dump_field(out, "text", this->text); - dump_field(out, "preannounce_media_id", this->preannounce_media_id); - dump_field(out, "start_conversation", this->start_conversation); + MessageDumpHelper helper(out, ESPHOME_PSTR("VoiceAssistantAnnounceRequest")); + dump_field(out, ESPHOME_PSTR("media_id"), this->media_id); + dump_field(out, ESPHOME_PSTR("text"), this->text); + dump_field(out, ESPHOME_PSTR("preannounce_media_id"), this->preannounce_media_id); + dump_field(out, ESPHOME_PSTR("start_conversation"), this->start_conversation); return out.c_str(); } const char *VoiceAssistantAnnounceFinished::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "VoiceAssistantAnnounceFinished"); - dump_field(out, "success", this->success); + MessageDumpHelper helper(out, ESPHOME_PSTR("VoiceAssistantAnnounceFinished")); + dump_field(out, ESPHOME_PSTR("success"), this->success); return out.c_str(); } const char *VoiceAssistantWakeWord::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "VoiceAssistantWakeWord"); - dump_field(out, "id", this->id); - dump_field(out, "wake_word", this->wake_word); + MessageDumpHelper helper(out, ESPHOME_PSTR("VoiceAssistantWakeWord")); + dump_field(out, ESPHOME_PSTR("id"), this->id); + dump_field(out, ESPHOME_PSTR("wake_word"), this->wake_word); for (const auto &it : this->trained_languages) { - dump_field(out, "trained_languages", it, 4); + dump_field(out, ESPHOME_PSTR("trained_languages"), it, 4); } return out.c_str(); } const char *VoiceAssistantExternalWakeWord::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "VoiceAssistantExternalWakeWord"); - dump_field(out, "id", this->id); - dump_field(out, "wake_word", this->wake_word); + MessageDumpHelper helper(out, ESPHOME_PSTR("VoiceAssistantExternalWakeWord")); + dump_field(out, ESPHOME_PSTR("id"), this->id); + dump_field(out, ESPHOME_PSTR("wake_word"), this->wake_word); for (const auto &it : this->trained_languages) { - dump_field(out, "trained_languages", it, 4); + dump_field(out, ESPHOME_PSTR("trained_languages"), it, 4); } - dump_field(out, "model_type", this->model_type); - dump_field(out, "model_size", this->model_size); - dump_field(out, "model_hash", this->model_hash); - dump_field(out, "url", this->url); + dump_field(out, ESPHOME_PSTR("model_type"), this->model_type); + dump_field(out, ESPHOME_PSTR("model_size"), this->model_size); + dump_field(out, ESPHOME_PSTR("model_hash"), this->model_hash); + dump_field(out, ESPHOME_PSTR("url"), this->url); return out.c_str(); } const char *VoiceAssistantConfigurationRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "VoiceAssistantConfigurationRequest"); + MessageDumpHelper helper(out, ESPHOME_PSTR("VoiceAssistantConfigurationRequest")); for (const auto &it : this->external_wake_words) { - out.append(" external_wake_words: "); + out.append(4, ' ').append_p(ESPHOME_PSTR("external_wake_words")).append(": "); it.dump_to(out); out.append("\n"); } return out.c_str(); } const char *VoiceAssistantConfigurationResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "VoiceAssistantConfigurationResponse"); + MessageDumpHelper helper(out, ESPHOME_PSTR("VoiceAssistantConfigurationResponse")); for (const auto &it : this->available_wake_words) { - out.append(" available_wake_words: "); + out.append(4, ' ').append_p(ESPHOME_PSTR("available_wake_words")).append(": "); it.dump_to(out); out.append("\n"); } for (const auto &it : *this->active_wake_words) { - dump_field(out, "active_wake_words", it, 4); + dump_field(out, ESPHOME_PSTR("active_wake_words"), it, 4); } - dump_field(out, "max_active_wake_words", this->max_active_wake_words); + dump_field(out, ESPHOME_PSTR("max_active_wake_words"), this->max_active_wake_words); return out.c_str(); } const char *VoiceAssistantSetConfiguration::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "VoiceAssistantSetConfiguration"); + MessageDumpHelper helper(out, ESPHOME_PSTR("VoiceAssistantSetConfiguration")); for (const auto &it : this->active_wake_words) { - dump_field(out, "active_wake_words", it, 4); + dump_field(out, ESPHOME_PSTR("active_wake_words"), it, 4); } return out.c_str(); } #endif #ifdef USE_ALARM_CONTROL_PANEL const char *ListEntitiesAlarmControlPanelResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesAlarmControlPanelResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesAlarmControlPanelResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "disabled_by_default", this->disabled_by_default); - dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "supported_features", this->supported_features); - dump_field(out, "requires_code", this->requires_code); - dump_field(out, "requires_code_to_arm", this->requires_code_to_arm); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("supported_features"), this->supported_features); + dump_field(out, ESPHOME_PSTR("requires_code"), this->requires_code); + dump_field(out, ESPHOME_PSTR("requires_code_to_arm"), this->requires_code_to_arm); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *AlarmControlPanelStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "AlarmControlPanelStateResponse"); - dump_field(out, "key", this->key); - dump_field(out, "state", static_cast(this->state)); + MessageDumpHelper helper(out, ESPHOME_PSTR("AlarmControlPanelStateResponse")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("state"), static_cast(this->state)); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *AlarmControlPanelCommandRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "AlarmControlPanelCommandRequest"); - dump_field(out, "key", this->key); - dump_field(out, "command", static_cast(this->command)); - dump_field(out, "code", this->code); + MessageDumpHelper helper(out, ESPHOME_PSTR("AlarmControlPanelCommandRequest")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("command"), static_cast(this->command)); + dump_field(out, ESPHOME_PSTR("code"), this->code); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } #endif #ifdef USE_TEXT const char *ListEntitiesTextResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesTextResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesTextResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "disabled_by_default", this->disabled_by_default); - dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "min_length", this->min_length); - dump_field(out, "max_length", this->max_length); - dump_field(out, "pattern", this->pattern); - dump_field(out, "mode", static_cast(this->mode)); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("min_length"), this->min_length); + dump_field(out, ESPHOME_PSTR("max_length"), this->max_length); + dump_field(out, ESPHOME_PSTR("pattern"), this->pattern); + dump_field(out, ESPHOME_PSTR("mode"), static_cast(this->mode)); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *TextStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "TextStateResponse"); - dump_field(out, "key", this->key); - dump_field(out, "state", this->state); - dump_field(out, "missing_state", this->missing_state); + MessageDumpHelper helper(out, ESPHOME_PSTR("TextStateResponse")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("state"), this->state); + dump_field(out, ESPHOME_PSTR("missing_state"), this->missing_state); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *TextCommandRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "TextCommandRequest"); - dump_field(out, "key", this->key); - dump_field(out, "state", this->state); + MessageDumpHelper helper(out, ESPHOME_PSTR("TextCommandRequest")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("state"), this->state); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } #endif #ifdef USE_DATETIME_DATE const char *ListEntitiesDateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesDateResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesDateResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "disabled_by_default", this->disabled_by_default); - dump_field(out, "entity_category", static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *DateStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "DateStateResponse"); - dump_field(out, "key", this->key); - dump_field(out, "missing_state", this->missing_state); - dump_field(out, "year", this->year); - dump_field(out, "month", this->month); - dump_field(out, "day", this->day); + MessageDumpHelper helper(out, ESPHOME_PSTR("DateStateResponse")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("missing_state"), this->missing_state); + dump_field(out, ESPHOME_PSTR("year"), this->year); + dump_field(out, ESPHOME_PSTR("month"), this->month); + dump_field(out, ESPHOME_PSTR("day"), this->day); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *DateCommandRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "DateCommandRequest"); - dump_field(out, "key", this->key); - dump_field(out, "year", this->year); - dump_field(out, "month", this->month); - dump_field(out, "day", this->day); + MessageDumpHelper helper(out, ESPHOME_PSTR("DateCommandRequest")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("year"), this->year); + dump_field(out, ESPHOME_PSTR("month"), this->month); + dump_field(out, ESPHOME_PSTR("day"), this->day); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } #endif #ifdef USE_DATETIME_TIME const char *ListEntitiesTimeResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesTimeResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesTimeResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "disabled_by_default", this->disabled_by_default); - dump_field(out, "entity_category", static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *TimeStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "TimeStateResponse"); - dump_field(out, "key", this->key); - dump_field(out, "missing_state", this->missing_state); - dump_field(out, "hour", this->hour); - dump_field(out, "minute", this->minute); - dump_field(out, "second", this->second); + MessageDumpHelper helper(out, ESPHOME_PSTR("TimeStateResponse")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("missing_state"), this->missing_state); + dump_field(out, ESPHOME_PSTR("hour"), this->hour); + dump_field(out, ESPHOME_PSTR("minute"), this->minute); + dump_field(out, ESPHOME_PSTR("second"), this->second); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *TimeCommandRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "TimeCommandRequest"); - dump_field(out, "key", this->key); - dump_field(out, "hour", this->hour); - dump_field(out, "minute", this->minute); - dump_field(out, "second", this->second); + MessageDumpHelper helper(out, ESPHOME_PSTR("TimeCommandRequest")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("hour"), this->hour); + dump_field(out, ESPHOME_PSTR("minute"), this->minute); + dump_field(out, ESPHOME_PSTR("second"), this->second); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } #endif #ifdef USE_EVENT const char *ListEntitiesEventResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesEventResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesEventResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "disabled_by_default", this->disabled_by_default); - dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "device_class", this->device_class); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("device_class"), this->device_class); for (const auto &it : *this->event_types) { - dump_field(out, "event_types", it, 4); + dump_field(out, ESPHOME_PSTR("event_types"), it, 4); } #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *EventResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "EventResponse"); - dump_field(out, "key", this->key); - dump_field(out, "event_type", this->event_type); + MessageDumpHelper helper(out, ESPHOME_PSTR("EventResponse")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("event_type"), this->event_type); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } #endif #ifdef USE_VALVE const char *ListEntitiesValveResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesValveResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesValveResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "disabled_by_default", this->disabled_by_default); - dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "device_class", this->device_class); - dump_field(out, "assumed_state", this->assumed_state); - dump_field(out, "supports_position", this->supports_position); - dump_field(out, "supports_stop", this->supports_stop); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("device_class"), this->device_class); + dump_field(out, ESPHOME_PSTR("assumed_state"), this->assumed_state); + dump_field(out, ESPHOME_PSTR("supports_position"), this->supports_position); + dump_field(out, ESPHOME_PSTR("supports_stop"), this->supports_stop); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *ValveStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ValveStateResponse"); - dump_field(out, "key", this->key); - dump_field(out, "position", this->position); - dump_field(out, "current_operation", static_cast(this->current_operation)); + MessageDumpHelper helper(out, ESPHOME_PSTR("ValveStateResponse")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("position"), this->position); + dump_field(out, ESPHOME_PSTR("current_operation"), static_cast(this->current_operation)); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *ValveCommandRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ValveCommandRequest"); - dump_field(out, "key", this->key); - dump_field(out, "has_position", this->has_position); - dump_field(out, "position", this->position); - dump_field(out, "stop", this->stop); + MessageDumpHelper helper(out, ESPHOME_PSTR("ValveCommandRequest")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("has_position"), this->has_position); + dump_field(out, ESPHOME_PSTR("position"), this->position); + dump_field(out, ESPHOME_PSTR("stop"), this->stop); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } #endif #ifdef USE_DATETIME_DATETIME const char *ListEntitiesDateTimeResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesDateTimeResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesDateTimeResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "disabled_by_default", this->disabled_by_default); - dump_field(out, "entity_category", static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *DateTimeStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "DateTimeStateResponse"); - dump_field(out, "key", this->key); - dump_field(out, "missing_state", this->missing_state); - dump_field(out, "epoch_seconds", this->epoch_seconds); + MessageDumpHelper helper(out, ESPHOME_PSTR("DateTimeStateResponse")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("missing_state"), this->missing_state); + dump_field(out, ESPHOME_PSTR("epoch_seconds"), this->epoch_seconds); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *DateTimeCommandRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "DateTimeCommandRequest"); - dump_field(out, "key", this->key); - dump_field(out, "epoch_seconds", this->epoch_seconds); + MessageDumpHelper helper(out, ESPHOME_PSTR("DateTimeCommandRequest")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("epoch_seconds"), this->epoch_seconds); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } #endif #ifdef USE_UPDATE const char *ListEntitiesUpdateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesUpdateResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesUpdateResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "disabled_by_default", this->disabled_by_default); - dump_field(out, "entity_category", static_cast(this->entity_category)); - dump_field(out, "device_class", this->device_class); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("device_class"), this->device_class); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *UpdateStateResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "UpdateStateResponse"); - dump_field(out, "key", this->key); - dump_field(out, "missing_state", this->missing_state); - dump_field(out, "in_progress", this->in_progress); - dump_field(out, "has_progress", this->has_progress); - dump_field(out, "progress", this->progress); - dump_field(out, "current_version", this->current_version); - dump_field(out, "latest_version", this->latest_version); - dump_field(out, "title", this->title); - dump_field(out, "release_summary", this->release_summary); - dump_field(out, "release_url", this->release_url); + MessageDumpHelper helper(out, ESPHOME_PSTR("UpdateStateResponse")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("missing_state"), this->missing_state); + dump_field(out, ESPHOME_PSTR("in_progress"), this->in_progress); + dump_field(out, ESPHOME_PSTR("has_progress"), this->has_progress); + dump_field(out, ESPHOME_PSTR("progress"), this->progress); + dump_field(out, ESPHOME_PSTR("current_version"), this->current_version); + dump_field(out, ESPHOME_PSTR("latest_version"), this->latest_version); + dump_field(out, ESPHOME_PSTR("title"), this->title); + dump_field(out, ESPHOME_PSTR("release_summary"), this->release_summary); + dump_field(out, ESPHOME_PSTR("release_url"), this->release_url); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } const char *UpdateCommandRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "UpdateCommandRequest"); - dump_field(out, "key", this->key); - dump_field(out, "command", static_cast(this->command)); + MessageDumpHelper helper(out, ESPHOME_PSTR("UpdateCommandRequest")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("command"), static_cast(this->command)); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif return out.c_str(); } #endif #ifdef USE_ZWAVE_PROXY const char *ZWaveProxyFrame::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ZWaveProxyFrame"); - dump_bytes_field(out, "data", this->data, this->data_len); + MessageDumpHelper helper(out, ESPHOME_PSTR("ZWaveProxyFrame")); + dump_bytes_field(out, ESPHOME_PSTR("data"), this->data, this->data_len); return out.c_str(); } const char *ZWaveProxyRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ZWaveProxyRequest"); - dump_field(out, "type", static_cast(this->type)); - dump_bytes_field(out, "data", this->data, this->data_len); + MessageDumpHelper helper(out, ESPHOME_PSTR("ZWaveProxyRequest")); + dump_field(out, ESPHOME_PSTR("type"), static_cast(this->type)); + dump_bytes_field(out, ESPHOME_PSTR("data"), this->data, this->data_len); return out.c_str(); } #endif #ifdef USE_INFRARED const char *ListEntitiesInfraredResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "ListEntitiesInfraredResponse"); - dump_field(out, "object_id", this->object_id); - dump_field(out, "key", this->key); - dump_field(out, "name", this->name); + MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesInfraredResponse")); + dump_field(out, ESPHOME_PSTR("object_id"), this->object_id); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("name"), this->name); #ifdef USE_ENTITY_ICON - dump_field(out, "icon", this->icon); + dump_field(out, ESPHOME_PSTR("icon"), this->icon); #endif - dump_field(out, "disabled_by_default", this->disabled_by_default); - dump_field(out, "entity_category", static_cast(this->entity_category)); + dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default); + dump_field(out, ESPHOME_PSTR("entity_category"), static_cast(this->entity_category)); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif - dump_field(out, "capabilities", this->capabilities); + dump_field(out, ESPHOME_PSTR("capabilities"), this->capabilities); + dump_field(out, ESPHOME_PSTR("receiver_frequency"), this->receiver_frequency); return out.c_str(); } #endif #ifdef USE_IR_RF const char *InfraredRFTransmitRawTimingsRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "InfraredRFTransmitRawTimingsRequest"); + MessageDumpHelper helper(out, ESPHOME_PSTR("InfraredRFTransmitRawTimingsRequest")); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif - dump_field(out, "key", this->key); - dump_field(out, "carrier_frequency", this->carrier_frequency); - dump_field(out, "repeat_count", this->repeat_count); - out.append(" timings: "); - out.append("packed buffer ["); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("carrier_frequency"), this->carrier_frequency); + dump_field(out, ESPHOME_PSTR("repeat_count"), this->repeat_count); + out.append(2, ' ').append_p(ESPHOME_PSTR("timings")).append(": "); + out.append_p(ESPHOME_PSTR("packed buffer [")); append_uint(out, this->timings_count_); - out.append(" values, "); + out.append_p(ESPHOME_PSTR(" values, ")); append_uint(out, this->timings_length_); - out.append(" bytes]\n"); + out.append_p(ESPHOME_PSTR(" bytes]\n")); return out.c_str(); } const char *InfraredRFReceiveEvent::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "InfraredRFReceiveEvent"); + MessageDumpHelper helper(out, ESPHOME_PSTR("InfraredRFReceiveEvent")); #ifdef USE_DEVICES - dump_field(out, "device_id", this->device_id); + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); #endif - dump_field(out, "key", this->key); + dump_field(out, ESPHOME_PSTR("key"), this->key); for (const auto &it : *this->timings) { - dump_field(out, "timings", it, 4); + dump_field(out, ESPHOME_PSTR("timings"), it, 4); } return out.c_str(); } #endif #ifdef USE_SERIAL_PROXY const char *SerialProxyConfigureRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "SerialProxyConfigureRequest"); - dump_field(out, "instance", this->instance); - dump_field(out, "baudrate", this->baudrate); - dump_field(out, "flow_control", this->flow_control); - dump_field(out, "parity", static_cast(this->parity)); - dump_field(out, "stop_bits", this->stop_bits); - dump_field(out, "data_size", this->data_size); + MessageDumpHelper helper(out, ESPHOME_PSTR("SerialProxyConfigureRequest")); + dump_field(out, ESPHOME_PSTR("instance"), this->instance); + dump_field(out, ESPHOME_PSTR("baudrate"), this->baudrate); + dump_field(out, ESPHOME_PSTR("flow_control"), this->flow_control); + dump_field(out, ESPHOME_PSTR("parity"), static_cast(this->parity)); + dump_field(out, ESPHOME_PSTR("stop_bits"), this->stop_bits); + dump_field(out, ESPHOME_PSTR("data_size"), this->data_size); return out.c_str(); } const char *SerialProxyDataReceived::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "SerialProxyDataReceived"); - dump_field(out, "instance", this->instance); - dump_bytes_field(out, "data", this->data_ptr_, this->data_len_); + MessageDumpHelper helper(out, ESPHOME_PSTR("SerialProxyDataReceived")); + dump_field(out, ESPHOME_PSTR("instance"), this->instance); + dump_bytes_field(out, ESPHOME_PSTR("data"), this->data_ptr_, this->data_len_); return out.c_str(); } const char *SerialProxyWriteRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "SerialProxyWriteRequest"); - dump_field(out, "instance", this->instance); - dump_bytes_field(out, "data", this->data, this->data_len); + MessageDumpHelper helper(out, ESPHOME_PSTR("SerialProxyWriteRequest")); + dump_field(out, ESPHOME_PSTR("instance"), this->instance); + dump_bytes_field(out, ESPHOME_PSTR("data"), this->data, this->data_len); return out.c_str(); } const char *SerialProxySetModemPinsRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "SerialProxySetModemPinsRequest"); - dump_field(out, "instance", this->instance); - dump_field(out, "line_states", this->line_states); + MessageDumpHelper helper(out, ESPHOME_PSTR("SerialProxySetModemPinsRequest")); + dump_field(out, ESPHOME_PSTR("instance"), this->instance); + dump_field(out, ESPHOME_PSTR("line_states"), this->line_states); return out.c_str(); } const char *SerialProxyGetModemPinsRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "SerialProxyGetModemPinsRequest"); - dump_field(out, "instance", this->instance); + MessageDumpHelper helper(out, ESPHOME_PSTR("SerialProxyGetModemPinsRequest")); + dump_field(out, ESPHOME_PSTR("instance"), this->instance); return out.c_str(); } const char *SerialProxyGetModemPinsResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "SerialProxyGetModemPinsResponse"); - dump_field(out, "instance", this->instance); - dump_field(out, "line_states", this->line_states); + MessageDumpHelper helper(out, ESPHOME_PSTR("SerialProxyGetModemPinsResponse")); + dump_field(out, ESPHOME_PSTR("instance"), this->instance); + dump_field(out, ESPHOME_PSTR("line_states"), this->line_states); return out.c_str(); } const char *SerialProxyRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "SerialProxyRequest"); - dump_field(out, "instance", this->instance); - dump_field(out, "type", static_cast(this->type)); + MessageDumpHelper helper(out, ESPHOME_PSTR("SerialProxyRequest")); + dump_field(out, ESPHOME_PSTR("instance"), this->instance); + dump_field(out, ESPHOME_PSTR("type"), static_cast(this->type)); return out.c_str(); } const char *SerialProxyRequestResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "SerialProxyRequestResponse"); - dump_field(out, "instance", this->instance); - dump_field(out, "type", static_cast(this->type)); - dump_field(out, "status", static_cast(this->status)); - dump_field(out, "error_message", this->error_message); + MessageDumpHelper helper(out, ESPHOME_PSTR("SerialProxyRequestResponse")); + dump_field(out, ESPHOME_PSTR("instance"), this->instance); + dump_field(out, ESPHOME_PSTR("type"), static_cast(this->type)); + dump_field(out, ESPHOME_PSTR("status"), static_cast(this->status)); + dump_field(out, ESPHOME_PSTR("error_message"), this->error_message); return out.c_str(); } #endif #ifdef USE_BLUETOOTH_PROXY const char *BluetoothSetConnectionParamsRequest::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothSetConnectionParamsRequest"); - dump_field(out, "address", this->address); - dump_field(out, "min_interval", this->min_interval); - dump_field(out, "max_interval", this->max_interval); - dump_field(out, "latency", this->latency); - dump_field(out, "timeout", this->timeout); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothSetConnectionParamsRequest")); + dump_field(out, ESPHOME_PSTR("address"), this->address); + dump_field(out, ESPHOME_PSTR("min_interval"), this->min_interval); + dump_field(out, ESPHOME_PSTR("max_interval"), this->max_interval); + dump_field(out, ESPHOME_PSTR("latency"), this->latency); + dump_field(out, ESPHOME_PSTR("timeout"), this->timeout); return out.c_str(); } const char *BluetoothSetConnectionParamsResponse::dump_to(DumpBuffer &out) const { - MessageDumpHelper helper(out, "BluetoothSetConnectionParamsResponse"); - dump_field(out, "address", this->address); - dump_field(out, "error", this->error); + MessageDumpHelper helper(out, ESPHOME_PSTR("BluetoothSetConnectionParamsResponse")); + dump_field(out, ESPHOME_PSTR("address"), this->address); + dump_field(out, ESPHOME_PSTR("error"), this->error); return out.c_str(); } #endif diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index f2f7fa5238..b41233eddd 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -1,6 +1,7 @@ // This file was automatically generated with a tool. // See script/api_protobuf/api_protobuf.py #include "api_pb2_service.h" +#include "api_connection.h" #include "esphome/core/log.h" namespace esphome::api { @@ -8,8 +9,8 @@ namespace esphome::api { static const char *const TAG = "api.service"; #ifdef HAS_PROTO_MESSAGE_DUMP -void APIServerConnectionBase::log_send_message_(const char *name, const char *dump) { - ESP_LOGVV(TAG, "send_message %s: %s", name, dump); +void APIServerConnectionBase::log_send_message_(const LogString *name, const char *dump) { + ESP_LOGVV(TAG, "send_message %s: %s", LOG_STR_ARG(name), dump); } void APIServerConnectionBase::log_receive_message_(const LogString *name, const ProtoMessage &msg) { DumpBuffer dump_buf; @@ -20,7 +21,7 @@ void APIServerConnectionBase::log_receive_message_(const LogString *name) { } #endif -void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) { +void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) { // Check authentication/connection requirements switch (msg_type) { case HelloRequest::MESSAGE_TYPE: // No setup required diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 10fd88d8e1..6ff988902f 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -8,238 +8,234 @@ namespace esphome::api { -class APIServerConnectionBase : public ProtoService { +class APIServerConnectionBase { public: #ifdef HAS_PROTO_MESSAGE_DUMP protected: - void log_send_message_(const char *name, const char *dump); + void log_send_message_(const LogString *name, const char *dump); void log_receive_message_(const LogString *name, const ProtoMessage &msg); void log_receive_message_(const LogString *name); public: #endif - virtual void on_hello_request(const HelloRequest &value){}; + void on_hello_request(const HelloRequest &value){}; - virtual void on_disconnect_request(){}; - virtual void on_disconnect_response(){}; - virtual void on_ping_request(){}; - virtual void on_ping_response(){}; - virtual void on_device_info_request(){}; + void on_disconnect_request(){}; + void on_disconnect_response(){}; + void on_ping_request(){}; + void on_ping_response(){}; + void on_device_info_request(){}; - virtual void on_list_entities_request(){}; + void on_list_entities_request(){}; - virtual void on_subscribe_states_request(){}; + void on_subscribe_states_request(){}; #ifdef USE_COVER - virtual void on_cover_command_request(const CoverCommandRequest &value){}; + void on_cover_command_request(const CoverCommandRequest &value){}; #endif #ifdef USE_FAN - virtual void on_fan_command_request(const FanCommandRequest &value){}; + void on_fan_command_request(const FanCommandRequest &value){}; #endif #ifdef USE_LIGHT - virtual void on_light_command_request(const LightCommandRequest &value){}; + void on_light_command_request(const LightCommandRequest &value){}; #endif #ifdef USE_SWITCH - virtual void on_switch_command_request(const SwitchCommandRequest &value){}; + void on_switch_command_request(const SwitchCommandRequest &value){}; #endif - virtual void on_subscribe_logs_request(const SubscribeLogsRequest &value){}; + void on_subscribe_logs_request(const SubscribeLogsRequest &value){}; #ifdef USE_API_NOISE - virtual void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){}; + void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){}; #endif #ifdef USE_API_HOMEASSISTANT_SERVICES - virtual void on_subscribe_homeassistant_services_request(){}; + void on_subscribe_homeassistant_services_request(){}; #endif #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES - virtual void on_homeassistant_action_response(const HomeassistantActionResponse &value){}; + void on_homeassistant_action_response(const HomeassistantActionResponse &value){}; #endif #ifdef USE_API_HOMEASSISTANT_STATES - virtual void on_subscribe_home_assistant_states_request(){}; + void on_subscribe_home_assistant_states_request(){}; #endif #ifdef USE_API_HOMEASSISTANT_STATES - virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){}; + void on_home_assistant_state_response(const HomeAssistantStateResponse &value){}; #endif - virtual void on_get_time_response(const GetTimeResponse &value){}; + void on_get_time_response(const GetTimeResponse &value){}; #ifdef USE_API_USER_DEFINED_ACTIONS - virtual void on_execute_service_request(const ExecuteServiceRequest &value){}; + void on_execute_service_request(const ExecuteServiceRequest &value){}; #endif #ifdef USE_CAMERA - virtual void on_camera_image_request(const CameraImageRequest &value){}; + void on_camera_image_request(const CameraImageRequest &value){}; #endif #ifdef USE_CLIMATE - virtual void on_climate_command_request(const ClimateCommandRequest &value){}; + void on_climate_command_request(const ClimateCommandRequest &value){}; #endif #ifdef USE_WATER_HEATER - virtual void on_water_heater_command_request(const WaterHeaterCommandRequest &value){}; + void on_water_heater_command_request(const WaterHeaterCommandRequest &value){}; #endif #ifdef USE_NUMBER - virtual void on_number_command_request(const NumberCommandRequest &value){}; + void on_number_command_request(const NumberCommandRequest &value){}; #endif #ifdef USE_SELECT - virtual void on_select_command_request(const SelectCommandRequest &value){}; + void on_select_command_request(const SelectCommandRequest &value){}; #endif #ifdef USE_SIREN - virtual void on_siren_command_request(const SirenCommandRequest &value){}; + void on_siren_command_request(const SirenCommandRequest &value){}; #endif #ifdef USE_LOCK - virtual void on_lock_command_request(const LockCommandRequest &value){}; + void on_lock_command_request(const LockCommandRequest &value){}; #endif #ifdef USE_BUTTON - virtual void on_button_command_request(const ButtonCommandRequest &value){}; + void on_button_command_request(const ButtonCommandRequest &value){}; #endif #ifdef USE_MEDIA_PLAYER - virtual void on_media_player_command_request(const MediaPlayerCommandRequest &value){}; + void on_media_player_command_request(const MediaPlayerCommandRequest &value){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_subscribe_bluetooth_le_advertisements_request( - const SubscribeBluetoothLEAdvertisementsRequest &value){}; + void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &value){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_bluetooth_device_request(const BluetoothDeviceRequest &value){}; + void on_bluetooth_device_request(const BluetoothDeviceRequest &value){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){}; + void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){}; + void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value){}; + void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &value){}; + void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &value){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &value){}; + void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &value){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value){}; + void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_subscribe_bluetooth_connections_free_request(){}; + void on_subscribe_bluetooth_connections_free_request(){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_unsubscribe_bluetooth_le_advertisements_request(){}; + void on_unsubscribe_bluetooth_le_advertisements_request(){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &value){}; + void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &value){}; #endif #ifdef USE_VOICE_ASSISTANT - virtual void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){}; + void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){}; #endif #ifdef USE_VOICE_ASSISTANT - virtual void on_voice_assistant_response(const VoiceAssistantResponse &value){}; + void on_voice_assistant_response(const VoiceAssistantResponse &value){}; #endif #ifdef USE_VOICE_ASSISTANT - virtual void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){}; + void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){}; #endif #ifdef USE_VOICE_ASSISTANT - virtual void on_voice_assistant_audio(const VoiceAssistantAudio &value){}; + void on_voice_assistant_audio(const VoiceAssistantAudio &value){}; #endif #ifdef USE_VOICE_ASSISTANT - virtual void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &value){}; + void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &value){}; #endif #ifdef USE_VOICE_ASSISTANT - virtual void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &value){}; + void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &value){}; #endif #ifdef USE_VOICE_ASSISTANT - virtual void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &value){}; + void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &value){}; #endif #ifdef USE_VOICE_ASSISTANT - virtual void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value){}; + void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value){}; #endif #ifdef USE_ALARM_CONTROL_PANEL - virtual void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &value){}; + void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &value){}; #endif #ifdef USE_TEXT - virtual void on_text_command_request(const TextCommandRequest &value){}; + void on_text_command_request(const TextCommandRequest &value){}; #endif #ifdef USE_DATETIME_DATE - virtual void on_date_command_request(const DateCommandRequest &value){}; + void on_date_command_request(const DateCommandRequest &value){}; #endif #ifdef USE_DATETIME_TIME - virtual void on_time_command_request(const TimeCommandRequest &value){}; + void on_time_command_request(const TimeCommandRequest &value){}; #endif #ifdef USE_VALVE - virtual void on_valve_command_request(const ValveCommandRequest &value){}; + void on_valve_command_request(const ValveCommandRequest &value){}; #endif #ifdef USE_DATETIME_DATETIME - virtual void on_date_time_command_request(const DateTimeCommandRequest &value){}; + void on_date_time_command_request(const DateTimeCommandRequest &value){}; #endif #ifdef USE_UPDATE - virtual void on_update_command_request(const UpdateCommandRequest &value){}; + void on_update_command_request(const UpdateCommandRequest &value){}; #endif #ifdef USE_ZWAVE_PROXY - virtual void on_z_wave_proxy_frame(const ZWaveProxyFrame &value){}; + void on_z_wave_proxy_frame(const ZWaveProxyFrame &value){}; #endif #ifdef USE_ZWAVE_PROXY - virtual void on_z_wave_proxy_request(const ZWaveProxyRequest &value){}; + void on_z_wave_proxy_request(const ZWaveProxyRequest &value){}; #endif #ifdef USE_IR_RF - virtual void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &value){}; + void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &value){}; #endif #ifdef USE_SERIAL_PROXY - virtual void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &value){}; + void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &value){}; #endif #ifdef USE_SERIAL_PROXY - virtual void on_serial_proxy_write_request(const SerialProxyWriteRequest &value){}; + void on_serial_proxy_write_request(const SerialProxyWriteRequest &value){}; #endif #ifdef USE_SERIAL_PROXY - virtual void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &value){}; + void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &value){}; #endif #ifdef USE_SERIAL_PROXY - virtual void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &value){}; + void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &value){}; #endif #ifdef USE_SERIAL_PROXY - virtual void on_serial_proxy_request(const SerialProxyRequest &value){}; + void on_serial_proxy_request(const SerialProxyRequest &value){}; #endif #ifdef USE_BLUETOOTH_PROXY - virtual void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &value){}; + void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &value){}; #endif - - protected: - void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override; }; } // namespace esphome::api diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 17d69405ad..d9c3cc6846 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -46,10 +46,8 @@ void APIServer::setup() { #ifndef USE_API_NOISE_PSK_FROM_YAML // Only load saved PSK if not set from YAML - SavedNoisePsk noise_pref_saved{}; - if (this->noise_pref_.load(&noise_pref_saved)) { + if (this->load_and_apply_noise_psk_()) { ESP_LOGD(TAG, "Loaded saved Noise PSK"); - this->set_noise_psk(noise_pref_saved.psk); } #endif #endif @@ -110,7 +108,7 @@ void APIServer::setup() { this->last_connected_ = App.get_loop_component_start_time(); // Set warning status if reboot timeout is enabled if (this->reboot_timeout_ != 0) { - this->status_set_warning(); + this->status_set_warning(LOG_STR("waiting for client connection")); } } @@ -189,7 +187,7 @@ void APIServer::remove_client_(size_t client_index) { // Last client disconnected - set warning and start tracking for reboot timeout if (this->clients_.empty() && this->reboot_timeout_ != 0) { - this->status_set_warning(); + this->status_set_warning(LOG_STR("waiting for client connection")); this->last_connected_ = App.get_loop_component_start_time(); } @@ -514,7 +512,7 @@ void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeo #ifdef USE_API_NOISE bool APIServer::update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, - const LogString *fail_log_msg, const psk_t &active_psk, bool make_active) { + const LogString *fail_log_msg, bool make_active) { if (!this->noise_pref_.save(&new_psk)) { ESP_LOGW(TAG, "%s", LOG_STR_ARG(fail_log_msg)); return false; @@ -526,9 +524,14 @@ bool APIServer::update_noise_psk_(const SavedNoisePsk &new_psk, const LogString } ESP_LOGD(TAG, "%s", LOG_STR_ARG(save_log_msg)); if (make_active) { - this->set_timeout(100, [this, active_psk]() { + this->set_timeout(100, [this]() { + // Re-read the PSK from preferences rather than capturing the 32-byte array + // in the lambda (which would exceed std::function SBO and heap-allocate). + if (!this->load_and_apply_noise_psk_()) { + ESP_LOGW(TAG, "Failed to load saved PSK for activation"); + return; + } ESP_LOGW(TAG, "Disconnecting all clients to reset PSK"); - this->set_noise_psk(active_psk); for (auto &c : this->clients_) { DisconnectRequest req; c->send_message(req); @@ -538,6 +541,14 @@ bool APIServer::update_noise_psk_(const SavedNoisePsk &new_psk, const LogString return true; } +bool APIServer::load_and_apply_noise_psk_() { + SavedNoisePsk saved{}; + if (!this->noise_pref_.load(&saved)) + return false; + this->set_noise_psk(saved.psk); + return true; +} + bool APIServer::save_noise_psk(psk_t psk, bool make_active) { #ifdef USE_API_NOISE_PSK_FROM_YAML // When PSK is set from YAML, this function should never be called @@ -552,7 +563,7 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) { } SavedNoisePsk new_saved_psk{psk}; - return this->update_noise_psk_(new_saved_psk, LOG_STR("Noise PSK saved"), LOG_STR("Failed to save Noise PSK"), psk, + return this->update_noise_psk_(new_saved_psk, LOG_STR("Noise PSK saved"), LOG_STR("Failed to save Noise PSK"), make_active); #endif } @@ -564,8 +575,7 @@ bool APIServer::clear_noise_psk(bool make_active) { return false; #else SavedNoisePsk empty_psk{}; - psk_t empty{}; - return this->update_noise_psk_(empty_psk, LOG_STR("Noise PSK cleared"), LOG_STR("Failed to clear Noise PSK"), empty, + return this->update_noise_psk_(empty_psk, LOG_STR("Noise PSK cleared"), LOG_STR("Failed to clear Noise PSK"), make_active); #endif } diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 69fc26cc00..65076879a2 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -36,11 +36,11 @@ struct SavedNoisePsk { } PACKED; // NOLINT #endif -class APIServer : public Component, - public Controller +class APIServer final : public Component, + public Controller #ifdef USE_CAMERA , - public camera::CameraListener + public camera::CameraListener #endif { public: @@ -239,7 +239,9 @@ class APIServer : public Component, #ifdef USE_API_NOISE bool update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, const LogString *fail_log_msg, - const psk_t &active_psk, bool make_active); + bool make_active); + // Load saved PSK from preferences and apply it. Returns true on success. + bool load_and_apply_noise_psk_(); #endif // USE_API_NOISE #ifdef USE_API_HOMEASSISTANT_STATES // Helper methods to reduce code duplication diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py index 0e71ad8fcb..0c6c569c7d 100644 --- a/esphome/components/api/client.py +++ b/esphome/components/api/client.py @@ -32,7 +32,11 @@ if TYPE_CHECKING: _LOGGER = logging.getLogger(__name__) -async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None: +async def async_run_logs( + config: dict[str, Any], + addresses: list[str], + subscribe_states: bool = True, +) -> None: """Run the logs command in the event loop.""" conf = config["api"] name = config["esphome"]["name"] @@ -89,14 +93,20 @@ async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None: config, raw_line, backtrace_state=backtrace_state ) - stop = await async_run(cli, on_log, name=name) + stop = await async_run(cli, on_log, name=name, subscribe_states=subscribe_states) try: await asyncio.Event().wait() finally: await stop() -def run_logs(config: dict[str, Any], addresses: list[str]) -> None: +def run_logs( + config: dict[str, Any], + addresses: list[str], + subscribe_states: bool = True, +) -> None: """Run the logs command.""" with contextlib.suppress(KeyboardInterrupt): - asyncio.run(async_run_logs(config, addresses)) + asyncio.run( + async_run_logs(config, addresses, subscribe_states=subscribe_states) + ) diff --git a/esphome/components/api/custom_api_device.h b/esphome/components/api/custom_api_device.h index 2fd9cb0dd2..c2b0f14bcc 100644 --- a/esphome/components/api/custom_api_device.h +++ b/esphome/components/api/custom_api_device.h @@ -136,8 +136,9 @@ class CustomAPIDevice { template void subscribe_homeassistant_state(void (T::*callback)(StringRef), const std::string &entity_id, const std::string &attribute = "") { - auto f = std::bind(callback, (T *) this, std::placeholders::_1); - global_api_server->subscribe_home_assistant_state(entity_id, optional(attribute), std::move(f)); + auto *obj = static_cast(this); + global_api_server->subscribe_home_assistant_state(entity_id, optional(attribute), + [obj, callback](StringRef state) { (obj->*callback)(state); }); } /** Subscribe to the state (or attribute state) of an entity from Home Assistant (legacy std::string version). @@ -148,10 +149,12 @@ class CustomAPIDevice { ESPDEPRECATED("Use void callback(StringRef) instead. Will be removed in 2027.1.0.", "2026.1.0") void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id, const std::string &attribute = "") { - auto f = std::bind(callback, (T *) this, std::placeholders::_1); + auto *obj = static_cast(this); // Explicit type to disambiguate overload resolution - global_api_server->subscribe_home_assistant_state(entity_id, optional(attribute), - std::function(f)); + global_api_server->subscribe_home_assistant_state( + entity_id, optional(attribute), + std::function( + [obj, callback](const std::string &state) { (obj->*callback)(state); })); } /** Subscribe to the state (or attribute state) of an entity from Home Assistant. @@ -176,8 +179,10 @@ class CustomAPIDevice { template void subscribe_homeassistant_state(void (T::*callback)(const std::string &, StringRef), const std::string &entity_id, const std::string &attribute = "") { - auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1); - global_api_server->subscribe_home_assistant_state(entity_id, optional(attribute), std::move(f)); + auto *obj = static_cast(this); + global_api_server->subscribe_home_assistant_state( + entity_id, optional(attribute), + [obj, callback, entity_id](StringRef state) { (obj->*callback)(entity_id, state); }); } /** Subscribe to the state (or attribute state) of an entity from Home Assistant (legacy std::string version). @@ -188,10 +193,12 @@ class CustomAPIDevice { ESPDEPRECATED("Use void callback(const std::string &, StringRef) instead. Will be removed in 2027.1.0.", "2026.1.0") void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id, const std::string &attribute = "") { - auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1); + auto *obj = static_cast(this); // Explicit type to disambiguate overload resolution - global_api_server->subscribe_home_assistant_state(entity_id, optional(attribute), - std::function(f)); + global_api_server->subscribe_home_assistant_state( + entity_id, optional(attribute), + std::function( + [obj, callback, entity_id](const std::string &state) { (obj->*callback)(entity_id, state); })); } #else template diff --git a/esphome/components/api/proto.cpp b/esphome/components/api/proto.cpp index 4f5b3f0918..236e4a474a 100644 --- a/esphome/components/api/proto.cpp +++ b/esphome/components/api/proto.cpp @@ -145,14 +145,15 @@ uint32_t ProtoDecodableMessage::count_repeated_field(const uint8_t *buffer, size // [tag][v1][v2][body ..... body] // ^-- pos_ = element end, within buffer void ProtoWriteBuffer::encode_sub_message(uint32_t field_id, const void *value, - void (*encode_fn)(const void *, ProtoWriteBuffer &)) { + uint8_t *(*encode_fn)(const void *, + ProtoWriteBuffer &PROTO_ENCODE_DEBUG_PARAM)) { this->encode_field_raw(field_id, 2); // Reserve 1 byte for length varint (optimistic: submessage < 128 bytes) uint8_t *len_pos = this->pos_; this->debug_check_bounds_(1); this->pos_++; uint8_t *body_start = this->pos_; - encode_fn(value, *this); + this->pos_ = encode_fn(value, *this PROTO_ENCODE_DEBUG_INIT(this->buffer_)); uint32_t body_size = static_cast(this->pos_ - body_start); if (body_size < 128) [[likely]] { // Common case: 1-byte varint, just backpatch @@ -173,22 +174,27 @@ void ProtoWriteBuffer::encode_sub_message(uint32_t field_id, const void *value, // Non-template core for encode_optional_sub_message. void ProtoWriteBuffer::encode_optional_sub_message(uint32_t field_id, uint32_t nested_size, const void *value, - void (*encode_fn)(const void *, ProtoWriteBuffer &)) { + uint8_t *(*encode_fn)(const void *, + ProtoWriteBuffer &PROTO_ENCODE_DEBUG_PARAM)) { if (nested_size == 0) return; this->encode_field_raw(field_id, 2); this->encode_varint_raw(nested_size); #ifdef ESPHOME_DEBUG_API uint8_t *start = this->pos_; - encode_fn(value, *this); + this->pos_ = encode_fn(value, *this PROTO_ENCODE_DEBUG_INIT(this->buffer_)); if (static_cast(this->pos_ - start) != nested_size) this->debug_check_encode_size_(field_id, nested_size, this->pos_ - start); #else - encode_fn(value, *this); + this->pos_ = encode_fn(value, *this PROTO_ENCODE_DEBUG_INIT(this->buffer_)); #endif } #ifdef ESPHOME_DEBUG_API +void proto_check_bounds_failed(const uint8_t *pos, size_t bytes, const uint8_t *end, const char *caller) { + ESP_LOGE(TAG, "Proto encode bounds check failed in %s: need %zu bytes, %td available", caller, bytes, end - pos); + abort(); +} void ProtoWriteBuffer::debug_check_bounds_(size_t bytes, const char *caller) { if (this->pos_ + bytes > this->buffer_->data() + this->buffer_->size()) { ESP_LOGE(TAG, "ProtoWriteBuffer bounds check failed in %s: bytes=%zu offset=%td buf_size=%zu", caller, bytes, @@ -201,6 +207,7 @@ void ProtoWriteBuffer::debug_check_encode_size_(uint32_t field_id, uint32_t expe expected, actual); abort(); } + #endif void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) { @@ -257,7 +264,13 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) { ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer)); return; } - uint32_t val = encode_uint32(ptr[3], ptr[2], ptr[1], ptr[0]); + uint32_t val; +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + // Protobuf fixed32 is little-endian — direct load on LE platforms + memcpy(&val, ptr, 4); +#else + val = encode_uint32(ptr[3], ptr[2], ptr[1], ptr[0]); +#endif if (!this->decode_32bit(field_id, Proto32Bit(val))) { ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val); } diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index 44d8f04585..e0a4e03189 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -5,6 +5,7 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include "esphome/core/progmem.h" #include "esphome/core/string_ref.h" #include @@ -24,6 +25,19 @@ constexpr uint8_t WIRE_TYPE_LENGTH_DELIMITED = 2; // string, bytes, embedded me constexpr uint8_t WIRE_TYPE_FIXED32 = 5; // fixed32, sfixed32, float constexpr uint8_t WIRE_TYPE_MASK = 0b111; // Mask to extract wire type from tag +// Reinterpret float bits as uint32_t without floating-point comparison. +// Used by both encode_float() and calc_float() to ensure identical zero checks. +// Uses union type-punning which is a GCC/Clang extension (not standard C++), +// but bit_cast/memcpy don't optimize to a no-op on xtensa-gcc (ESP8266). +inline uint32_t float_to_raw(float value) { + union { + float f; + uint32_t u; + } v; + v.f = value; + return v.u; +} + // Helper functions for ZigZag encoding/decoding inline constexpr uint32_t encode_zigzag32(int32_t value) { return (static_cast(value) << 1) ^ (static_cast(value >> 31)); @@ -152,8 +166,7 @@ class ProtoVarInt { #endif }; -// Forward declarations for decode_to_message and related encoding helpers -class ProtoDecodableMessage; +// Forward declarations for encoding helpers class ProtoMessage; class ProtoSize; @@ -166,16 +179,9 @@ class ProtoLengthDelimited { const uint8_t *data() const { return this->value_; } size_t size() const { return this->length_; } - /** - * Decode the length-delimited data into an existing ProtoDecodableMessage instance. - * - * This method allows decoding without templates, enabling use in contexts - * where the message type is not known at compile time. The ProtoDecodableMessage's - * decode() method will be called with the raw data and length. - * - * @param msg The ProtoDecodableMessage instance to decode into - */ - void decode_to_message(ProtoDecodableMessage &msg) const; + /// Decode the length-delimited data into a message instance. + /// Template preserves concrete type so decode() resolves statically. + template void decode_to_message(T &msg) const; protected: const uint8_t *const value_; @@ -202,6 +208,26 @@ class Proto32Bit { // NOTE: Proto64Bit class removed - wire type 1 (64-bit fixed) not supported +// Debug bounds checking for proto encode functions. +// In debug mode (ESPHOME_DEBUG_API), an extra end-of-buffer pointer is threaded +// through the entire encode chain. In production, these expand to nothing. +#ifdef ESPHOME_DEBUG_API +#define PROTO_ENCODE_DEBUG_PARAM , uint8_t *proto_debug_end_ +#define PROTO_ENCODE_DEBUG_ARG , proto_debug_end_ +#define PROTO_ENCODE_DEBUG_INIT(buf) , (buf)->data() + (buf)->size() +#define PROTO_ENCODE_CHECK_BOUNDS(pos, n) \ + do { \ + if ((pos) + (n) > proto_debug_end_) \ + proto_check_bounds_failed(pos, n, proto_debug_end_, __builtin_FUNCTION()); \ + } while (0) +void proto_check_bounds_failed(const uint8_t *pos, size_t bytes, const uint8_t *end, const char *caller); +#else +#define PROTO_ENCODE_DEBUG_PARAM +#define PROTO_ENCODE_DEBUG_ARG +#define PROTO_ENCODE_DEBUG_INIT(buf) +#define PROTO_ENCODE_CHECK_BOUNDS(pos, n) +#endif + class ProtoWriteBuffer { public: ProtoWriteBuffer(APIBuffer *buffer) : buffer_(buffer), pos_(buffer->data() + buffer->size()) {} @@ -214,15 +240,6 @@ class ProtoWriteBuffer { } this->encode_varint_raw_slow_(value); } - void encode_varint_raw_64(uint64_t value) { - while (value > 0x7F) { - this->debug_check_bounds_(1); - *this->pos_++ = static_cast(value | 0x80); - value >>= 7; - } - this->debug_check_bounds_(1); - *this->pos_++ = static_cast(value); - } /** * Encode a field key (tag/wire type combination). * @@ -236,98 +253,6 @@ class ProtoWriteBuffer { * Following https://protobuf.dev/programming-guides/encoding/#structure */ void encode_field_raw(uint32_t field_id, uint32_t type) { this->encode_varint_raw((field_id << 3) | type); } - void encode_string(uint32_t field_id, const char *string, size_t len, bool force = false) { - if (len == 0 && !force) - return; - - this->encode_field_raw(field_id, 2); // type 2: Length-delimited string - this->encode_varint_raw(len); - // Direct memcpy into pre-sized buffer — avoids push_back() per-byte capacity checks - // and vector::insert() iterator overhead. ~10-11x faster for 16-32 byte strings. - this->debug_check_bounds_(len); - std::memcpy(this->pos_, string, len); - this->pos_ += len; - } - void encode_string(uint32_t field_id, const std::string &value, bool force = false) { - this->encode_string(field_id, value.data(), value.size(), force); - } - void encode_string(uint32_t field_id, const StringRef &ref, bool force = false) { - this->encode_string(field_id, ref.c_str(), ref.size(), force); - } - void encode_bytes(uint32_t field_id, const uint8_t *data, size_t len, bool force = false) { - this->encode_string(field_id, reinterpret_cast(data), len, force); - } - void encode_uint32(uint32_t field_id, uint32_t value, bool force = false) { - if (value == 0 && !force) - return; - this->encode_field_raw(field_id, 0); // type 0: Varint - uint32 - this->encode_varint_raw(value); - } - void encode_uint64(uint32_t field_id, uint64_t value, bool force = false) { - if (value == 0 && !force) - return; - this->encode_field_raw(field_id, 0); // type 0: Varint - uint64 - this->encode_varint_raw_64(value); - } - void encode_bool(uint32_t field_id, bool value, bool force = false) { - if (!value && !force) - return; - this->encode_field_raw(field_id, 0); // type 0: Varint - bool - this->debug_check_bounds_(1); - *this->pos_++ = value ? 0x01 : 0x00; - } - // noinline: 51 call sites; inlining causes net code growth vs a single out-of-line copy - __attribute__((noinline)) void encode_fixed32(uint32_t field_id, uint32_t value, bool force = false) { - if (value == 0 && !force) - return; - - this->encode_field_raw(field_id, 5); // type 5: 32-bit fixed32 - this->debug_check_bounds_(4); -#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - // Protobuf fixed32 is little-endian, so direct copy works - std::memcpy(this->pos_, &value, 4); - this->pos_ += 4; -#else - *this->pos_++ = (value >> 0) & 0xFF; - *this->pos_++ = (value >> 8) & 0xFF; - *this->pos_++ = (value >> 16) & 0xFF; - *this->pos_++ = (value >> 24) & 0xFF; -#endif - } - // NOTE: Wire type 1 (64-bit fixed: double, fixed64, sfixed64) is intentionally - // not supported to reduce overhead on embedded systems. All ESPHome devices are - // 32-bit microcontrollers where 64-bit operations are expensive. If 64-bit support - // is needed in the future, the necessary encoding/decoding functions must be added. - void encode_float(uint32_t field_id, float value, bool force = false) { - if (value == 0.0f && !force) - return; - - union { - float value; - uint32_t raw; - } val{}; - val.value = value; - this->encode_fixed32(field_id, val.raw); - } - void encode_int32(uint32_t field_id, int32_t value, bool force = false) { - if (value < 0) { - // negative int32 is always 10 byte long - this->encode_int64(field_id, value, force); - return; - } - this->encode_uint32(field_id, static_cast(value), force); - } - void encode_int64(uint32_t field_id, int64_t value, bool force = false) { - this->encode_uint64(field_id, static_cast(value), force); - } - void encode_sint32(uint32_t field_id, int32_t value, bool force = false) { - this->encode_uint32(field_id, encode_zigzag32(value), force); - } - void encode_sint64(uint32_t field_id, int64_t value, bool force = false) { - this->encode_uint64(field_id, encode_zigzag64(value), force); - } - /// Encode a packed repeated sint32 field (zero-copy from vector) - void encode_packed_sint32(uint32_t field_id, const std::vector &values); /// Single-pass encode for repeated submessage elements. /// Thin template wrapper; all buffer work is in the non-template core. template void encode_sub_message(uint32_t field_id, const T &value); @@ -335,12 +260,17 @@ class ProtoWriteBuffer { /// Thin template wrapper; all buffer work is in the non-template core. template void encode_optional_sub_message(uint32_t field_id, const T &value); + // NOLINTBEGIN(readability-identifier-naming) // Non-template core for encode_sub_message — backpatch approach. - void encode_sub_message(uint32_t field_id, const void *value, void (*encode_fn)(const void *, ProtoWriteBuffer &)); + void encode_sub_message(uint32_t field_id, const void *value, + uint8_t *(*encode_fn)(const void *, ProtoWriteBuffer &PROTO_ENCODE_DEBUG_PARAM)); // Non-template core for encode_optional_sub_message. void encode_optional_sub_message(uint32_t field_id, uint32_t nested_size, const void *value, - void (*encode_fn)(const void *, ProtoWriteBuffer &)); + uint8_t *(*encode_fn)(const void *, ProtoWriteBuffer &PROTO_ENCODE_DEBUG_PARAM)); + // NOLINTEND(readability-identifier-naming) APIBuffer *get_buffer() const { return buffer_; } + uint8_t *get_pos() const { return pos_; } + void set_pos(uint8_t *pos) { pos_ = pos; } protected: // Slow path for encode_varint_raw values >= 128, outlined to keep fast path small @@ -357,6 +287,220 @@ class ProtoWriteBuffer { uint8_t *pos_; }; +// Varint encoding thresholds — used by both proto_encode_* free functions and ProtoSize. +constexpr uint32_t VARINT_MAX_1_BYTE = 1 << 7; // 128 +constexpr uint32_t VARINT_MAX_2_BYTE = 1 << 14; // 16384 + +/// Static encode helpers for generated encode() functions. +/// Generated code hoists buffer.pos_ into a local uint8_t *__restrict__ pos, +/// then calls these methods which take pos by reference. No struct, no overhead. +/// For sub-messages, pos is synced back to buffer before the call and reloaded after. +class ProtoEncode { + public: + /// Write a multi-byte varint directly through a pos pointer. + template + static inline void encode_varint_raw_loop(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, T value) { + do { + PROTO_ENCODE_CHECK_BOUNDS(pos, 1); + *pos++ = static_cast(value | 0x80); + value >>= 7; + } while (value > 0x7F); + PROTO_ENCODE_CHECK_BOUNDS(pos, 1); + *pos++ = static_cast(value); + } + static inline void ESPHOME_ALWAYS_INLINE encode_varint_raw(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, + uint32_t value) { + if (value < VARINT_MAX_1_BYTE) [[likely]] { + PROTO_ENCODE_CHECK_BOUNDS(pos, 1); + *pos++ = static_cast(value); + return; + } + encode_varint_raw_loop(pos PROTO_ENCODE_DEBUG_ARG, value); + } + /// Encode a varint that is expected to be 1-2 bytes (e.g. zigzag RSSI, small lengths). + static inline void ESPHOME_ALWAYS_INLINE encode_varint_raw_short(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, + uint32_t value) { + if (value < VARINT_MAX_1_BYTE) [[likely]] { + PROTO_ENCODE_CHECK_BOUNDS(pos, 1); + *pos++ = static_cast(value); + return; + } + if (value < VARINT_MAX_2_BYTE) [[likely]] { + PROTO_ENCODE_CHECK_BOUNDS(pos, 2); + *pos++ = static_cast(value | 0x80); + *pos++ = static_cast(value >> 7); + return; + } + encode_varint_raw_loop(pos PROTO_ENCODE_DEBUG_ARG, value); + } + static inline void ESPHOME_ALWAYS_INLINE encode_varint_raw_64(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, + uint64_t value) { + if (value < VARINT_MAX_1_BYTE) [[likely]] { + PROTO_ENCODE_CHECK_BOUNDS(pos, 1); + *pos++ = static_cast(value); + return; + } + encode_varint_raw_loop(pos PROTO_ENCODE_DEBUG_ARG, value); + } + static inline void ESPHOME_ALWAYS_INLINE encode_field_raw(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, + uint32_t field_id, uint32_t type) { + encode_varint_raw(pos PROTO_ENCODE_DEBUG_ARG, (field_id << 3) | type); + } + /// Write a single precomputed tag byte. Tag must be < 128. + static inline void ESPHOME_ALWAYS_INLINE write_raw_byte(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, + uint8_t b) { + PROTO_ENCODE_CHECK_BOUNDS(pos, 1); + *pos++ = b; + } + /// Write raw bytes to the buffer (no tag, no length prefix). + static inline void ESPHOME_ALWAYS_INLINE encode_raw(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, + const void *data, size_t len) { + PROTO_ENCODE_CHECK_BOUNDS(pos, len); + std::memcpy(pos, data, len); + pos += len; + } + /// Encode tag + 1-byte length + raw string data. For strings with max_data_length < 128. + /// Tag must be a single-byte varint (< 128). Always encodes (no zero check). + static inline void encode_short_string_force(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint8_t tag, + const StringRef &ref) { +#ifdef ESPHOME_DEBUG_API + assert(ref.size() < 128 && "encode_short_string_force: string exceeds max_data_length < 128"); +#endif + PROTO_ENCODE_CHECK_BOUNDS(pos, 2 + ref.size()); + pos[0] = tag; + pos[1] = static_cast(ref.size()); + std::memcpy(pos + 2, ref.c_str(), ref.size()); + pos += 2 + ref.size(); + } + /// Write a precomputed tag byte + 32-bit value in one operation. + static inline void ESPHOME_ALWAYS_INLINE write_tag_and_fixed32(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, + uint8_t tag, uint32_t value) { + PROTO_ENCODE_CHECK_BOUNDS(pos, 5); + pos[0] = tag; +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + std::memcpy(pos + 1, &value, 4); +#else + pos[1] = static_cast(value & 0xFF); + pos[2] = static_cast((value >> 8) & 0xFF); + pos[3] = static_cast((value >> 16) & 0xFF); + pos[4] = static_cast((value >> 24) & 0xFF); +#endif + pos += 5; + } + static inline void encode_string(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, + const char *string, size_t len, bool force = false) { + if (len == 0 && !force) + return; + encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 2); // type 2: Length-delimited string + if (len < VARINT_MAX_1_BYTE) [[likely]] { + PROTO_ENCODE_CHECK_BOUNDS(pos, 1 + len); + *pos++ = static_cast(len); + } else { + encode_varint_raw_loop(pos PROTO_ENCODE_DEBUG_ARG, len); + PROTO_ENCODE_CHECK_BOUNDS(pos, len); + } + std::memcpy(pos, string, len); + pos += len; + } + static inline void encode_string(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, + const std::string &value, bool force = false) { + encode_string(pos PROTO_ENCODE_DEBUG_ARG, field_id, value.data(), value.size(), force); + } + static inline void encode_string(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, + const StringRef &ref, bool force = false) { + encode_string(pos PROTO_ENCODE_DEBUG_ARG, field_id, ref.c_str(), ref.size(), force); + } + static inline void encode_bytes(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, + const uint8_t *data, size_t len, bool force = false) { + encode_string(pos PROTO_ENCODE_DEBUG_ARG, field_id, reinterpret_cast(data), len, force); + } + static inline void encode_uint32(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, + uint32_t value, bool force = false) { + if (value == 0 && !force) + return; + encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 0); + encode_varint_raw(pos PROTO_ENCODE_DEBUG_ARG, value); + } + static inline void encode_uint64(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, + uint64_t value, bool force = false) { + if (value == 0 && !force) + return; + encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 0); + encode_varint_raw_64(pos PROTO_ENCODE_DEBUG_ARG, value); + } + static inline void encode_bool(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, bool value, + bool force = false) { + if (!value && !force) + return; + encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 0); + PROTO_ENCODE_CHECK_BOUNDS(pos, 1); + *pos++ = value ? 0x01 : 0x00; + } + static inline void encode_fixed32(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, + uint32_t value, bool force = false) { + if (value == 0 && !force) + return; + encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 5); + PROTO_ENCODE_CHECK_BOUNDS(pos, 4); +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + std::memcpy(pos, &value, 4); + pos += 4; +#else + *pos++ = (value >> 0) & 0xFF; + *pos++ = (value >> 8) & 0xFF; + *pos++ = (value >> 16) & 0xFF; + *pos++ = (value >> 24) & 0xFF; +#endif + } + // NOTE: Wire type 1 (64-bit fixed: double, fixed64, sfixed64) is intentionally + // not supported to reduce overhead on embedded systems. All ESPHome devices are + // 32-bit microcontrollers where 64-bit operations are expensive. If 64-bit support + // is needed in the future, the necessary encoding/decoding functions must be added. + static inline void encode_float(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, float value, + bool force = false) { + uint32_t raw = float_to_raw(value); + if (raw == 0 && !force) + return; + encode_fixed32(pos PROTO_ENCODE_DEBUG_ARG, field_id, raw); + } + static inline void encode_int32(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, int32_t value, + bool force = false) { + if (value < 0) { + // negative int32 is always 10 byte long + encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, field_id, static_cast(value), force); + return; + } + encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, field_id, static_cast(value), force); + } + static inline void encode_int64(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, int64_t value, + bool force = false) { + encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, field_id, static_cast(value), force); + } + static inline void encode_sint32(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, + int32_t value, bool force = false) { + encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, field_id, encode_zigzag32(value), force); + } + static inline void encode_sint64(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, + int64_t value, bool force = false) { + encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, field_id, encode_zigzag64(value), force); + } + /// Sub-message encoding: sync pos to buffer, delegate, get pos from return value. + template + static inline void encode_sub_message(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, ProtoWriteBuffer &buffer, + uint32_t field_id, const T &value) { + buffer.set_pos(pos); + buffer.encode_sub_message(field_id, value); + pos = buffer.get_pos(); + } + template + static inline void encode_optional_sub_message(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM, + ProtoWriteBuffer &buffer, uint32_t field_id, const T &value) { + buffer.set_pos(pos); + buffer.encode_optional_sub_message(field_id, value); + pos = buffer.get_pos(); + } +}; + #ifdef HAS_PROTO_MESSAGE_DUMP /** * Fixed-size buffer for message dumps - avoids heap allocation. @@ -394,6 +538,23 @@ class DumpBuffer { return *this; } + /// Append a PROGMEM string (flash-safe on ESP8266, regular append on other platforms) + DumpBuffer &append_p(const char *str) { + if (str) { +#ifdef USE_ESP8266 + append_p_esp8266(str); +#else + append_impl_(str, strlen(str)); +#endif + } + return *this; + } + +#ifdef USE_ESP8266 + /// Out-of-line ESP8266 PROGMEM append to avoid inlining strlen_P/memcpy_P at every call site + void append_p_esp8266(const char *str); +#endif + const char *c_str() const { return buf_; } size_t size() const { return pos_; } @@ -435,11 +596,11 @@ class ProtoMessage { // All call sites use templates to preserve the concrete type, so virtual // dispatch is not needed. This eliminates per-message vtable entries for // encode/calculate_size, saving ~1.3 KB of flash across all message types. - void encode(ProtoWriteBuffer &buffer) const {} + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { return buffer.get_pos(); } uint32_t calculate_size() const { return 0; } #ifdef HAS_PROTO_MESSAGE_DUMP virtual const char *dump_to(DumpBuffer &out) const = 0; - virtual const char *message_name() const { return "unknown"; } + virtual const LogString *message_name() const { return LOG_STR("unknown"); } #endif #ifndef USE_HOST @@ -454,7 +615,7 @@ class ProtoMessage { // Base class for messages that support decoding class ProtoDecodableMessage : public ProtoMessage { public: - virtual void decode(const uint8_t *buffer, size_t length); + void decode(const uint8_t *buffer, size_t length); /** * Count occurrences of a repeated field in a protobuf buffer. @@ -477,6 +638,24 @@ class ProtoDecodableMessage : public ProtoMessage { class ProtoSize { public: + // Varint encoding thresholds — use namespace-level constants for 1/2 byte, + // class-level for 3/4 byte (only used within ProtoSize). + static constexpr uint32_t VARINT_THRESHOLD_1_BYTE = VARINT_MAX_1_BYTE; + static constexpr uint32_t VARINT_THRESHOLD_2_BYTE = VARINT_MAX_2_BYTE; + static constexpr uint32_t VARINT_THRESHOLD_3_BYTE = 1 << 21; // 2097152 + static constexpr uint32_t VARINT_THRESHOLD_4_BYTE = 1 << 28; // 268435456 + + // Varint encoded length for a 16-bit value (1, 2, or 3 bytes). + // Fully inline — no slow path call for values >= 128. + static constexpr inline uint8_t ESPHOME_ALWAYS_INLINE varint16(uint16_t value) { + return value < VARINT_THRESHOLD_1_BYTE ? 1 : (value < VARINT_THRESHOLD_2_BYTE ? 2 : 3); + } + + // Varint encoded length for an 8-bit value (1 or 2 bytes). + static constexpr inline uint8_t ESPHOME_ALWAYS_INLINE varint8(uint8_t value) { + return value < VARINT_THRESHOLD_1_BYTE ? 1 : 2; + } + /** * @brief Calculates the size in bytes needed to encode a uint32_t value as a varint * @@ -484,23 +663,34 @@ class ProtoSize { * @return The number of bytes needed to encode the value */ static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE varint(uint32_t value) { - if (value < 128) [[likely]] + if (value < VARINT_THRESHOLD_1_BYTE) [[likely]] return 1; // Fast path: 7 bits, most common case if (__builtin_is_constant_evaluated()) return varint_wide(value); return varint_slow(value); } + /// Size of a varint expected to be 1-2 bytes (e.g. zigzag RSSI, small lengths). + /// Inlines both checks; falls back to slow path for 3+ bytes. + static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE varint_short(uint32_t value) { + if (value < VARINT_THRESHOLD_1_BYTE) [[likely]] + return 1; + if (value < VARINT_THRESHOLD_2_BYTE) [[likely]] + return 2; + if (__builtin_is_constant_evaluated()) + return varint_wide(value); + return varint_slow(value); + } private: // Slow path for varint >= 128, outlined to keep fast path small static uint32_t varint_slow(uint32_t value) __attribute__((noinline)); // Shared cascade for values >= 128 (used by both constexpr and noinline paths) static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE varint_wide(uint32_t value) { - if (value < 16384) + if (value < VARINT_THRESHOLD_2_BYTE) return 2; - if (value < 2097152) + if (value < VARINT_THRESHOLD_3_BYTE) return 3; - if (value < 268435456) + if (value < VARINT_THRESHOLD_4_BYTE) return 4; return 5; } @@ -594,8 +784,8 @@ class ProtoSize { } static constexpr uint32_t calc_bool(uint32_t field_id_size, bool value) { return value ? field_id_size + 1 : 0; } static constexpr uint32_t calc_bool_force(uint32_t field_id_size) { return field_id_size + 1; } - static constexpr uint32_t calc_float(uint32_t field_id_size, float value) { - return value != 0.0f ? field_id_size + 4 : 0; + static uint32_t calc_float(uint32_t field_id_size, float value) { + return float_to_raw(value) != 0 ? field_id_size + 4 : 0; } static constexpr uint32_t calc_fixed32(uint32_t field_id_size, uint32_t value) { return value ? field_id_size + 4 : 0; @@ -604,10 +794,10 @@ class ProtoSize { return value ? field_id_size + 4 : 0; } static constexpr uint32_t calc_sint32(uint32_t field_id_size, int32_t value) { - return value ? field_id_size + varint(encode_zigzag32(value)) : 0; + return value ? field_id_size + varint_short(encode_zigzag32(value)) : 0; } - static constexpr uint32_t calc_sint32_force(uint32_t field_id_size, int32_t value) { - return field_id_size + varint(encode_zigzag32(value)); + static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE calc_sint32_force(uint32_t field_id_size, int32_t value) { + return field_id_size + varint_short(encode_zigzag32(value)); } static constexpr uint32_t calc_int64(uint32_t field_id_size, int64_t value) { return value ? field_id_size + varint(value) : 0; @@ -618,13 +808,13 @@ class ProtoSize { static constexpr uint32_t calc_uint64(uint32_t field_id_size, uint64_t value) { return value ? field_id_size + varint(value) : 0; } - static constexpr uint32_t calc_uint64_force(uint32_t field_id_size, uint64_t value) { + static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE calc_uint64_force(uint32_t field_id_size, uint64_t value) { return field_id_size + varint(value); } static constexpr uint32_t calc_length(uint32_t field_id_size, size_t len) { return len ? field_id_size + varint(static_cast(len)) + static_cast(len) : 0; } - static constexpr uint32_t calc_length_force(uint32_t field_id_size, size_t len) { + static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE calc_length_force(uint32_t field_id_size, size_t len) { return field_id_size + varint(static_cast(len)) + static_cast(len); } static constexpr uint32_t calc_sint64(uint32_t field_id_size, int64_t value) { @@ -642,35 +832,17 @@ class ProtoSize { static constexpr uint32_t calc_message(uint32_t field_id_size, uint32_t nested_size) { return nested_size ? field_id_size + varint(nested_size) + nested_size : 0; } - static constexpr uint32_t calc_message_force(uint32_t field_id_size, uint32_t nested_size) { + static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE calc_message_force(uint32_t field_id_size, + uint32_t nested_size) { return field_id_size + varint(nested_size) + nested_size; } }; // Implementation of methods that depend on ProtoSize being fully defined -// Implementation of encode_packed_sint32 - must be after ProtoSize is defined -inline void ProtoWriteBuffer::encode_packed_sint32(uint32_t field_id, const std::vector &values) { - if (values.empty()) - return; - - // Calculate packed size - size_t packed_size = 0; - for (int value : values) { - packed_size += ProtoSize::varint(encode_zigzag32(value)); - } - - // Write tag (LENGTH_DELIMITED) + length + all zigzag-encoded values - this->encode_field_raw(field_id, WIRE_TYPE_LENGTH_DELIMITED); - this->encode_varint_raw(packed_size); - for (int value : values) { - this->encode_varint_raw(encode_zigzag32(value)); - } -} - // Encode thunk — converts void* back to concrete type for direct encode() call -template void proto_encode_msg(const void *msg, ProtoWriteBuffer &buf) { - static_cast(msg)->encode(buf); +template uint8_t *proto_encode_msg(const void *msg, ProtoWriteBuffer &buf PROTO_ENCODE_DEBUG_PARAM) { + return static_cast(msg)->encode(buf PROTO_ENCODE_DEBUG_ARG); } // Thin template wrapper; delegates to non-template core in proto.cpp. @@ -683,33 +855,14 @@ template inline void ProtoWriteBuffer::encode_optional_sub_message(u this->encode_optional_sub_message(field_id, value.calculate_size(), &value, &proto_encode_msg); } -// Implementation of decode_to_message - must be after ProtoDecodableMessage is defined -inline void ProtoLengthDelimited::decode_to_message(ProtoDecodableMessage &msg) const { +// Template decode_to_message - preserves concrete type so decode() resolves statically +template void ProtoLengthDelimited::decode_to_message(T &msg) const { msg.decode(this->value_, this->length_); } template const char *proto_enum_to_string(T value); -class ProtoService { - public: - protected: - virtual bool is_authenticated() = 0; - virtual bool is_connection_setup() = 0; - virtual void on_fatal_error() = 0; - virtual void on_no_setup_connection() = 0; - virtual bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) = 0; - virtual void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) = 0; - - // Authentication helper methods - inline bool check_connection_setup_() { - if (!this->is_connection_setup()) { - this->on_no_setup_connection(); - return false; - } - return true; - } - - inline bool check_authenticated_() { return this->check_connection_setup_(); } -}; +// ProtoService removed — its methods were inlined into APIConnection. +// APIConnection is the concrete server-side implementation; the extra virtual layer was unnecessary. } // namespace esphome::api diff --git a/esphome/components/api/user_services.h b/esphome/components/api/user_services.h index d1b8a6ef0d..29eadda927 100644 --- a/esphome/components/api/user_services.h +++ b/esphome/components/api/user_services.h @@ -275,7 +275,7 @@ template class APIRespondAction : public Action { protected: APIServer *parent_; - TemplatableValue success_{true}; + TemplatableFn success_{[](Ts...) -> bool { return true; }}; TemplatableValue error_message_{""}; #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON std::function json_builder_; diff --git a/esphome/components/as3935/sensor.py b/esphome/components/as3935/sensor.py index 79bc7af4a9..9b43155563 100644 --- a/esphome/components/as3935/sensor.py +++ b/esphome/components/as3935/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_LIGHTNING_ENERGY, ICON_FLASH, ICON_SIGNAL_DISTANCE_VARIANT, + STATE_CLASS_MEASUREMENT, UNIT_KILOMETER, ) @@ -20,13 +21,14 @@ CONFIG_SCHEMA = cv.Schema( unit_of_measurement=UNIT_KILOMETER, icon=ICON_SIGNAL_DISTANCE_VARIANT, accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_LIGHTNING_ENERGY): sensor.sensor_schema( icon=ICON_FLASH, accuracy_decimals=1, ), } -).extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): diff --git a/esphome/components/as5600/sensor/__init__.py b/esphome/components/as5600/sensor/__init__.py index e84733a484..cf67a3f203 100644 --- a/esphome/components/as5600/sensor/__init__.py +++ b/esphome/components/as5600/sensor/__init__.py @@ -2,11 +2,9 @@ import esphome.codegen as cg from esphome.components import sensor import esphome.config_validation as cv from esphome.const import ( - CONF_ANGLE, CONF_GAIN, CONF_ID, CONF_MAGNITUDE, - CONF_POSITION, CONF_STATUS, ENTITY_CATEGORY_DIAGNOSTIC, ICON_MAGNET, @@ -21,7 +19,6 @@ DEPENDENCIES = ["as5600"] AS5600Sensor = as5600_ns.class_("AS5600Sensor", sensor.Sensor, cg.PollingComponent) -CONF_RAW_ANGLE = "raw_angle" CONF_RAW_POSITION = "raw_position" CONF_SLOW_FILTER = "slow_filter" CONF_FAST_FILTER = "fast_filter" @@ -89,18 +86,6 @@ async def to_code(config): if out_of_range_mode_config := config.get(CONF_OUT_OF_RANGE_MODE): cg.add(var.set_out_of_range_mode(out_of_range_mode_config)) - if angle_config := config.get(CONF_ANGLE): - sens = await sensor.new_sensor(angle_config) - cg.add(var.set_angle_sensor(sens)) - - if raw_angle_config := config.get(CONF_RAW_ANGLE): - sens = await sensor.new_sensor(raw_angle_config) - cg.add(var.set_raw_angle_sensor(sens)) - - if position_config := config.get(CONF_POSITION): - sens = await sensor.new_sensor(position_config) - cg.add(var.set_position_sensor(sens)) - if raw_position_config := config.get(CONF_RAW_POSITION): sens = await sensor.new_sensor(raw_position_config) cg.add(var.set_raw_position_sensor(sens)) diff --git a/esphome/components/as5600/sensor/as5600_sensor.cpp b/esphome/components/as5600/sensor/as5600_sensor.cpp index 1c0f4bad2c..4e549d24d5 100644 --- a/esphome/components/as5600/sensor/as5600_sensor.cpp +++ b/esphome/components/as5600/sensor/as5600_sensor.cpp @@ -25,27 +25,10 @@ static const uint8_t REGISTER_MAGNITUDE = 0x1B; // 16 bytes / R void AS5600Sensor::dump_config() { LOG_SENSOR("", "AS5600 Sensor", this); ESP_LOGCONFIG(TAG, " Out of Range Mode: %u", this->out_of_range_mode_); - if (this->angle_sensor_ != nullptr) { - LOG_SENSOR(" ", "Angle Sensor", this->angle_sensor_); - } - if (this->raw_angle_sensor_ != nullptr) { - LOG_SENSOR(" ", "Raw Angle Sensor", this->raw_angle_sensor_); - } - if (this->position_sensor_ != nullptr) { - LOG_SENSOR(" ", "Position Sensor", this->position_sensor_); - } - if (this->raw_position_sensor_ != nullptr) { - LOG_SENSOR(" ", "Raw Position Sensor", this->raw_position_sensor_); - } - if (this->gain_sensor_ != nullptr) { - LOG_SENSOR(" ", "Gain Sensor", this->gain_sensor_); - } - if (this->magnitude_sensor_ != nullptr) { - LOG_SENSOR(" ", "Magnitude Sensor", this->magnitude_sensor_); - } - if (this->status_sensor_ != nullptr) { - LOG_SENSOR(" ", "Status Sensor", this->status_sensor_); - } + LOG_SENSOR(" ", "Raw Position Sensor", this->raw_position_sensor_); + LOG_SENSOR(" ", "Gain Sensor", this->gain_sensor_); + LOG_SENSOR(" ", "Magnitude Sensor", this->magnitude_sensor_); + LOG_SENSOR(" ", "Status Sensor", this->status_sensor_); LOG_UPDATE_INTERVAL(this); } diff --git a/esphome/components/as5600/sensor/as5600_sensor.h b/esphome/components/as5600/sensor/as5600_sensor.h index d471be49b5..77593f4b12 100644 --- a/esphome/components/as5600/sensor/as5600_sensor.h +++ b/esphome/components/as5600/sensor/as5600_sensor.h @@ -15,9 +15,6 @@ class AS5600Sensor : public PollingComponent, public Parented, void update() override; void dump_config() override; - void set_angle_sensor(sensor::Sensor *angle_sensor) { this->angle_sensor_ = angle_sensor; } - void set_raw_angle_sensor(sensor::Sensor *raw_angle_sensor) { this->raw_angle_sensor_ = raw_angle_sensor; } - void set_position_sensor(sensor::Sensor *position_sensor) { this->position_sensor_ = position_sensor; } void set_raw_position_sensor(sensor::Sensor *raw_position_sensor) { this->raw_position_sensor_ = raw_position_sensor; } @@ -28,9 +25,6 @@ class AS5600Sensor : public PollingComponent, public Parented, OutRangeMode get_out_of_range_mode() { return this->out_of_range_mode_; } protected: - sensor::Sensor *angle_sensor_{nullptr}; - sensor::Sensor *raw_angle_sensor_{nullptr}; - sensor::Sensor *position_sensor_{nullptr}; sensor::Sensor *raw_position_sensor_{nullptr}; sensor::Sensor *gain_sensor_{nullptr}; sensor::Sensor *magnitude_sensor_{nullptr}; diff --git a/esphome/components/async_tcp/async_tcp_socket.cpp b/esphome/components/async_tcp/async_tcp_socket.cpp index f64e494f5f..e8c0f163b3 100644 --- a/esphome/components/async_tcp/async_tcp_socket.cpp +++ b/esphome/components/async_tcp/async_tcp_socket.cpp @@ -52,11 +52,12 @@ bool AsyncClient::connect(const char *host, uint16_t port) { connect_cb_(connect_arg_, this); return true; } - if (errno != EINPROGRESS) { - ESP_LOGE(TAG, "Connect failed: %d", errno); + const int saved_errno = errno; + if (saved_errno != EINPROGRESS) { + ESP_LOGE(TAG, "Connect failed: %d", saved_errno); close(); if (error_cb_) - error_cb_(error_arg_, this, errno); + error_cb_(error_arg_, this, saved_errno); return false; } @@ -79,11 +80,12 @@ size_t AsyncClient::write(const char *data, size_t len) { ssize_t sent = socket_->write(data, len); if (sent < 0) { - if (errno != EAGAIN && errno != EWOULDBLOCK) { - ESP_LOGE(TAG, "Write error: %d", errno); + const int err = errno; + if (err != EAGAIN && err != EWOULDBLOCK) { + ESP_LOGE(TAG, "Write error: %d", err); close(); if (error_cb_) - error_cb_(error_arg_, this, errno); + error_cb_(error_arg_, this, err); } return 0; } @@ -129,10 +131,11 @@ void AsyncClient::loop() { error_cb_(error_arg_, this, error); } } else if (ret < 0) { - ESP_LOGE(TAG, "Select error: %d", errno); + const int err = errno; + ESP_LOGE(TAG, "Select error: %d", err); close(); if (error_cb_) - error_cb_(error_arg_, this, errno); + error_cb_(error_arg_, this, err); } } else if (connected_) { // For connected sockets, use the Application's select() results @@ -148,11 +151,14 @@ void AsyncClient::loop() { } else if (len > 0) { if (data_cb_) data_cb_(data_arg_, this, buf, len); - } else if (errno != EAGAIN && errno != EWOULDBLOCK) { - ESP_LOGW(TAG, "Read error: %d", errno); - close(); - if (error_cb_) - error_cb_(error_arg_, this, errno); + } else { + const int err = errno; + if (err != EAGAIN && err != EWOULDBLOCK) { + ESP_LOGW(TAG, "Read error: %d", err); + close(); + if (error_cb_) + error_cb_(error_arg_, this, err); + } } } } diff --git a/esphome/components/at581x/__init__.py b/esphome/components/at581x/__init__.py index 0780814ea6..94b68db4b3 100644 --- a/esphome/components/at581x/__init__.py +++ b/esphome/components/at581x/__init__.py @@ -169,53 +169,43 @@ async def at581x_settings_to_code(config, action_id, template_arg, args): # Radar configuration if frontend_reset := config.get(CONF_HW_FRONTEND_RESET): - template_ = await cg.templatable(frontend_reset, args, int) + template_ = await cg.templatable(frontend_reset, args, cg.int8) cg.add(var.set_hw_frontend_reset(template_)) if freq := config.get(CONF_FREQUENCY): - template_ = await cg.templatable(freq, args, float) - template_ = int(template_ / 1000000) + if not cg.is_template(freq): + freq = int(freq / 1000000) + template_ = await cg.templatable(freq, args, cg.int_) cg.add(var.set_frequency(template_)) - if sens_dist := config.get(CONF_SENSING_DISTANCE): - template_ = await cg.templatable(sens_dist, args, int) + if (sens_dist := config.get(CONF_SENSING_DISTANCE)) is not None: + template_ = await cg.templatable(sens_dist, args, cg.int_) cg.add(var.set_sensing_distance(template_)) if selfcheck := config.get(CONF_POWERON_SELFCHECK_TIME): - template_ = await cg.templatable(selfcheck, args, float) - if isinstance(template_, cv.TimePeriod): - template_ = template_.total_milliseconds - template_ = int(template_) + template_ = await cg.templatable(selfcheck, args, cg.int32) cg.add(var.set_poweron_selfcheck_time(template_)) if protect := config.get(CONF_PROTECT_TIME): - template_ = await cg.templatable(protect, args, float) - if isinstance(template_, cv.TimePeriod): - template_ = template_.total_milliseconds - template_ = int(template_) + template_ = await cg.templatable(protect, args, cg.int32) cg.add(var.set_protect_time(template_)) if trig_base := config.get(CONF_TRIGGER_BASE): - template_ = await cg.templatable(trig_base, args, float) - if isinstance(template_, cv.TimePeriod): - template_ = template_.total_milliseconds - template_ = int(template_) + template_ = await cg.templatable(trig_base, args, cg.int32) cg.add(var.set_trigger_base(template_)) if trig_keep := config.get(CONF_TRIGGER_KEEP): - template_ = await cg.templatable(trig_keep, args, float) - if isinstance(template_, cv.TimePeriod): - template_ = template_.total_milliseconds - template_ = int(template_) + template_ = await cg.templatable(trig_keep, args, cg.int32) cg.add(var.set_trigger_keep(template_)) - if stage_gain := config.get(CONF_STAGE_GAIN): - template_ = await cg.templatable(stage_gain, args, int) + if (stage_gain := config.get(CONF_STAGE_GAIN)) is not None: + template_ = await cg.templatable(stage_gain, args, cg.int_) cg.add(var.set_stage_gain(template_)) if power := config.get(CONF_POWER_CONSUMPTION): - template_ = await cg.templatable(power, args, float) - template_ = int(template_ * 1000000) + if not cg.is_template(power): + power = int(power * 1000000) + template_ = await cg.templatable(power, args, cg.int_) cg.add(var.set_power_consumption(template_)) return var diff --git a/esphome/components/atm90e26/sensor.py b/esphome/components/atm90e26/sensor.py index 4522e94846..5941cb35b4 100644 --- a/esphome/components/atm90e26/sensor.py +++ b/esphome/components/atm90e26/sensor.py @@ -14,6 +14,7 @@ from esphome.const import ( CONF_VOLTAGE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_FREQUENCY, DEVICE_CLASS_POWER, DEVICE_CLASS_POWER_FACTOR, DEVICE_CLASS_REACTIVE_POWER, @@ -103,6 +104,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_HERTZ, icon=ICON_CURRENT_AC, accuracy_decimals=1, + device_class=DEVICE_CLASS_FREQUENCY, state_class=STATE_CLASS_MEASUREMENT, ), cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True), diff --git a/esphome/components/atm90e32/atm90e32.cpp b/esphome/components/atm90e32/atm90e32.cpp index ee7fe5ce75..db29702c54 100644 --- a/esphome/components/atm90e32/atm90e32.cpp +++ b/esphome/components/atm90e32/atm90e32.cpp @@ -550,8 +550,8 @@ float ATM90E32Component::get_phase_harmonic_active_power_(uint8_t phase) { } float ATM90E32Component::get_phase_angle_(uint8_t phase) { - uint16_t val = this->read16_(ATM90E32_REGISTER_PANGLE + phase) / 10.0; - return (val > 180) ? (float) (val - 360.0f) : (float) val; + float val = this->read16_(ATM90E32_REGISTER_PANGLE + phase) / 10.0f; + return (val > 180.0f) ? val - 360.0f : val; } float ATM90E32Component::get_phase_peak_current_(uint8_t phase) { diff --git a/esphome/components/atm90e32/atm90e32.h b/esphome/components/atm90e32/atm90e32.h index 2524616470..c44a11e3ed 100644 --- a/esphome/components/atm90e32/atm90e32.h +++ b/esphome/components/atm90e32/atm90e32.h @@ -134,7 +134,6 @@ class ATM90E32Component : public PollingComponent, void set_freq_status_text_sensor(text_sensor::TextSensor *sensor) { this->freq_status_text_sensor_ = sensor; } #endif uint16_t calculate_voltage_threshold(int line_freq, uint16_t ugain, float multiplier); - int32_t last_periodic_millis = millis(); protected: #ifdef USE_NUMBER diff --git a/esphome/components/atm90e32/sensor.py b/esphome/components/atm90e32/sensor.py index a510095217..7e5d85c57a 100644 --- a/esphome/components/atm90e32/sensor.py +++ b/esphome/components/atm90e32/sensor.py @@ -20,6 +20,7 @@ from esphome.const import ( DEVICE_CLASS_APPARENT_POWER, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_FREQUENCY, DEVICE_CLASS_POWER, DEVICE_CLASS_POWER_FACTOR, DEVICE_CLASS_REACTIVE_POWER, @@ -131,7 +132,6 @@ ATM90E32_PHASE_SCHEMA = cv.Schema( cv.Optional(CONF_PHASE_ANGLE): sensor.sensor_schema( unit_of_measurement=UNIT_DEGREES, accuracy_decimals=2, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_HARMONIC_POWER): sensor.sensor_schema( @@ -166,6 +166,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_HERTZ, icon=ICON_CURRENT_AC, accuracy_decimals=1, + device_class=DEVICE_CLASS_FREQUENCY, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CHIP_TEMPERATURE): sensor.sensor_schema( diff --git a/esphome/components/audio/__init__.py b/esphome/components/audio/__init__.py index b28c2ed3d8..8f2102de6a 100644 --- a/esphome/components/audio/__init__.py +++ b/esphome/components/audio/__init__.py @@ -204,14 +204,15 @@ async def to_code(config): add_idf_component( name="esphome/esp-audio-libs", - ref="2.0.3", + ref="2.0.4", ) data = _get_data() if data.flac_support: cg.add_define("USE_AUDIO_FLAC_SUPPORT") + add_idf_component(name="esphome/micro-flac", ref="0.1.1") if data.mp3_support: cg.add_define("USE_AUDIO_MP3_SUPPORT") if data.opus_support: cg.add_define("USE_AUDIO_OPUS_SUPPORT") - add_idf_component(name="esphome/micro-opus", ref="0.3.5") + add_idf_component(name="esphome/micro-opus", ref="0.3.6") diff --git a/esphome/components/audio/audio_decoder.cpp b/esphome/components/audio/audio_decoder.cpp index bc05bc0006..baa4c41c06 100644 --- a/esphome/components/audio/audio_decoder.cpp +++ b/esphome/components/audio/audio_decoder.cpp @@ -84,13 +84,10 @@ esp_err_t AudioDecoder::start(AudioFileType audio_file_type) { switch (this->audio_file_type_) { #ifdef USE_AUDIO_FLAC_SUPPORT case AudioFileType::FLAC: - this->flac_decoder_ = make_unique(); - // CRC check slows down decoding by 15-20% on an ESP32-S3. FLAC sources in ESPHome are either from an http source - // or built into the firmware, so the data integrity is already verified by the time it gets to the decoder, - // making the CRC check unnecessary. - this->flac_decoder_->set_crc_check_enabled(false); + this->flac_decoder_ = make_unique(); this->free_buffer_required_ = this->output_transfer_buffer_->capacity(); // Adjusted and reallocated after reading the header + this->decoder_buffers_internally_ = true; break; #endif #ifdef USE_AUDIO_MP3_SUPPORT @@ -268,59 +265,45 @@ AudioDecoderState AudioDecoder::decode(bool stop_gracefully) { #ifdef USE_AUDIO_FLAC_SUPPORT FileDecoderState AudioDecoder::decode_flac_() { - if (!this->audio_stream_info_.has_value()) { - // Header hasn't been read - auto result = this->flac_decoder_->read_header(this->input_buffer_->data(), this->input_buffer_->available()); + size_t bytes_consumed, samples_decoded; - if (result > esp_audio_libs::flac::FLAC_DECODER_HEADER_OUT_OF_DATA) { - // Serrious error reading FLAC header, there is no recovery - return FileDecoderState::FAILED; + micro_flac::FLACDecoderResult result = this->flac_decoder_->decode( + this->input_buffer_->data(), this->input_buffer_->available(), this->output_transfer_buffer_->get_buffer_end(), + this->output_transfer_buffer_->free(), bytes_consumed, samples_decoded); + + if (result == micro_flac::FLAC_DECODER_SUCCESS) { + if (samples_decoded > 0 && this->audio_stream_info_.has_value()) { + this->output_transfer_buffer_->increase_buffer_length( + this->audio_stream_info_.value().samples_to_bytes(samples_decoded)); } - - size_t bytes_consumed = this->flac_decoder_->get_bytes_index(); this->input_buffer_->consume(bytes_consumed); + } else if (result == micro_flac::FLAC_DECODER_HEADER_READY) { + // Header just parsed, stream info now available + const auto &info = this->flac_decoder_->get_stream_info(); + this->audio_stream_info_ = audio::AudioStreamInfo(info.bits_per_sample(), info.num_channels(), info.sample_rate()); - if (result == esp_audio_libs::flac::FLAC_DECODER_HEADER_OUT_OF_DATA) { - return FileDecoderState::MORE_TO_PROCESS; - } - - // Reallocate the output transfer buffer to the smallest necessary size - this->free_buffer_required_ = flac_decoder_->get_output_buffer_size_bytes(); + // Reallocate the output transfer buffer to the required size + this->free_buffer_required_ = this->flac_decoder_->get_output_buffer_size_samples() * info.bytes_per_sample(); if (!this->output_transfer_buffer_->reallocate(this->free_buffer_required_)) { - // Couldn't reallocate output buffer return FileDecoderState::FAILED; } - - this->audio_stream_info_ = - audio::AudioStreamInfo(this->flac_decoder_->get_sample_depth(), this->flac_decoder_->get_num_channels(), - this->flac_decoder_->get_sample_rate()); - - return FileDecoderState::MORE_TO_PROCESS; - } - - uint32_t output_samples = 0; - auto result = this->flac_decoder_->decode_frame(this->input_buffer_->data(), this->input_buffer_->available(), - this->output_transfer_buffer_->get_buffer_end(), &output_samples); - - if (result == esp_audio_libs::flac::FLAC_DECODER_ERROR_OUT_OF_DATA) { - // Not an issue, just needs more data that we'll get next time. - return FileDecoderState::POTENTIALLY_FAILED; - } - - size_t bytes_consumed = this->flac_decoder_->get_bytes_index(); - this->input_buffer_->consume(bytes_consumed); - - if (result > esp_audio_libs::flac::FLAC_DECODER_ERROR_OUT_OF_DATA) { - // Corrupted frame, don't retry with current buffer content, wait for new sync - return FileDecoderState::POTENTIALLY_FAILED; - } - - // We have successfully decoded some input data and have new output data - this->output_transfer_buffer_->increase_buffer_length( - this->audio_stream_info_.value().samples_to_bytes(output_samples)); - - if (result == esp_audio_libs::flac::FLAC_DECODER_NO_MORE_FRAMES) { + this->input_buffer_->consume(bytes_consumed); + } else if (result == micro_flac::FLAC_DECODER_END_OF_STREAM) { + this->input_buffer_->consume(bytes_consumed); return FileDecoderState::END_OF_FILE; + } else if (result == micro_flac::FLAC_DECODER_NEED_MORE_DATA) { + this->input_buffer_->consume(bytes_consumed); + return FileDecoderState::MORE_TO_PROCESS; + } else if (result == micro_flac::FLAC_DECODER_ERROR_OUTPUT_TOO_SMALL) { + // Reallocate to decode the frame on the next call + const auto &info = this->flac_decoder_->get_stream_info(); + this->free_buffer_required_ = this->flac_decoder_->get_output_buffer_size_samples() * info.bytes_per_sample(); + if (!this->output_transfer_buffer_->reallocate(this->free_buffer_required_)) { + return FileDecoderState::FAILED; + } + } else { + ESP_LOGE(TAG, "FLAC decoder failed: %d", static_cast(result)); + return FileDecoderState::POTENTIALLY_FAILED; } return FileDecoderState::MORE_TO_PROCESS; diff --git a/esphome/components/audio/audio_decoder.h b/esphome/components/audio/audio_decoder.h index 726baa289e..6e3a228a68 100644 --- a/esphome/components/audio/audio_decoder.h +++ b/esphome/components/audio/audio_decoder.h @@ -16,14 +16,16 @@ #include "esp_err.h" // esp-audio-libs -#ifdef USE_AUDIO_FLAC_SUPPORT -#include -#endif #ifdef USE_AUDIO_MP3_SUPPORT #include #endif #include +// micro-flac +#ifdef USE_AUDIO_FLAC_SUPPORT +#include +#endif + // micro-opus #ifdef USE_AUDIO_OPUS_SUPPORT #include @@ -119,7 +121,7 @@ class AudioDecoder { std::unique_ptr wav_decoder_; #ifdef USE_AUDIO_FLAC_SUPPORT FileDecoderState decode_flac_(); - std::unique_ptr flac_decoder_; + std::unique_ptr flac_decoder_; #endif #ifdef USE_AUDIO_MP3_SUPPORT FileDecoderState decode_mp3_(); diff --git a/esphome/components/audio_adc/__init__.py b/esphome/components/audio_adc/__init__.py index 3c9b32e610..3c3a4988b5 100644 --- a/esphome/components/audio_adc/__init__.py +++ b/esphome/components/audio_adc/__init__.py @@ -32,7 +32,7 @@ async def audio_adc_set_mic_gain_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) - template_ = await cg.templatable(config.get(CONF_MIC_GAIN), args, float) + template_ = await cg.templatable(config.get(CONF_MIC_GAIN), args, cg.float_) cg.add(var.set_mic_gain(template_)) return var diff --git a/esphome/components/audio_dac/__init__.py b/esphome/components/audio_dac/__init__.py index a950c1967b..46c277ce51 100644 --- a/esphome/components/audio_dac/__init__.py +++ b/esphome/components/audio_dac/__init__.py @@ -52,7 +52,7 @@ async def audio_dac_set_volume_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) - template_ = await cg.templatable(config.get(CONF_VOLUME), args, float) + template_ = await cg.templatable(config.get(CONF_VOLUME), args, cg.float_) cg.add(var.set_volume(template_)) return var diff --git a/esphome/components/audio_file/media_source/audio_file_media_source.cpp b/esphome/components/audio_file/media_source/audio_file_media_source.cpp index 120f871d2f..fbb5ecd88d 100644 --- a/esphome/components/audio_file/media_source/audio_file_media_source.cpp +++ b/esphome/components/audio_file/media_source/audio_file_media_source.cpp @@ -4,6 +4,7 @@ #include "esphome/components/audio/audio_decoder.h" +#include #include namespace esphome::audio_file { @@ -249,7 +250,7 @@ void AudioFileMediaSource::decode_task(void *params) { audio::AudioStreamInfo stream_info = decoder->get_audio_stream_info().value(); - ESP_LOGD(TAG, "Bits per sample: %d, Channels: %d, Sample rate: %d", stream_info.get_bits_per_sample(), + ESP_LOGD(TAG, "Bits per sample: %d, Channels: %d, Sample rate: %" PRIu32, stream_info.get_bits_per_sample(), stream_info.get_channels(), stream_info.get_sample_rate()); if (stream_info.get_bits_per_sample() != 16 || stream_info.get_channels() > 2) { diff --git a/esphome/components/bedjet/bedjet_codec.h b/esphome/components/bedjet/bedjet_codec.h index 07aee32d54..3936ba2315 100644 --- a/esphome/components/bedjet/bedjet_codec.h +++ b/esphome/components/bedjet/bedjet_codec.h @@ -183,7 +183,7 @@ class BedjetCodec { BedjetPacket packet_; - BedjetStatusPacket *status_packet_; + BedjetStatusPacket *status_packet_{nullptr}; BedjetStatusPacket buf_; }; diff --git a/esphome/components/bedjet/climate/bedjet_climate.cpp b/esphome/components/bedjet/climate/bedjet_climate.cpp index a17407f08f..88ed902a11 100644 --- a/esphome/components/bedjet/climate/bedjet_climate.cpp +++ b/esphome/components/bedjet/climate/bedjet_climate.cpp @@ -61,6 +61,15 @@ void BedJetClimate::dump_config() { } void BedJetClimate::setup() { + // Set custom modes once during setup — stored on Climate base class, wired via get_traits() + this->set_supported_custom_fan_modes(BEDJET_FAN_STEP_NAMES); + this->set_supported_custom_presets({ + this->heating_mode_ == HEAT_MODE_EXTENDED ? "LTD HT" : "EXT HT", + "M1", + "M2", + "M3", + }); + // restore set points auto restore = this->restore_state_(); if (restore.has_value()) { diff --git a/esphome/components/bedjet/climate/bedjet_climate.h b/esphome/components/bedjet/climate/bedjet_climate.h index 05f4a849e0..d12c2a8255 100644 --- a/esphome/components/bedjet/climate/bedjet_climate.h +++ b/esphome/components/bedjet/climate/bedjet_climate.h @@ -42,21 +42,14 @@ class BedJetClimate : public climate::Climate, public BedJetClient, public Polli climate::CLIMATE_MODE_DRY, }); - // It would be better if we had a slider for the fan modes. - traits.set_supported_custom_fan_modes(BEDJET_FAN_STEP_NAMES); traits.set_supported_presets({ // If we support NONE, then have to decide what happens if the user switches to it (turn off?) // climate::CLIMATE_PRESET_NONE, // Climate doesn't have a "TURBO" mode, but we can use the BOOST preset instead. climate::CLIMATE_PRESET_BOOST, }); - // String literals are stored in rodata and valid for program lifetime - traits.set_supported_custom_presets({ - this->heating_mode_ == HEAT_MODE_EXTENDED ? "LTD HT" : "EXT HT", - "M1", - "M2", - "M3", - }); + // Custom fan modes and presets are set once in setup(), stored on Climate base class, + // and wired automatically via get_traits() traits.set_visual_min_temperature(19.0); traits.set_visual_max_temperature(43.0); traits.set_visual_temperature_step(1.0); diff --git a/esphome/components/bh1900nux/sensor.py b/esphome/components/bh1900nux/sensor.py index 5e1c0395af..a70db3555a 100644 --- a/esphome/components/bh1900nux/sensor.py +++ b/esphome/components/bh1900nux/sensor.py @@ -12,7 +12,7 @@ CODEOWNERS = ["@B48D81EFCC"] sensor_ns = cg.esphome_ns.namespace("bh1900nux") BH1900NUXSensor = sensor_ns.class_( - "BH1900NUXSensor", cg.PollingComponent, i2c.I2CDevice + "BH1900NUXSensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice ) CONFIG_SCHEMA = ( diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 37cccc01be..0b36c299f6 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -120,25 +120,15 @@ BinarySensorInitiallyOff = binary_sensor_ns.class_( BinarySensorPtr = BinarySensor.operator("ptr") # Triggers -PressTrigger = binary_sensor_ns.class_("PressTrigger", automation.Trigger.template()) -ReleaseTrigger = binary_sensor_ns.class_( - "ReleaseTrigger", automation.Trigger.template() -) ClickTrigger = binary_sensor_ns.class_("ClickTrigger", automation.Trigger.template()) DoubleClickTrigger = binary_sensor_ns.class_( "DoubleClickTrigger", automation.Trigger.template() ) -MultiClickTrigger = binary_sensor_ns.class_( - "MultiClickTrigger", automation.Trigger.template(), cg.Component +MultiClickTriggerBase = binary_sensor_ns.class_( + "MultiClickTriggerBase", automation.Trigger.template(), cg.Component ) +MultiClickTrigger = binary_sensor_ns.class_("MultiClickTrigger", MultiClickTriggerBase) MultiClickTriggerEvent = binary_sensor_ns.struct("MultiClickTriggerEvent") -StateTrigger = binary_sensor_ns.class_( - "StateTrigger", automation.Trigger.template(bool) -) -StateChangeTrigger = binary_sensor_ns.class_( - "StateChangeTrigger", - automation.Trigger.template(cg.optional.template(bool), cg.optional.template(bool)), -) BinarySensorPublishAction = binary_sensor_ns.class_( "BinarySensorPublishAction", automation.Action @@ -266,6 +256,7 @@ async def delayed_off_filter_to_code(config, filter_id): ): cv.positive_time_period_milliseconds, } ), + cv.Length(max=254), ), ) async def autorepeat_filter_to_code(config, filter_id): @@ -294,7 +285,7 @@ async def autorepeat_filter_to_code(config, filter_id): ), ) ] - var = cg.new_Pvariable(filter_id, timings) + var = cg.new_Pvariable(filter_id, cg.TemplateArguments(len(timings)), timings) await cg.register_component(var, {}) return var @@ -399,7 +390,7 @@ def validate_multi_click_timing(value): new_state = v_.get(CONF_STATE, not state) if new_state == state: raise cv.Invalid( - f"Timings must have alternating state. Indices {i} and {i + 1} have the same state {state}" + f"Timings must have alternating state. Indices {i - 1} and {i} have the same state {state}" ) if max_length is not None and max_length < min_length: raise cv.Invalid( @@ -458,16 +449,8 @@ _BINARY_SENSOR_SCHEMA = ( ): cv.boolean, cv.Optional(CONF_DEVICE_CLASS): validate_device_class, cv.Optional(CONF_FILTERS): validate_filters, - cv.Optional(CONF_ON_PRESS): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger), - } - ), - cv.Optional(CONF_ON_RELEASE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger), - } - ), + cv.Optional(CONF_ON_PRESS): automation.validate_automation({}), + cv.Optional(CONF_ON_RELEASE): automation.validate_automation({}), cv.Optional(CONF_ON_CLICK): cv.All( automation.validate_automation( { @@ -502,23 +485,17 @@ _BINARY_SENSOR_SCHEMA = ( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger), cv.Required(CONF_TIMING): cv.All( - [parse_multi_click_timing_str], validate_multi_click_timing + [parse_multi_click_timing_str], + validate_multi_click_timing, + cv.Length(min=1, max=255), ), cv.Optional( CONF_INVALID_COOLDOWN, default="1s" ): cv.positive_time_period_milliseconds, } ), - cv.Optional(CONF_ON_STATE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), - } - ), - cv.Optional(CONF_ON_STATE_CHANGE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateChangeTrigger), - } - ), + cv.Optional(CONF_ON_STATE): automation.validate_automation({}), + cv.Optional(CONF_ON_STATE_CHANGE): automation.validate_automation({}), } ) ) @@ -554,15 +531,31 @@ def binary_sensor_schema( return _BINARY_SENSOR_SCHEMA.extend(schema) +_CALLBACK_AUTOMATIONS = ( + automation.CallbackAutomation( + CONF_ON_PRESS, + "add_on_state_callback", + forwarder=automation.TriggerOnTrueForwarder, + ), + automation.CallbackAutomation( + CONF_ON_RELEASE, + "add_on_state_callback", + forwarder=automation.TriggerOnFalseForwarder, + ), + automation.CallbackAutomation( + CONF_ON_STATE, "add_on_state_callback", [(bool, "x")] + ), + automation.CallbackAutomation( + CONF_ON_STATE_CHANGE, + "add_full_state_callback", + [(cg.optional.template(bool), "x_previous"), (cg.optional.template(bool), "x")], + ), +) + + @coroutine_with_priority(CoroPriority.AUTOMATION) async def _build_binary_sensor_automations(var, config): - for conf in config.get(CONF_ON_PRESS, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - - for conf in config.get(CONF_ON_RELEASE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS) for conf in config.get(CONF_ON_CLICK, []): trigger = cg.new_Pvariable( @@ -586,27 +579,14 @@ async def _build_binary_sensor_automations(var, config): ) for tim in conf[CONF_TIMING] ] - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, timings) + trigger = cg.new_Pvariable( + conf[CONF_TRIGGER_ID], cg.TemplateArguments(len(timings)), var, timings + ) if CONF_INVALID_COOLDOWN in conf: cg.add(trigger.set_invalid_cooldown(conf[CONF_INVALID_COOLDOWN])) await cg.register_component(trigger, conf) await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_STATE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(bool, "x")], conf) - - for conf in config.get(CONF_ON_STATE_CHANGE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, - [ - (cg.optional.template(bool), "x_previous"), - (cg.optional.template(bool), "x"), - ], - conf, - ) - @setup_entity("binary_sensor") async def setup_binary_sensor_core_(var, config): diff --git a/esphome/components/binary_sensor/automation.cpp b/esphome/components/binary_sensor/automation.cpp index 7e43d42357..eb68abce3b 100644 --- a/esphome/components/binary_sensor/automation.cpp +++ b/esphome/components/binary_sensor/automation.cpp @@ -13,7 +13,7 @@ constexpr uint32_t MULTICLICK_COOLDOWN_ID = 1; constexpr uint32_t MULTICLICK_IS_VALID_ID = 2; constexpr uint32_t MULTICLICK_IS_NOT_VALID_ID = 3; -void MultiClickTrigger::on_state_(bool state) { +void MultiClickTriggerBase::on_state_(bool state) { // Handle duplicate events if (state == this->last_state_) { return; @@ -32,7 +32,7 @@ void MultiClickTrigger::on_state_(bool state) { ESP_LOGV(TAG, "START min=%" PRIu32 " max=%" PRIu32, evt.min_length, evt.max_length); ESP_LOGV(TAG, "Multi Click: Starting multi click action!"); this->at_index_ = 1; - if (this->timing_.size() == 1 && evt.max_length == 4294967294UL) { + if (this->timing_count_ == 1 && evt.max_length == 4294967294UL) { this->set_timeout(MULTICLICK_TRIGGER_ID, evt.min_length, [this]() { this->trigger_(); }); } else { this->schedule_is_valid_(evt.min_length); @@ -50,7 +50,7 @@ void MultiClickTrigger::on_state_(bool state) { return; } - if (*this->at_index_ == this->timing_.size()) { + if (*this->at_index_ == this->timing_count_) { this->trigger_(); return; } @@ -61,7 +61,7 @@ void MultiClickTrigger::on_state_(bool state) { ESP_LOGV(TAG, "A i=%zu min=%" PRIu32 " max=%" PRIu32, *this->at_index_, evt.min_length, evt.max_length); // NOLINT this->schedule_is_valid_(evt.min_length); this->schedule_is_not_valid_(evt.max_length); - } else if (*this->at_index_ + 1 != this->timing_.size()) { + } else if (*this->at_index_ + 1 != this->timing_count_) { ESP_LOGV(TAG, "B i=%zu min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID); this->schedule_is_valid_(evt.min_length); @@ -74,7 +74,7 @@ void MultiClickTrigger::on_state_(bool state) { *this->at_index_ = *this->at_index_ + 1; } -void MultiClickTrigger::schedule_cooldown_() { +void MultiClickTriggerBase::schedule_cooldown_() { ESP_LOGV(TAG, "Multi Click: Invalid length of press, starting cooldown of %" PRIu32 " ms", this->invalid_cooldown_); this->is_in_cooldown_ = true; this->set_timeout(MULTICLICK_COOLDOWN_ID, this->invalid_cooldown_, [this]() { @@ -86,7 +86,7 @@ void MultiClickTrigger::schedule_cooldown_() { this->cancel_timeout(MULTICLICK_IS_VALID_ID); this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID); } -void MultiClickTrigger::schedule_is_valid_(uint32_t min_length) { +void MultiClickTriggerBase::schedule_is_valid_(uint32_t min_length) { if (min_length == 0) { this->is_valid_ = true; return; @@ -97,19 +97,19 @@ void MultiClickTrigger::schedule_is_valid_(uint32_t min_length) { this->is_valid_ = true; }); } -void MultiClickTrigger::schedule_is_not_valid_(uint32_t max_length) { +void MultiClickTriggerBase::schedule_is_not_valid_(uint32_t max_length) { this->set_timeout(MULTICLICK_IS_NOT_VALID_ID, max_length, [this]() { ESP_LOGV(TAG, "Multi Click: You waited too long to %s.", this->parent_->state ? "RELEASE" : "PRESS"); this->is_valid_ = false; this->schedule_cooldown_(); }); } -void MultiClickTrigger::cancel() { +void MultiClickTriggerBase::cancel() { ESP_LOGV(TAG, "Multi Click: Sequence explicitly cancelled."); this->is_valid_ = false; this->schedule_cooldown_(); } -void MultiClickTrigger::trigger_() { +void MultiClickTriggerBase::trigger_() { ESP_LOGV(TAG, "Multi Click: Hooray, multi click is valid. Triggering!"); this->at_index_.reset(); this->cancel_timeout(MULTICLICK_TRIGGER_ID); diff --git a/esphome/components/binary_sensor/automation.h b/esphome/components/binary_sensor/automation.h index f8b130e08a..1875910aff 100644 --- a/esphome/components/binary_sensor/automation.h +++ b/esphome/components/binary_sensor/automation.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -89,15 +90,14 @@ class DoubleClickTrigger : public Trigger<> { uint32_t max_length_; /// Maximum length of click. 0 means no maximum. }; -class MultiClickTrigger : public Trigger<>, public Component { +/// Non-template base for MultiClickTrigger (keeps large method bodies out of the header). +class MultiClickTriggerBase : public Trigger<>, public Component { public: - explicit MultiClickTrigger(BinarySensor *parent, std::initializer_list timing) - : parent_(parent), timing_(timing) {} + explicit MultiClickTriggerBase(BinarySensor *parent) : parent_(parent) {} void setup() override { this->last_state_ = this->parent_->get_state_default(false); - auto f = std::bind(&MultiClickTrigger::on_state_, this, std::placeholders::_1); - this->parent_->add_on_state_callback(f); + this->parent_->add_on_state_callback([this](bool state) { this->on_state_(state); }); } float get_setup_priority() const override { return setup_priority::HARDWARE; } @@ -105,6 +105,8 @@ class MultiClickTrigger : public Trigger<>, public Component { void set_invalid_cooldown(uint32_t invalid_cooldown) { this->invalid_cooldown_ = invalid_cooldown; } void cancel(); + MultiClickTriggerBase(const MultiClickTriggerBase &) = delete; + MultiClickTriggerBase &operator=(const MultiClickTriggerBase &) = delete; protected: void on_state_(bool state); @@ -114,14 +116,30 @@ class MultiClickTrigger : public Trigger<>, public Component { void trigger_(); BinarySensor *parent_; - FixedVector timing_; + const MultiClickTriggerEvent *timing_{nullptr}; uint32_t invalid_cooldown_{1000}; optional at_index_{}; + uint8_t timing_count_{0}; bool last_state_{false}; bool is_in_cooldown_{false}; bool is_valid_{false}; }; +/// Template wrapper that provides inline std::array storage for timing events. +/// N is set by code generation to match the exact number of timing events configured in YAML. +template class MultiClickTrigger : public MultiClickTriggerBase { + public: + MultiClickTrigger(BinarySensor *parent, std::initializer_list timing) + : MultiClickTriggerBase(parent) { + init_array_from(this->timing_storage_, timing); + this->timing_ = this->timing_storage_.data(); + this->timing_count_ = N; + } + + protected: + std::array timing_storage_{}; +}; + class StateTrigger : public Trigger { public: explicit StateTrigger(BinarySensor *parent) { diff --git a/esphome/components/binary_sensor/binary_sensor.cpp b/esphome/components/binary_sensor/binary_sensor.cpp index c4d3a29a1e..7596975a68 100644 --- a/esphome/components/binary_sensor/binary_sensor.cpp +++ b/esphome/components/binary_sensor/binary_sensor.cpp @@ -32,20 +32,13 @@ void BinarySensor::publish_initial_state(bool new_state) { this->invalidate_state(); this->publish_state(new_state); } -void BinarySensor::send_state_internal(bool new_state) { - // copy the new state to the visible property for backwards compatibility, before any callbacks - this->state = new_state; - // Note that set_new_state_ de-dups and will only trigger callbacks if the state has actually changed - this->set_new_state(new_state); -} - bool BinarySensor::set_new_state(const optional &new_state) { if (StatefulEntityBase::set_new_state(new_state)) { // weirdly, this file could be compiled even without USE_BINARY_SENSOR defined #if defined(USE_BINARY_SENSOR) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_binary_sensor_update(this); #endif - ESP_LOGD(TAG, "'%s' >> %s", this->get_name().c_str(), ONOFFMAYBE(new_state)); + ESP_LOGV(TAG, "'%s' >> %s", this->get_name().c_str(), ONOFFMAYBE(new_state)); return true; } return false; diff --git a/esphome/components/binary_sensor/binary_sensor.h b/esphome/components/binary_sensor/binary_sensor.h index 6ae5d04bcb..28c156763a 100644 --- a/esphome/components/binary_sensor/binary_sensor.h +++ b/esphome/components/binary_sensor/binary_sensor.h @@ -32,7 +32,10 @@ void log_binary_sensor(const char *tag, const char *prefix, const char *type, Bi */ class BinarySensor : public StatefulEntityBase { public: - explicit BinarySensor(){}; + explicit BinarySensor() = default; + + const bool &get_state() const override { return this->state; } + void set_trigger_on_initial_state(bool value) { this->trigger_on_initial_state_ = value; } /** Publish a new state to the front-end. * @@ -54,16 +57,24 @@ class BinarySensor : public StatefulEntityBase { // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) - void send_state_internal(bool new_state); + void send_state_internal(bool new_state) { + // Fast path: skip virtual dispatch when state hasn't changed + if (this->flags_.has_state && this->state == new_state) + return; + this->set_new_state(new_state); + } /// Return whether this binary sensor has outputted a state. virtual bool is_status_binary_sensor() const; - // For backward compatibility, provide an accessible property - + /// The current state of this binary sensor. Also used as the backing storage for StatefulEntityBase. bool state{}; protected: + bool get_trigger_on_initial_state() const override { return this->trigger_on_initial_state_; } + void set_state_value(const bool &value) override { this->state = value; } + + bool trigger_on_initial_state_{true}; #ifdef USE_BINARY_SENSOR_FILTER Filter *filter_list_{nullptr}; #endif @@ -73,7 +84,7 @@ class BinarySensor : public StatefulEntityBase { class BinarySensorInitiallyOff : public BinarySensor { public: - bool has_state() const override { return true; } + BinarySensorInitiallyOff() { this->set_has_state(true); } }; } // namespace esphome::binary_sensor diff --git a/esphome/components/binary_sensor/filter.cpp b/esphome/components/binary_sensor/filter.cpp index 5d525e967d..914060ce13 100644 --- a/esphome/components/binary_sensor/filter.cpp +++ b/esphome/components/binary_sensor/filter.cpp @@ -76,14 +76,11 @@ float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARD optional InvertFilter::new_value(bool value) { return !value; } -AutorepeatFilter::AutorepeatFilter(std::initializer_list timings) : timings_(timings) {} - -optional AutorepeatFilter::new_value(bool value) { +// AutorepeatFilterBase +optional AutorepeatFilterBase::new_value(bool value) { if (value) { - // Ignore if already running if (this->active_timing_ != 0) return {}; - this->next_timing_(); return true; } else { @@ -94,34 +91,26 @@ optional AutorepeatFilter::new_value(bool value) { } } -void AutorepeatFilter::next_timing_() { - // Entering this method - // 1st time: starts waiting the first delay - // 2nd time: starts waiting the second delay and starts toggling with the first time_off / _on - // last time: no delay to start but have to bump the index to reflect the last - if (this->active_timing_ < this->timings_.size()) { +void AutorepeatFilterBase::next_timing_() { + if (this->active_timing_ < this->timings_count_) { this->set_timeout(AUTOREPEAT_TIMING_ID, this->timings_[this->active_timing_].delay, [this]() { this->next_timing_(); }); } - - if (this->active_timing_ <= this->timings_.size()) { + if (this->active_timing_ <= this->timings_count_) { this->active_timing_++; } - if (this->active_timing_ == 2) this->next_value_(false); - - // Leaving this method: if the toggling is started, it has to use [active_timing_ - 2] for the intervals } -void AutorepeatFilter::next_value_(bool val) { +void AutorepeatFilterBase::next_value_(bool val) { const AutorepeatFilterTiming &timing = this->timings_[this->active_timing_ - 2]; - this->output(val); // This is at least the second one so not initial + this->output(val); this->set_timeout(AUTOREPEAT_ON_OFF_ID, val ? timing.time_on : timing.time_off, [this, val]() { this->next_value_(!val); }); } -float AutorepeatFilter::get_setup_priority() const { return setup_priority::HARDWARE; } +float AutorepeatFilterBase::get_setup_priority() const { return setup_priority::HARDWARE; } LambdaFilter::LambdaFilter(std::function(bool)> f) : f_(std::move(f)) {} diff --git a/esphome/components/binary_sensor/filter.h b/esphome/components/binary_sensor/filter.h index 2735a32ab0..2e45554f81 100644 --- a/esphome/components/binary_sensor/filter.h +++ b/esphome/components/binary_sensor/filter.h @@ -3,6 +3,8 @@ #include "esphome/core/defines.h" #ifdef USE_BINARY_SENSOR_FILTER +#include + #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" @@ -34,10 +36,10 @@ class TimeoutFilter : public Filter, public Component { template void set_timeout_value(T timeout) { this->timeout_delay_ = timeout; } protected: - TemplatableValue timeout_delay_{}; + TemplatableFn timeout_delay_{}; }; -class DelayedOnOffFilter : public Filter, public Component { +class DelayedOnOffFilter final : public Filter, public Component { public: optional new_value(bool value) override; @@ -47,8 +49,8 @@ class DelayedOnOffFilter : public Filter, public Component { template void set_off_delay(T delay) { this->off_delay_ = delay; } protected: - TemplatableValue on_delay_{}; - TemplatableValue off_delay_{}; + TemplatableFn on_delay_{}; + TemplatableFn off_delay_{}; }; class DelayedOnFilter : public Filter, public Component { @@ -60,7 +62,7 @@ class DelayedOnFilter : public Filter, public Component { template void set_delay(T delay) { this->delay_ = delay; } protected: - TemplatableValue delay_{}; + TemplatableFn delay_{}; }; class DelayedOffFilter : public Filter, public Component { @@ -72,7 +74,7 @@ class DelayedOffFilter : public Filter, public Component { template void set_delay(T delay) { this->delay_ = delay; } protected: - TemplatableValue delay_{}; + TemplatableFn delay_{}; }; class InvertFilter : public Filter { @@ -86,22 +88,39 @@ struct AutorepeatFilterTiming { uint32_t time_on; }; -class AutorepeatFilter : public Filter, public Component { +/// Non-template base for AutorepeatFilter — all methods in filter.cpp. +/// Lambdas capture this base pointer, so set_timeout/cancel_timeout are instantiated once. +class AutorepeatFilterBase : public Filter, public Component { public: - explicit AutorepeatFilter(std::initializer_list timings); - optional new_value(bool value) override; - float get_setup_priority() const override; + AutorepeatFilterBase(const AutorepeatFilterBase &) = delete; + AutorepeatFilterBase &operator=(const AutorepeatFilterBase &) = delete; protected: + AutorepeatFilterBase() = default; void next_timing_(); void next_value_(bool val); - FixedVector timings_; + const AutorepeatFilterTiming *timings_{nullptr}; + uint8_t timings_count_{0}; uint8_t active_timing_{0}; }; +/// Template wrapper that provides inline std::array storage for timings. +/// N is set by code generation to match the exact number of timings configured in YAML. +template class AutorepeatFilter : public AutorepeatFilterBase { + public: + explicit AutorepeatFilter(std::initializer_list timings) { + init_array_from(this->timings_storage_, timings); + this->timings_ = this->timings_storage_.data(); + this->timings_count_ = N; + } + + protected: + std::array timings_storage_{}; +}; + class LambdaFilter : public Filter { public: explicit LambdaFilter(std::function(bool)> f); @@ -136,7 +155,7 @@ class SettleFilter : public Filter, public Component { template void set_delay(T delay) { this->delay_ = delay; } protected: - TemplatableValue delay_{}; + TemplatableFn delay_{}; bool steady_{true}; }; diff --git a/esphome/components/bl0940/number/__init__.py b/esphome/components/bl0940/number/__init__.py index a640c2ae08..92ab2837b3 100644 --- a/esphome/components/bl0940/number/__init__.py +++ b/esphome/components/bl0940/number/__init__.py @@ -89,6 +89,6 @@ async def to_code(config): ) await cg.register_component(var, conf) - if restore_value := config.get(CONF_RESTORE_VALUE): + if restore_value := conf.get(CONF_RESTORE_VALUE): cg.add(var.set_restore_value(restore_value)) cg.add(getattr(bl0940, setter_method)(var)) diff --git a/esphome/components/bl0940/sensor.py b/esphome/components/bl0940/sensor.py index d2e0ea435d..992064943b 100644 --- a/esphome/components/bl0940/sensor.py +++ b/esphome/components/bl0940/sensor.py @@ -124,9 +124,9 @@ def set_reference_values(config): config.setdefault(CONF_VOLTAGE_REFERENCE, DEFAULT_BL0940_LEGACY_UREF) config.setdefault(CONF_CURRENT_REFERENCE, DEFAULT_BL0940_LEGACY_IREF) config.setdefault(CONF_POWER_REFERENCE, DEFAULT_BL0940_LEGACY_PREF) - config.setdefault(CONF_ENERGY_REFERENCE, DEFAULT_BL0940_LEGACY_PREF) + config.setdefault(CONF_ENERGY_REFERENCE, DEFAULT_BL0940_LEGACY_EREF) else: - vref = config.get(CONF_VOLTAGE_REFERENCE, DEFAULT_BL0940_VREF) + vref = config.get(CONF_REFERENCE_VOLTAGE, DEFAULT_BL0940_VREF) r_one = config.get(CONF_RESISTOR_ONE, DEFAULT_BL0940_R1) r_two = config.get(CONF_RESISTOR_TWO, DEFAULT_BL0940_R2) r_shunt = config.get(CONF_RESISTOR_SHUNT, DEFAULT_BL0940_RL) diff --git a/esphome/components/ble_client/sensor/ble_sensor.cpp b/esphome/components/ble_client/sensor/ble_sensor.cpp index fe5f11bbc2..4bd871dc81 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_sensor.cpp @@ -102,6 +102,10 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga break; } case ESP_GATTC_NOTIFY_EVT: { + if (param->notify.value_len == 0) { + ESP_LOGW(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: empty value", this->get_name().c_str()); + break; + } ESP_LOGD(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(), param->notify.handle, param->notify.value[0]); if (param->notify.handle != this->handle) @@ -131,8 +135,10 @@ float BLESensor::parse_data_(uint8_t *value, uint16_t value_len) { if (this->has_data_to_value_) { std::vector data(value, value + value_len); return this->data_to_value_func_(data); - } else { + } else if (value_len > 0) { return value[0]; + } else { + return NAN; } } diff --git a/esphome/components/ble_client/text_sensor/__init__.py b/esphome/components/ble_client/text_sensor/__init__.py index a6b8956f93..0f53cccdad 100644 --- a/esphome/components/ble_client/text_sensor/__init__.py +++ b/esphome/components/ble_client/text_sensor/__init__.py @@ -88,7 +88,7 @@ async def to_code(config): ) cg.add(var.set_char_uuid128(uuid128)) - if descriptor_uuid := config: + if descriptor_uuid := config.get(CONF_DESCRIPTOR_UUID): if len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid16_format): cg.add(var.set_descr_uuid16(esp32_ble_tracker.as_hex(descriptor_uuid))) elif len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid32_format): diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp index cacf1b4835..7eaa6af076 100644 --- a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp @@ -104,6 +104,10 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ case ESP_GATTC_NOTIFY_EVT: { if (param->notify.handle != this->handle) break; + if (param->notify.value_len == 0) { + ESP_LOGW(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: empty value", this->get_name().c_str()); + break; + } ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(), param->notify.handle, param->notify.value[0]); this->publish_state(reinterpret_cast(param->notify.value), param->notify.value_len); diff --git a/esphome/components/ble_nus/ble_nus.cpp b/esphome/components/ble_nus/ble_nus.cpp index 2f60f81471..71d98332e0 100644 --- a/esphome/components/ble_nus/ble_nus.cpp +++ b/esphome/components/ble_nus/ble_nus.cpp @@ -103,17 +103,17 @@ size_t BLENUS::available() { #endif } -uart::FlushResult BLENUS::flush() { +uart::UARTFlushResult BLENUS::flush() { constexpr uint32_t timeout_500ms = 500; uint32_t start = millis(); while (atomic_get(&this->tx_status_) != TX_DISABLED && !ring_buf_is_empty(&global_ble_tx_ring_buf)) { if (millis() - start > timeout_500ms) { ESP_LOGW(TAG, "Flush timeout"); - return uart::FlushResult::TIMEOUT; + return uart::UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT; } delay(1); } - return uart::FlushResult::SUCCESS; + return uart::UARTFlushResult::UART_FLUSH_RESULT_SUCCESS; } void BLENUS::connected(bt_conn *conn, uint8_t err) { diff --git a/esphome/components/ble_nus/ble_nus.h b/esphome/components/ble_nus/ble_nus.h index b482c240e5..f1afd54af9 100644 --- a/esphome/components/ble_nus/ble_nus.h +++ b/esphome/components/ble_nus/ble_nus.h @@ -26,7 +26,7 @@ class BLENUS : public uart::UARTComponent, public Component { bool peek_byte(uint8_t *data) override; bool read_array(uint8_t *data, size_t len) override; size_t available() override; - uart::FlushResult flush() override; + uart::UARTFlushResult flush() override; void check_logger_conflict() override {} void set_expose_log(bool expose_log) { this->expose_log_ = expose_log; } #ifdef USE_LOGGER diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index 87206996b2..c69163b1f7 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -30,6 +30,19 @@ void BluetoothProxy::setup() { this->configured_scan_active_ = this->parent_->get_scan_active(); this->parent_->add_scanner_state_listener(this); + + this->set_interval(100, [this]() { + if (api::global_api_server->is_connected() && this->api_connection_ != nullptr) { + this->flush_pending_advertisements_(); + return; + } + for (uint8_t i = 0; i < this->connection_count_; i++) { + auto *connection = this->connections_[i]; + if (connection->get_address() != 0 && !connection->disconnect_pending()) { + connection->disconnect(); + } + } + }); } void BluetoothProxy::on_scanner_state(esp32_ble_tracker::ScannerState state) { @@ -101,25 +114,15 @@ bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results, // Flush if we have reached BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE if (this->response_.advertisements_len >= BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE) { - this->flush_pending_advertisements(); + this->flush_pending_advertisements_(); } } return true; } -void BluetoothProxy::flush_pending_advertisements() { - if (this->response_.advertisements_len == 0 || !api::global_api_server->is_connected() || - this->api_connection_ == nullptr) - return; - - // Send the message - this->api_connection_->send_message(this->response_); - +void BluetoothProxy::log_advertisement_flush_() { ESP_LOGV(TAG, "Sent batch of %u BLE advertisements", this->response_.advertisements_len); - - // Reset the length for the next batch - this->response_.advertisements_len = 0; } void BluetoothProxy::dump_config() { @@ -130,27 +133,6 @@ void BluetoothProxy::dump_config() { YESNO(this->active_), this->connection_count_); } -void BluetoothProxy::loop() { - if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) { - for (uint8_t i = 0; i < this->connection_count_; i++) { - auto *connection = this->connections_[i]; - if (connection->get_address() != 0 && !connection->disconnect_pending()) { - connection->disconnect(); - } - } - return; - } - - // Flush any pending BLE advertisements that have been accumulated but not yet sent - uint32_t now = App.get_loop_component_start_time(); - - // Flush accumulated advertisements every 100ms - if (now - this->last_advertisement_flush_time_ >= 100) { - this->flush_pending_advertisements(); - this->last_advertisement_flush_time_ = now; - } -} - esp32_ble_tracker::AdvertisementParserType BluetoothProxy::get_advertisement_parser_type() { return esp32_ble_tracker::AdvertisementParserType::RAW_ADVERTISEMENTS; } diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index f1b723e719..6680ab0e84 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -65,8 +65,6 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, bool parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) override; void dump_config() override; void setup() override; - void loop() override; - void flush_pending_advertisements(); esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override; void register_connection(BluetoothConnection *connection) { @@ -150,6 +148,18 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, protected: void send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state); + /// Caller must ensure api_connection_ is non-null and API server is connected. + void flush_pending_advertisements_() { + if (this->response_.advertisements_len == 0) + return; + this->api_connection_->send_message(this->response_); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + this->log_advertisement_flush_(); +#endif + this->response_.advertisements_len = 0; + } + void log_advertisement_flush_(); + BluetoothConnection *get_connection_(uint64_t address, bool reserve); void log_connection_request_ignored_(BluetoothConnection *connection, espbt::ClientState state); void log_connection_info_(BluetoothConnection *connection, const char *message); @@ -166,9 +176,6 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, // BLE advertisement batching api::BluetoothLERawAdvertisementsResponse response_; - // Group 3: 4-byte types - uint32_t last_advertisement_flush_time_{0}; - // Pre-allocated response message - always ready to send api::BluetoothConnectionsFreeResponse connections_free_response_; diff --git a/esphome/components/bm8563/bm8563.cpp b/esphome/components/bm8563/bm8563.cpp index 07831485c1..062094c036 100644 --- a/esphome/components/bm8563/bm8563.cpp +++ b/esphome/components/bm8563/bm8563.cpp @@ -1,4 +1,7 @@ #include "bm8563.h" + +#include + #include "esphome/core/log.h" namespace esphome::bm8563 { @@ -56,7 +59,6 @@ void BM8563::read_time() { ESPTime rtc_time; this->get_time_(rtc_time); this->get_date_(rtc_time); - rtc_time.day_of_year = 1; // unused by recalc_timestamp_utc, but needs to be valid ESP_LOGD(TAG, "Read time: %i-%i-%i %i, %i:%i:%i", rtc_time.year, rtc_time.month, rtc_time.day_of_month, rtc_time.day_of_week, rtc_time.hour, rtc_time.minute, rtc_time.second); @@ -147,10 +149,10 @@ optional BM8563::read_register_(uint8_t reg) { } void BM8563::set_timer_irq_(uint32_t duration_s) { - ESP_LOGI(TAG, "Timer Duration: %u s", duration_s); + ESP_LOGI(TAG, "Timer Duration: %" PRIu32 " s", duration_s); if (duration_s > MAX_TIMER_DURATION_S) { - ESP_LOGW(TAG, "Timer duration %u s exceeds maximum %u s", duration_s, MAX_TIMER_DURATION_S); + ESP_LOGW(TAG, "Timer duration %" PRIu32 " s exceeds maximum %" PRIu32 " s", duration_s, MAX_TIMER_DURATION_S); return; } diff --git a/esphome/components/bme680_bsec/bme680_bsec.cpp b/esphome/components/bme680_bsec/bme680_bsec.cpp index 75efb6835a..bb0417b823 100644 --- a/esphome/components/bme680_bsec/bme680_bsec.cpp +++ b/esphome/components/bme680_bsec/bme680_bsec.cpp @@ -521,7 +521,7 @@ int BME680BSECComponent::reinit_bsec_lib_() { } void BME680BSECComponent::load_state_() { - uint32_t hash = fnv1_hash("bme680_bsec_state_" + this->device_id_); + uint32_t hash = fnv1_hash_extend(fnv1_hash("bme680_bsec_state_"), this->device_id_); this->bsec_state_ = global_preferences->make_preference(hash, true); if (!this->bsec_state_.load(&this->bsec_state_data_)) { diff --git a/esphome/components/bme68x_bsec2/bme68x_bsec2.cpp b/esphome/components/bme68x_bsec2/bme68x_bsec2.cpp index 0210d1e67d..d4ac57d750 100644 --- a/esphome/components/bme68x_bsec2/bme68x_bsec2.cpp +++ b/esphome/components/bme68x_bsec2/bme68x_bsec2.cpp @@ -6,10 +6,7 @@ #ifdef USE_BSEC2 #include "bme68x_bsec2.h" -#include - -namespace esphome { -namespace bme68x_bsec2 { +namespace esphome::bme68x_bsec2 { #define BME68X_BSEC2_ALGORITHM_OUTPUT_LOG(a) (a == ALGORITHM_OUTPUT_CLASSIFICATION ? "Classification" : "Regression") #define BME68X_BSEC2_OPERATING_AGE_LOG(o) (o == OPERATING_AGE_4D ? "4 days" : "28 days") @@ -18,9 +15,19 @@ namespace bme68x_bsec2 { static const char *const TAG = "bme68x_bsec2.sensor"; -static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"}; +static constexpr const char *const IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"}; + +static bool is_no_new_data_warning(int8_t status) { +#ifdef BME68X_W_NO_NEW_DATA + return status == BME68X_W_NO_NEW_DATA; +#else + return status == 2; +#endif +} void BME68xBSEC2Component::setup() { + this->warn_if_blocking_over_ = 60; // initial reads may block for up to 60ms + this->bsec_status_ = bsec_init_m(&this->bsec_instance_); if (this->bsec_status_ != BSEC_OK) { this->mark_failed(); @@ -82,7 +89,7 @@ void BME68xBSEC2Component::dump_config() { " Operating age: %s\n" " Sample rate: %s\n" " Voltage: %s\n" - " State save interval: %ims\n" + " State save interval: %" PRIu32 "ms\n" " Temperature offset: %.2f", BME68X_BSEC2_OPERATING_AGE_LOG(this->operating_age_), BME68X_BSEC2_SAMPLE_RATE_LOG(this->sample_rate_), BME68X_BSEC2_VOLTAGE_LOG(this->voltage_), this->state_save_interval_ms_, this->temperature_offset_); @@ -114,7 +121,8 @@ void BME68xBSEC2Component::loop() { } else { this->status_clear_error(); } - if (this->bsec_status_ > BSEC_OK || this->bme68x_status_ > BME68X_OK) { + const bool has_bme68x_warning = this->bme68x_status_ > BME68X_OK && !is_no_new_data_warning(this->bme68x_status_); + if (this->bsec_status_ > BSEC_OK || has_bme68x_warning) { this->status_set_warning(); } else { this->status_clear_warning(); @@ -130,7 +138,7 @@ void BME68xBSEC2Component::loop() { void BME68xBSEC2Component::set_config_(const uint8_t *config, uint32_t len) { if (len > BSEC_MAX_PROPERTY_BLOB_SIZE) { - ESP_LOGE(TAG, "Configuration is larger than BSEC_MAX_PROPERTY_BLOB_SIZE"); + ESP_LOGE(TAG, "Configuration blob too large"); this->mark_failed(); return; } @@ -212,14 +220,12 @@ void BME68xBSEC2Component::run_() { if (curr_time_ns < this->bsec_settings_.next_call) { return; } - uint8_t status; - ESP_LOGV(TAG, "Performing sensor run"); struct bme68x_conf bme68x_conf; this->bsec_status_ = bsec_sensor_control_m(&this->bsec_instance_, curr_time_ns, &this->bsec_settings_); if (this->bsec_status_ < BSEC_OK) { - ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC2 error code %d)", this->bsec_status_); + ESP_LOGW(TAG, "Fetching control settings failed (BSEC2 error code %d)", this->bsec_status_); return; } @@ -235,9 +241,9 @@ void BME68xBSEC2Component::run_() { this->bme68x_heatr_conf_.heatr_temp = this->bsec_settings_.heater_temperature; this->bme68x_heatr_conf_.heatr_dur = this->bsec_settings_.heater_duration; - // status = bme68x_set_op_mode(this->bsec_settings_.op_mode, &this->bme68x_); - status = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &this->bme68x_heatr_conf_, &this->bme68x_); - status = bme68x_set_op_mode(BME68X_FORCED_MODE, &this->bme68x_); + // this->bme68x_status_ = bme68x_set_op_mode(this->bsec_settings_.op_mode, &this->bme68x_); + this->bme68x_status_ = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &this->bme68x_heatr_conf_, &this->bme68x_); + this->bme68x_status_ = bme68x_set_op_mode(BME68X_FORCED_MODE, &this->bme68x_); this->op_mode_ = BME68X_FORCED_MODE; ESP_LOGV(TAG, "Using forced mode"); @@ -259,9 +265,8 @@ void BME68xBSEC2Component::run_() { BSEC_TOTAL_HEAT_DUR - (bme68x_get_meas_dur(BME68X_PARALLEL_MODE, &bme68x_conf, &this->bme68x_) / INT64_C(1000)); - status = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &this->bme68x_heatr_conf_, &this->bme68x_); - - status = bme68x_set_op_mode(BME68X_PARALLEL_MODE, &this->bme68x_); + this->bme68x_status_ = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &this->bme68x_heatr_conf_, &this->bme68x_); + this->bme68x_status_ = bme68x_set_op_mode(BME68X_PARALLEL_MODE, &this->bme68x_); this->op_mode_ = BME68X_PARALLEL_MODE; ESP_LOGV(TAG, "Using parallel mode"); } @@ -276,29 +281,21 @@ void BME68xBSEC2Component::run_() { } if (this->bsec_settings_.trigger_measurement && this->bsec_settings_.op_mode != BME68X_SLEEP_MODE) { - uint32_t meas_dur = 0; - meas_dur = bme68x_get_meas_dur(this->op_mode_, &bme68x_conf, &this->bme68x_); - ESP_LOGV(TAG, "Queueing read in %uus", meas_dur); - this->set_timeout("read", meas_dur / 1000, [this, curr_time_ns]() { this->read_(curr_time_ns); }); + bme68x_get_conf(&bme68x_conf, &this->bme68x_); + uint32_t meas_dur = bme68x_get_meas_dur(this->op_mode_, &bme68x_conf, &this->bme68x_); + ESP_LOGV(TAG, "Queueing read in %" PRIu32 "us", meas_dur); + this->trigger_time_ns_ = curr_time_ns; + this->set_timeout("read", meas_dur / 1000, [this]() { this->read_(this->trigger_time_ns_); }); } else { - ESP_LOGV(TAG, "Measurement not required"); - this->read_(curr_time_ns); + ESP_LOGV(TAG, "Measurement not required, queueing immediate read"); + this->trigger_time_ns_ = curr_time_ns; + this->set_timeout("read", 0, [this]() { this->read_(this->trigger_time_ns_); }); } } void BME68xBSEC2Component::read_(int64_t trigger_time_ns) { ESP_LOGV(TAG, "Reading data"); - if (this->bsec_settings_.trigger_measurement) { - uint8_t current_op_mode; - this->bme68x_status_ = bme68x_get_op_mode(¤t_op_mode, &this->bme68x_); - - if (current_op_mode == BME68X_SLEEP_MODE) { - ESP_LOGV(TAG, "Still in sleep mode, doing nothing"); - return; - } - } - if (!this->bsec_settings_.process_data) { ESP_LOGV(TAG, "Data processing not required"); return; @@ -308,12 +305,16 @@ void BME68xBSEC2Component::read_(int64_t trigger_time_ns) { uint8_t nFields = 0; this->bme68x_status_ = bme68x_get_data(this->op_mode_, &data[0], &nFields, &this->bme68x_); + if (is_no_new_data_warning(this->bme68x_status_)) { + ESP_LOGV(TAG, "BME68X did not provide new data"); + return; + } if (this->bme68x_status_ != BME68X_OK) { - ESP_LOGW(TAG, "Failed to get sensor data (BME68X error code %d)", this->bme68x_status_); + ESP_LOGW(TAG, "Fetching data failed (BME68X error code %d)", this->bme68x_status_); return; } if (nFields < 1) { - ESP_LOGD(TAG, "BME68X did not provide new data"); + ESP_LOGV(TAG, "BME68X did not provide new fields"); return; } @@ -372,7 +373,7 @@ void BME68xBSEC2Component::read_(int64_t trigger_time_ns) { uint8_t num_outputs = BSEC_NUMBER_OUTPUTS; this->bsec_status_ = bsec_do_steps_m(&this->bsec_instance_, inputs, num_inputs, outputs, &num_outputs); if (this->bsec_status_ != BSEC_OK) { - ESP_LOGW(TAG, "BSEC2 failed to process signals (BSEC2 error code %d)", this->bsec_status_); + ESP_LOGW(TAG, "Signal processing failed (BSEC2 error code %d)", this->bsec_status_); return; } if (num_outputs < 1) { @@ -473,7 +474,7 @@ void BME68xBSEC2Component::publish_sensor_(sensor::Sensor *sensor, float value, #endif #ifdef USE_TEXT_SENSOR -void BME68xBSEC2Component::publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value) { +void BME68xBSEC2Component::publish_sensor_(text_sensor::TextSensor *sensor, const char *value) { if (!sensor || (sensor->has_state() && sensor->state == value)) { return; } @@ -525,6 +526,5 @@ void BME68xBSEC2Component::save_state_(uint8_t accuracy) { ESP_LOGI(TAG, "Saved state"); } -} // namespace bme68x_bsec2 -} // namespace esphome +} // namespace esphome::bme68x_bsec2 #endif diff --git a/esphome/components/bme68x_bsec2/bme68x_bsec2.h b/esphome/components/bme68x_bsec2/bme68x_bsec2.h index 8f4d8f61c2..9317229a1f 100644 --- a/esphome/components/bme68x_bsec2/bme68x_bsec2.h +++ b/esphome/components/bme68x_bsec2/bme68x_bsec2.h @@ -19,8 +19,7 @@ #include -namespace esphome { -namespace bme68x_bsec2 { +namespace esphome::bme68x_bsec2 { enum AlgorithmOutput { ALGORITHM_OUTPUT_IAQ, @@ -97,7 +96,7 @@ class BME68xBSEC2Component : public Component { void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only = false); #endif #ifdef USE_TEXT_SENSOR - void publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value); + void publish_sensor_(text_sensor::TextSensor *sensor, const char *value); #endif void load_state_(); @@ -108,37 +107,12 @@ class BME68xBSEC2Component : public Component { struct bme68x_dev bme68x_; bsec_bme_settings_t bsec_settings_; bsec_version_t version_; - uint8_t bsec_instance_[BSEC_INSTANCE_SIZE]; - struct bme68x_heatr_conf bme68x_heatr_conf_; - uint8_t op_mode_; // operating mode of sensor - bsec_library_return_t bsec_status_{BSEC_OK}; - int8_t bme68x_status_{BME68X_OK}; - - int64_t last_time_ms_{0}; - uint32_t millis_overflow_counter_{0}; std::queue> queue_; + ESPPreferenceObject bsec_state_; uint8_t const *bsec2_configuration_{nullptr}; - uint32_t bsec2_configuration_length_{0}; - bool bsec2_blob_configured_{false}; - - ESPPreferenceObject bsec_state_; - uint32_t state_save_interval_ms_{21600000}; // 6 hours - 4 times a day - uint32_t last_state_save_ms_ = 0; - - float temperature_offset_{0}; - - AlgorithmOutput algorithm_output_{ALGORITHM_OUTPUT_IAQ}; - OperatingAge operating_age_{OPERATING_AGE_28D}; - Voltage voltage_{VOLTAGE_3_3V}; - - SampleRate sample_rate_{SAMPLE_RATE_LP}; // Core/gas sample rate - SampleRate temperature_sample_rate_{SAMPLE_RATE_DEFAULT}; - SampleRate pressure_sample_rate_{SAMPLE_RATE_DEFAULT}; - SampleRate humidity_sample_rate_{SAMPLE_RATE_DEFAULT}; - #ifdef USE_SENSOR sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *pressure_sensor_{nullptr}; @@ -153,8 +127,32 @@ class BME68xBSEC2Component : public Component { #ifdef USE_TEXT_SENSOR text_sensor::TextSensor *iaq_accuracy_text_sensor_{nullptr}; #endif + + int64_t last_time_ms_{0}; + int64_t trigger_time_ns_{0}; // Stored for set_timeout lambda to help avoid heap allocation on supported 32-bit + // toolchains with small std::function SBO + + uint32_t state_save_interval_ms_{21600000}; // 6 hours - 4 times a day + uint32_t last_state_save_ms_{0}; + uint32_t millis_overflow_counter_{0}; + uint32_t bsec2_configuration_length_{0}; + bsec_library_return_t bsec_status_{BSEC_OK}; + + float temperature_offset_{0}; + + AlgorithmOutput algorithm_output_{ALGORITHM_OUTPUT_IAQ}; + OperatingAge operating_age_{OPERATING_AGE_28D}; + Voltage voltage_{VOLTAGE_3_3V}; + SampleRate sample_rate_{SAMPLE_RATE_LP}; // Core/gas sample rate + SampleRate temperature_sample_rate_{SAMPLE_RATE_DEFAULT}; + SampleRate pressure_sample_rate_{SAMPLE_RATE_DEFAULT}; + SampleRate humidity_sample_rate_{SAMPLE_RATE_DEFAULT}; + + uint8_t bsec_instance_[BSEC_INSTANCE_SIZE]; + uint8_t op_mode_; // operating mode of sensor + int8_t bme68x_status_{BME68X_OK}; + bool bsec2_blob_configured_{false}; }; -} // namespace bme68x_bsec2 -} // namespace esphome +} // namespace esphome::bme68x_bsec2 #endif diff --git a/esphome/components/bmp581_base/bmp581_base.cpp b/esphome/components/bmp581_base/bmp581_base.cpp index 89a92de31d..7a627eee03 100644 --- a/esphome/components/bmp581_base/bmp581_base.cpp +++ b/esphome/components/bmp581_base/bmp581_base.cpp @@ -126,7 +126,7 @@ void BMP581Component::setup() { } // verify id - if (chip_id != BMP581_ASIC_ID) { + if (chip_id != BMP581_ASIC_ID && chip_id != BMP585_ASIC_ID) { ESP_LOGE(TAG, "Unknown chip ID"); this->error_code_ = ERROR_WRONG_CHIP_ID; @@ -469,14 +469,18 @@ bool BMP581Component::read_temperature_and_pressure_(float &temperature, float & } bool BMP581Component::reset_() { + // - activates interface (only relevant for SPI mode) // - writes reset command to the command register // - waits for sensor to complete reset + // - activates interface (only relevant for SPI mode) // - returns the Power-On-Reboot interrupt status, which is asserted if successful + // activates communication interface (SPI only) + this->activate_interface(); + // writes reset command to BMP's command register if (!this->bmp_write_byte(BMP581_COMMAND, RESET_COMMAND)) { ESP_LOGE(TAG, "Failed to write reset command"); - return false; } @@ -484,6 +488,9 @@ bool BMP581Component::reset_() { // - round up to 3 ms delay(3); + // reactivates communication interface after reset (SPI only) + this->activate_interface(); + // read interrupt status register if (!this->bmp_read_byte(BMP581_INT_STATUS, &this->int_status_.reg)) { ESP_LOGE(TAG, "Failed to read interrupt status register"); @@ -491,7 +498,7 @@ bool BMP581Component::reset_() { return false; } - // Power-On-Reboot bit is asserted if sensor successfully reset + // power-On-Reboot bit is asserted if sensor successfully reset return this->int_status_.bit.por; } diff --git a/esphome/components/bmp581_base/bmp581_base.h b/esphome/components/bmp581_base/bmp581_base.h index d99c420272..1a73a91558 100644 --- a/esphome/components/bmp581_base/bmp581_base.h +++ b/esphome/components/bmp581_base/bmp581_base.h @@ -8,7 +8,8 @@ namespace esphome::bmp581_base { static const uint8_t BMP581_ASIC_ID = 0x50; // BMP581's ASIC chip ID (page 51 of datasheet) -static const uint8_t RESET_COMMAND = 0xB6; // Soft reset command +static const uint8_t BMP585_ASIC_ID = 0x51; +static const uint8_t RESET_COMMAND = 0xB6; // Soft reset command // BMP581 Register Addresses enum { @@ -87,6 +88,9 @@ class BMP581Component : public PollingComponent { virtual bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0; virtual bool bmp_write_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0; + // Interface activation function. Only used for SPI interface; no-op for I2C. + virtual void activate_interface() {} + sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *pressure_sensor_{nullptr}; diff --git a/esphome/components/bmp581_spi/__init__.py b/esphome/components/bmp581_spi/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/bmp581_spi/bmp581_spi.cpp b/esphome/components/bmp581_spi/bmp581_spi.cpp new file mode 100644 index 0000000000..01435880f0 --- /dev/null +++ b/esphome/components/bmp581_spi/bmp581_spi.cpp @@ -0,0 +1,73 @@ +#include +#include + +#include "bmp581_spi.h" +#include "esphome/components/bmp581_base/bmp581_base.h" +#include "esphome/components/spi/spi.h" + +namespace esphome::bmp581_spi { + +static const char *const TAG = "bmp581_spi"; + +// OR (|) register with BMP_SPI_READ for read +inline constexpr uint8_t BMP_SPI_READ = 0x80; + +// AND (&) register with BMP_SPI_WRITE for write +inline constexpr uint8_t BMP_SPI_WRITE = 0x7F; + +void BMP581SPIComponent::dump_config() { + BMP581Component::dump_config(); + LOG_SPI_DEVICE(this); +} + +void BMP581SPIComponent::setup() { + this->spi_setup(); + BMP581Component::setup(); +} + +void BMP581SPIComponent::activate_interface() { + // - forces the device into SPI mode using a dummy read + uint8_t dummy_read = 0; + this->bmp_read_byte(bmp581_base::BMP581_CHIP_ID, &dummy_read); +} + +// In SPI mode, only 7 bits of the register addresses are used; the MSB of register address is not used +// and replaced by a read/write bit (RW = ‘0’ for write and RW = ‘1’ for read). +// Example: address 0xF7 is accessed by using SPI register address 0x77. For write access, the byte +// 0x77 is transferred, for read access, the byte 0xF7 is transferred. +// The expressions BMP_SPI_READ (| with register) and BMP_SPI_WRITE (& with register) +// are defined for readability. +// https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp581-ds004.pdf + +bool BMP581SPIComponent::bmp_read_byte(uint8_t a_register, uint8_t *data) { + this->enable(); + this->transfer_byte(a_register | BMP_SPI_READ); + *data = this->transfer_byte(0); + this->disable(); + return true; +} + +bool BMP581SPIComponent::bmp_write_byte(uint8_t a_register, uint8_t data) { + this->enable(); + this->transfer_byte(a_register & BMP_SPI_WRITE); + this->transfer_byte(data); + this->disable(); + return true; +} + +bool BMP581SPIComponent::bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) { + this->enable(); + this->transfer_byte(a_register | BMP_SPI_READ); + this->read_array(data, len); + this->disable(); + return true; +} + +bool BMP581SPIComponent::bmp_write_bytes(uint8_t a_register, uint8_t *data, size_t len) { + this->enable(); + this->transfer_byte(a_register & BMP_SPI_WRITE); + this->write_array(data, len); + this->disable(); + return true; +} +} // namespace esphome::bmp581_spi diff --git a/esphome/components/bmp581_spi/bmp581_spi.h b/esphome/components/bmp581_spi/bmp581_spi.h new file mode 100644 index 0000000000..57f75588d5 --- /dev/null +++ b/esphome/components/bmp581_spi/bmp581_spi.h @@ -0,0 +1,24 @@ +#pragma once + +#include "esphome/components/bmp581_base/bmp581_base.h" +#include "esphome/components/spi/spi.h" + +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 { + public: + 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; + bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) override; + bool bmp_write_bytes(uint8_t a_register, uint8_t *data, size_t len) override; + void dump_config() override; + + protected: + void activate_interface() override; +}; + +} // namespace esphome::bmp581_spi diff --git a/esphome/components/bmp581_spi/sensor.py b/esphome/components/bmp581_spi/sensor.py new file mode 100644 index 0000000000..db0d0cd529 --- /dev/null +++ b/esphome/components/bmp581_spi/sensor.py @@ -0,0 +1,48 @@ +import logging + +import esphome.codegen as cg +from esphome.components import spi +from esphome.components.spi import CONF_SPI_MODE +import esphome.config_validation as cv + +from ..bmp581_base import CONFIG_SCHEMA_BASE, to_code_base + +AUTO_LOAD = ["bmp581_base"] +CODEOWNERS = ["@kahrendt", "@danielkent-net"] +DEPENDENCIES = ["spi"] + +_LOGGER = logging.getLogger(__name__) + +VALID_SPI_MODES = { + 0: "MODE0", + "0": "MODE0", + "MODE0": "MODE0", + 3: "MODE3", + "3": "MODE3", + "MODE3": "MODE3", +} + +bmp581_ns = cg.esphome_ns.namespace("bmp581_spi") +BMP581SPIComponent = bmp581_ns.class_( + "BMP581SPIComponent", cg.PollingComponent, spi.SPIDevice +) + + +def check_spi_mode(config): + spi_mode = config.get(CONF_SPI_MODE) + if spi_mode not in VALID_SPI_MODES: + raise cv.Invalid("BMP581 only supports SPI mode 0 or mode 3") + return config + + +CONFIG_SCHEMA = cv.All( + CONFIG_SCHEMA_BASE.extend(spi.spi_device_schema(default_mode="mode3")).extend( + {cv.GenerateID(): cv.declare_id(BMP581SPIComponent)} + ), + check_spi_mode, +) + + +async def to_code(config): + var = await to_code_base(config) + await spi.register_spi_device(var, config) diff --git a/esphome/components/bp1658cj/output.py b/esphome/components/bp1658cj/output.py index 023b6ecd1e..78cf717aba 100644 --- a/esphome/components/bp1658cj/output.py +++ b/esphome/components/bp1658cj/output.py @@ -14,7 +14,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( { cv.GenerateID(CONF_BP1658CJ_ID): cv.use_id(BP1658CJ), cv.Required(CONF_ID): cv.declare_id(Channel), - cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=65535), + cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=4), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/bthome_mithermometer/bthome_ble.cpp b/esphome/components/bthome_mithermometer/bthome_ble.cpp index 2b73d8735c..32278dbfbd 100644 --- a/esphome/components/bthome_mithermometer/bthome_ble.cpp +++ b/esphome/components/bthome_mithermometer/bthome_ble.cpp @@ -10,7 +10,12 @@ #ifdef USE_ESP32 +#include +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) +#include +#else #include "mbedtls/ccm.h" +#endif namespace esphome { namespace bthome_mithermometer { @@ -196,6 +201,37 @@ bool BTHomeMiThermometer::decrypt_bthome_payload_(const std::vector &da const uint8_t *ciphertext = data.data() + 1; const uint8_t *mic = data.data() + data.size() - BTHOME_MIC_SIZE; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + // PSA AEAD expects ciphertext + tag concatenated + // BLE advertisement max payload is 31 bytes, so this is always sufficient + static constexpr size_t MAX_CT_WITH_TAG = 32; + uint8_t ct_with_tag[MAX_CT_WITH_TAG]; + size_t ct_with_tag_size = ciphertext_size + BTHOME_MIC_SIZE; + memcpy(ct_with_tag, ciphertext, ciphertext_size); + memcpy(ct_with_tag + ciphertext_size, mic, BTHOME_MIC_SIZE); + + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + psa_set_key_type(&attributes, PSA_KEY_TYPE_AES); + psa_set_key_bits(&attributes, BTHOME_BINDKEY_SIZE * 8); + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DECRYPT); + psa_set_key_algorithm(&attributes, PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_CCM, BTHOME_MIC_SIZE)); + + mbedtls_svc_key_id_t key_id; + if (psa_import_key(&attributes, this->bindkey_, BTHOME_BINDKEY_SIZE, &key_id) != PSA_SUCCESS) { + ESP_LOGVV(TAG, "psa_import_key() failed."); + return false; + } + + size_t plaintext_length; + psa_status_t status = psa_aead_decrypt(key_id, PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_CCM, BTHOME_MIC_SIZE), + nonce.data(), nonce.size(), nullptr, 0, ct_with_tag, ct_with_tag_size, + payload.data(), ciphertext_size, &plaintext_length); + psa_destroy_key(key_id); + if (status != PSA_SUCCESS || plaintext_length != ciphertext_size) { + ESP_LOGVV(TAG, "BTHome decryption failed."); + return false; + } +#else mbedtls_ccm_context ctx; mbedtls_ccm_init(&ctx); @@ -213,6 +249,7 @@ bool BTHomeMiThermometer::decrypt_bthome_payload_(const std::vector &da ESP_LOGVV(TAG, "BTHome decryption failed (ret=%d).", ret); return false; } +#endif return true; } diff --git a/esphome/components/button/__init__.py b/esphome/components/button/__init__.py index 12d9ebaba6..2c19ea69b1 100644 --- a/esphome/components/button/__init__.py +++ b/esphome/components/button/__init__.py @@ -10,7 +10,6 @@ from esphome.const import ( CONF_ID, CONF_MQTT_ID, CONF_ON_PRESS, - CONF_TRIGGER_ID, CONF_WEB_SERVER, DEVICE_CLASS_EMPTY, DEVICE_CLASS_IDENTIFY, @@ -41,10 +40,6 @@ ButtonPtr = Button.operator("ptr") PressAction = button_ns.class_("PressAction", automation.Action) -ButtonPressTrigger = button_ns.class_( - "ButtonPressTrigger", automation.Trigger.template() -) - validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") @@ -55,11 +50,7 @@ _BUTTON_SCHEMA = ( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent), cv.Optional(CONF_DEVICE_CLASS): validate_device_class, - cv.Optional(CONF_ON_PRESS): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger), - } - ), + cv.Optional(CONF_ON_PRESS): automation.validate_automation({}), } ) ) @@ -88,11 +79,14 @@ def button_schema( return _BUTTON_SCHEMA.extend(schema) +_CALLBACK_AUTOMATIONS = ( + automation.CallbackAutomation(CONF_ON_PRESS, "add_on_press_callback"), +) + + @setup_entity("button") async def setup_button_core_(var, config): - for conf in config.get(CONF_ON_PRESS, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS) setup_device_class(config) diff --git a/esphome/components/button/button.cpp b/esphome/components/button/button.cpp index 8c06cfe59b..2a2a645132 100644 --- a/esphome/components/button/button.cpp +++ b/esphome/components/button/button.cpp @@ -16,10 +16,9 @@ void log_button(const char *tag, const char *prefix, const char *type, Button *o } void Button::press() { - ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str()); + ESP_LOGV(TAG, "'%s' Pressed.", this->get_name().c_str()); this->press_action(); this->press_callback_.call(); } -void Button::add_on_press_callback(std::function &&callback) { this->press_callback_.add(std::move(callback)); } } // namespace esphome::button diff --git a/esphome/components/button/button.h b/esphome/components/button/button.h index 0f7576a419..96e9107532 100644 --- a/esphome/components/button/button.h +++ b/esphome/components/button/button.h @@ -34,7 +34,9 @@ class Button : public EntityBase { * * @param callback The void() callback. */ - void add_on_press_callback(std::function &&callback); + template void add_on_press_callback(F &&callback) { + this->press_callback_.add(std::forward(callback)); + } protected: /** You should implement this virtual method if you want to create your own button. diff --git a/esphome/components/camera_encoder/__init__.py b/esphome/components/camera_encoder/__init__.py index 89181d27b4..3bbeae7835 100644 --- a/esphome/components/camera_encoder/__init__.py +++ b/esphome/components/camera_encoder/__init__.py @@ -50,7 +50,7 @@ async def to_code(config: ConfigType) -> None: buffer = cg.new_Pvariable(config[CONF_ENCODER_BUFFER_ID]) cg.add(buffer.set_buffer_size(config[CONF_BUFFER_SIZE])) if config[CONF_TYPE] == ESP32_CAMERA_ENCODER: - add_idf_component(name="espressif/esp32-camera", ref="2.1.1") + add_idf_component(name="espressif/esp32-camera", ref="2.1.6") cg.add_define("USE_ESP32_CAMERA_JPEG_ENCODER") var = cg.new_Pvariable( config[CONF_ID], diff --git a/esphome/components/canbus/__init__.py b/esphome/components/canbus/__init__.py index c94c8647a9..7d3bf78f49 100644 --- a/esphome/components/canbus/__init__.py +++ b/esphome/components/canbus/__init__.py @@ -161,7 +161,7 @@ async def canbus_action_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_CANBUS_ID]) - if can_id := config.get(CONF_CAN_ID): + if (can_id := config.get(CONF_CAN_ID)) is not None: can_id = await cg.templatable(can_id, args, cg.uint32) cg.add(var.set_can_id(can_id)) cg.add(var.set_use_extended_id(config[CONF_USE_EXTENDED_ID])) diff --git a/esphome/components/canbus/canbus.h b/esphome/components/canbus/canbus.h index f7b84111bd..420125e1d3 100644 --- a/esphome/components/canbus/canbus.h +++ b/esphome/components/canbus/canbus.h @@ -91,10 +91,7 @@ class Canbus : public Component { * - rtr If this is a remote transmission request * - data The message data */ - void add_callback( - std::function &data)> callback) { - this->callback_manager_.add(std::move(callback)); - } + template void add_callback(F &&callback) { this->callback_manager_.add(std::forward(callback)); } protected: template friend class CanbusSendAction; diff --git a/esphome/components/captive_portal/dns_server_esp32_idf.cpp b/esphome/components/captive_portal/dns_server_esp32_idf.cpp index 7b75f04241..56ad9f7176 100644 --- a/esphome/components/captive_portal/dns_server_esp32_idf.cpp +++ b/esphome/components/captive_portal/dns_server_esp32_idf.cpp @@ -100,8 +100,9 @@ void DNSServer::process_next_request() { &client_addr_len); if (len < 0) { - if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { - ESP_LOGE(TAG, "recvfrom failed: %d", errno); + const int err = errno; + if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) { + ESP_LOGE(TAG, "recvfrom failed: %d", err); } return; } diff --git a/esphome/components/cc1101/__init__.py b/esphome/components/cc1101/__init__.py index 2709290862..0feb384ac2 100644 --- a/esphome/components/cc1101/__init__.py +++ b/esphome/components/cc1101/__init__.py @@ -423,11 +423,10 @@ def _register_setter_actions(): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) data = config[CONF_VALUE] - if cg.is_template(data): - templ_ = await cg.templatable(data, args, _type) - cg.add(getattr(var, _setter)(templ_)) - else: - cg.add(getattr(var, _setter)(_map[data] if _map else data)) + if _map and not cg.is_template(data): + data = _map[data] + templ_ = await cg.templatable(data, args, _type) + cg.add(getattr(var, _setter)(templ_)) return var automation.register_action( diff --git a/esphome/components/cd74hc4067/__init__.py b/esphome/components/cd74hc4067/__init__.py index 9b69576b43..af6866df78 100644 --- a/esphome/components/cd74hc4067/__init__.py +++ b/esphome/components/cd74hc4067/__init__.py @@ -9,9 +9,7 @@ MULTI_CONF = True cd74hc4067_ns = cg.esphome_ns.namespace("cd74hc4067") -CD74HC4067Component = cd74hc4067_ns.class_( - "CD74HC4067Component", cg.Component, cg.PollingComponent -) +CD74HC4067Component = cd74hc4067_ns.class_("CD74HC4067Component", cg.Component) CONF_PIN_S0 = "pin_s0" CONF_PIN_S1 = "pin_s1" diff --git a/esphome/components/ch422g/ch422g.cpp b/esphome/components/ch422g/ch422g.cpp index 5f5e848c76..fc856cd563 100644 --- a/esphome/components/ch422g/ch422g.cpp +++ b/esphome/components/ch422g/ch422g.cpp @@ -124,12 +124,6 @@ bool CH422GComponent::write_outputs_() { float CH422GComponent::get_setup_priority() const { return setup_priority::IO; } -#ifdef USE_LOOP_PRIORITY -// Run our loop() method very early in the loop, so that we cache read values -// before other components call our digital_read() method. -float CH422GComponent::get_loop_priority() const { return 9.0f; } // Just after WIFI -#endif - void CH422GGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool CH422GGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) ^ this->inverted_; } diff --git a/esphome/components/ch422g/ch422g.h b/esphome/components/ch422g/ch422g.h index 1b96568209..6e6bdad64a 100644 --- a/esphome/components/ch422g/ch422g.h +++ b/esphome/components/ch422g/ch422g.h @@ -23,9 +23,6 @@ class CH422GComponent : public Component, public i2c::I2CDevice { void pin_mode(uint8_t pin, gpio::Flags flags); float get_setup_priority() const override; -#ifdef USE_LOOP_PRIORITY - float get_loop_priority() const override; -#endif void dump_config() override; protected: diff --git a/esphome/components/ch423/ch423.cpp b/esphome/components/ch423/ch423.cpp index 805d8df877..8424d130b4 100644 --- a/esphome/components/ch423/ch423.cpp +++ b/esphome/components/ch423/ch423.cpp @@ -129,12 +129,6 @@ bool CH423Component::write_outputs_() { float CH423Component::get_setup_priority() const { return setup_priority::IO; } -#ifdef USE_LOOP_PRIORITY -// Run our loop() method very early in the loop, so that we cache read values -// before other components call our digital_read() method. -float CH423Component::get_loop_priority() const { return 9.0f; } // Just after WIFI -#endif - void CH423GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool CH423GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) ^ this->inverted_; } diff --git a/esphome/components/ch423/ch423.h b/esphome/components/ch423/ch423.h index d85648a8f9..d384971a72 100644 --- a/esphome/components/ch423/ch423.h +++ b/esphome/components/ch423/ch423.h @@ -22,9 +22,6 @@ class CH423Component : public Component, public i2c::I2CDevice { void pin_mode(uint8_t pin, gpio::Flags flags); float get_setup_priority() const override; -#ifdef USE_LOOP_PRIORITY - float get_loop_priority() const override; -#endif void dump_config() override; protected: diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index 8cf5fa9b0c..df77fa5c1c 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -400,7 +400,7 @@ async def setup_climate_core_(var, config): ) ) is not None: cg.add( - mqtt_.set_custom_target_temperature_state_topic( + mqtt_.set_custom_target_temperature_low_state_topic( target_temperature_low_state_topic ) ) @@ -488,16 +488,16 @@ async def climate_control_to_code(config, action_id, template_arg, args): template_ = await cg.templatable(mode, args, ClimateMode) cg.add(var.set_mode(template_)) if (target_temp := config.get(CONF_TARGET_TEMPERATURE)) is not None: - template_ = await cg.templatable(target_temp, args, float) + template_ = await cg.templatable(target_temp, args, cg.float_) cg.add(var.set_target_temperature(template_)) if (target_temp_low := config.get(CONF_TARGET_TEMPERATURE_LOW)) is not None: - template_ = await cg.templatable(target_temp_low, args, float) + template_ = await cg.templatable(target_temp_low, args, cg.float_) cg.add(var.set_target_temperature_low(template_)) if (target_temp_high := config.get(CONF_TARGET_TEMPERATURE_HIGH)) is not None: - template_ = await cg.templatable(target_temp_high, args, float) + template_ = await cg.templatable(target_temp_high, args, cg.float_) cg.add(var.set_target_temperature_high(template_)) if (target_humidity := config.get(CONF_TARGET_HUMIDITY)) is not None: - template_ = await cg.templatable(target_humidity, args, float) + template_ = await cg.templatable(target_humidity, args, cg.float_) cg.add(var.set_target_humidity(template_)) if (fan_mode := config.get(CONF_FAN_MODE)) is not None: template_ = await cg.templatable(fan_mode, args, ClimateFanMode) diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 43d25effa3..e132497140 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -46,45 +46,45 @@ constexpr StringToUint8 CLIMATE_SWING_MODES_BY_STR[] = { void ClimateCall::perform() { this->parent_->control_callback_.call(*this); - ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); + ESP_LOGV(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); this->validate_(); if (this->mode_.has_value()) { const LogString *mode_s = climate_mode_to_string(*this->mode_); - ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(mode_s)); + ESP_LOGV(TAG, " Mode: %s", LOG_STR_ARG(mode_s)); } if (this->custom_fan_mode_ != nullptr) { this->fan_mode_.reset(); - ESP_LOGD(TAG, " Custom Fan: %s", this->custom_fan_mode_); + ESP_LOGV(TAG, " Custom Fan: %s", this->custom_fan_mode_); } if (this->fan_mode_.has_value()) { this->custom_fan_mode_ = nullptr; const LogString *fan_mode_s = climate_fan_mode_to_string(*this->fan_mode_); - ESP_LOGD(TAG, " Fan: %s", LOG_STR_ARG(fan_mode_s)); + ESP_LOGV(TAG, " Fan: %s", LOG_STR_ARG(fan_mode_s)); } if (this->custom_preset_ != nullptr) { this->preset_.reset(); - ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset_); + ESP_LOGV(TAG, " Custom Preset: %s", this->custom_preset_); } if (this->preset_.has_value()) { this->custom_preset_ = nullptr; const LogString *preset_s = climate_preset_to_string(*this->preset_); - ESP_LOGD(TAG, " Preset: %s", LOG_STR_ARG(preset_s)); + ESP_LOGV(TAG, " Preset: %s", LOG_STR_ARG(preset_s)); } if (this->swing_mode_.has_value()) { const LogString *swing_mode_s = climate_swing_mode_to_string(*this->swing_mode_); - ESP_LOGD(TAG, " Swing: %s", LOG_STR_ARG(swing_mode_s)); + ESP_LOGV(TAG, " Swing: %s", LOG_STR_ARG(swing_mode_s)); } if (this->target_temperature_.has_value()) { - ESP_LOGD(TAG, " Target Temperature: %.2f", *this->target_temperature_); + ESP_LOGV(TAG, " Target Temperature: %.2f", *this->target_temperature_); } if (this->target_temperature_low_.has_value()) { - ESP_LOGD(TAG, " Target Temperature Low: %.2f", *this->target_temperature_low_); + ESP_LOGV(TAG, " Target Temperature Low: %.2f", *this->target_temperature_low_); } if (this->target_temperature_high_.has_value()) { - ESP_LOGD(TAG, " Target Temperature High: %.2f", *this->target_temperature_high_); + ESP_LOGV(TAG, " Target Temperature High: %.2f", *this->target_temperature_high_); } if (this->target_humidity_.has_value()) { - ESP_LOGD(TAG, " Target Humidity: %.0f", *this->target_humidity_); + ESP_LOGV(TAG, " Target Humidity: %.0f", *this->target_humidity_); } this->parent_->control(*this); } @@ -356,14 +356,6 @@ ClimateCall &ClimateCall::set_swing_mode(optional swing_mode) return *this; } -void Climate::add_on_state_callback(std::function &&callback) { - this->state_callback_.add(std::move(callback)); -} - -void Climate::add_on_control_callback(std::function &&callback) { - this->control_callback_.add(std::move(callback)); -} - // Random 32bit value; If this changes existing restore preferences are invalidated static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL; @@ -375,7 +367,7 @@ optional Climate::restore_state_() { return recovered; } -void Climate::save_state_() { +void Climate::save_state_(const ClimateTraits &traits) { #if (defined(USE_ESP32) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0))) && \ !defined(CLANG_TIDY) #pragma GCC diagnostic ignored "-Wclass-memaccess" @@ -390,7 +382,6 @@ void Climate::save_state_() { #endif state.mode = this->mode; - auto traits = this->get_traits(); if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE | CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) { state.target_temperature_low = this->target_temperature_low; @@ -443,43 +434,43 @@ void Climate::save_state_() { } void Climate::publish_state() { - ESP_LOGD(TAG, "'%s' >>", this->name_.c_str()); + ESP_LOGV(TAG, "'%s' >>", this->name_.c_str()); auto traits = this->get_traits(); - ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode))); + ESP_LOGV(TAG, " Mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode))); if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION)) { - ESP_LOGD(TAG, " Action: %s", LOG_STR_ARG(climate_action_to_string(this->action))); + ESP_LOGV(TAG, " Action: %s", LOG_STR_ARG(climate_action_to_string(this->action))); } if (traits.get_supports_fan_modes() && this->fan_mode.has_value()) { - ESP_LOGD(TAG, " Fan Mode: %s", LOG_STR_ARG(climate_fan_mode_to_string(this->fan_mode.value()))); + ESP_LOGV(TAG, " Fan Mode: %s", LOG_STR_ARG(climate_fan_mode_to_string(this->fan_mode.value()))); } if (!traits.get_supported_custom_fan_modes().empty() && this->has_custom_fan_mode()) { - ESP_LOGD(TAG, " Custom Fan Mode: %s", this->custom_fan_mode_); + ESP_LOGV(TAG, " Custom Fan Mode: %s", this->custom_fan_mode_); } if (traits.get_supports_presets() && this->preset.has_value()) { - ESP_LOGD(TAG, " Preset: %s", LOG_STR_ARG(climate_preset_to_string(this->preset.value()))); + ESP_LOGV(TAG, " Preset: %s", LOG_STR_ARG(climate_preset_to_string(this->preset.value()))); } if (!traits.get_supported_custom_presets().empty() && this->has_custom_preset()) { - ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset_); + ESP_LOGV(TAG, " Custom Preset: %s", this->custom_preset_); } if (traits.get_supports_swing_modes()) { - ESP_LOGD(TAG, " Swing Mode: %s", LOG_STR_ARG(climate_swing_mode_to_string(this->swing_mode))); + ESP_LOGV(TAG, " Swing Mode: %s", LOG_STR_ARG(climate_swing_mode_to_string(this->swing_mode))); } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE)) { - ESP_LOGD(TAG, " Current Temperature: %.2f°C", this->current_temperature); + ESP_LOGV(TAG, " Current Temperature: %.2f°C", this->current_temperature); } if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE | CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) { - ESP_LOGD(TAG, " Target Temperature: Low: %.2f°C High: %.2f°C", this->target_temperature_low, + ESP_LOGV(TAG, " Target Temperature: Low: %.2f°C High: %.2f°C", this->target_temperature_low, this->target_temperature_high); } else { - ESP_LOGD(TAG, " Target Temperature: %.2f°C", this->target_temperature); + ESP_LOGV(TAG, " Target Temperature: %.2f°C", this->target_temperature); } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY)) { - ESP_LOGD(TAG, " Current Humidity: %.0f%%", this->current_humidity); + ESP_LOGV(TAG, " Current Humidity: %.0f%%", this->current_humidity); } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) { - ESP_LOGD(TAG, " Target Humidity: %.0f%%", this->target_humidity); + ESP_LOGV(TAG, " Target Humidity: %.0f%%", this->target_humidity); } // Send state to frontend @@ -488,11 +479,16 @@ void Climate::publish_state() { ControllerRegistry::notify_climate_update(this); #endif // Save state - this->save_state_(); + this->save_state_(traits); } ClimateTraits Climate::get_traits() { auto traits = this->traits(); + // Wire custom mode pointers from Climate-owned storage + if (this->supported_custom_fan_modes_) + traits.set_supported_custom_fan_modes_(this->supported_custom_fan_modes_); + if (this->supported_custom_presets_) + traits.set_supported_custom_presets_(this->supported_custom_presets_); #ifdef USE_CLIMATE_VISUAL_OVERRIDES if (!std::isnan(this->visual_min_temperature_override_)) { traits.set_visual_min_temperature(this->visual_min_temperature_override_); @@ -690,9 +686,8 @@ bool Climate::set_fan_mode_(ClimateFanMode mode) { } bool Climate::set_custom_fan_mode_(const char *mode, size_t len) { - auto traits = this->get_traits(); - return set_custom_mode(this->custom_fan_mode_, this->fan_mode, - traits.find_custom_fan_mode_(mode, len), this->has_custom_fan_mode()); + return set_custom_mode(this->custom_fan_mode_, this->fan_mode, this->find_custom_fan_mode_(mode, len), + this->has_custom_fan_mode()); } void Climate::clear_custom_fan_mode_() { this->custom_fan_mode_ = nullptr; } @@ -700,8 +695,7 @@ void Climate::clear_custom_fan_mode_() { this->custom_fan_mode_ = nullptr; } bool Climate::set_preset_(ClimatePreset preset) { return set_primary_mode(this->preset, this->custom_preset_, preset); } bool Climate::set_custom_preset_(const char *preset, size_t len) { - auto traits = this->get_traits(); - return set_custom_mode(this->custom_preset_, this->preset, traits.find_custom_preset_(preset, len), + return set_custom_mode(this->custom_preset_, this->preset, this->find_custom_preset_(preset, len), this->has_custom_preset()); } @@ -712,6 +706,10 @@ const char *Climate::find_custom_fan_mode_(const char *custom_fan_mode) { } const char *Climate::find_custom_fan_mode_(const char *custom_fan_mode, size_t len) { + if (this->supported_custom_fan_modes_) { + return vector_find(*this->supported_custom_fan_modes_, custom_fan_mode, len); + } + // Fallback for deprecated path: external components may set modes on ClimateTraits directly return this->get_traits().find_custom_fan_mode_(custom_fan_mode, len); } @@ -720,6 +718,10 @@ const char *Climate::find_custom_preset_(const char *custom_preset) { } const char *Climate::find_custom_preset_(const char *custom_preset, size_t len) { + if (this->supported_custom_presets_) { + return vector_find(*this->supported_custom_presets_, custom_preset, len); + } + // Fallback for deprecated path: external components may set modes on ClimateTraits directly return this->get_traits().find_custom_preset_(custom_preset, len); } diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index aa9ca91bc2..04f653a2b0 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -1,5 +1,6 @@ #pragma once +#include #include "esphome/core/component.h" #include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" @@ -192,7 +193,9 @@ class Climate : public EntityBase { * * @param callback The callback to call. */ - void add_on_state_callback(std::function &&callback); + template void add_on_state_callback(F &&callback) { + this->state_callback_.add(std::forward(callback)); + } /** * Add a callback for the climate device configuration; each time the configuration parameters of a climate device @@ -200,7 +203,9 @@ class Climate : public EntityBase { * * @param callback The callback to call. */ - void add_on_control_callback(std::function &&callback); + template void add_on_control_callback(F &&callback) { + this->control_callback_.add(std::forward(callback)); + } /** Make a climate device control call, this is used to control the climate device, see the ClimateCall description * for more info. @@ -230,6 +235,28 @@ class Climate : public EntityBase { void set_visual_max_humidity_override(float visual_max_humidity_override); #endif + /// Set the supported custom fan modes (stored on Climate, referenced by ClimateTraits). + void set_supported_custom_fan_modes(std::initializer_list modes) { + this->ensure_custom_fan_modes_().assign(modes.begin(), modes.end()); + } + void set_supported_custom_fan_modes(const std::vector &modes) { + this->ensure_custom_fan_modes_() = modes; + } + template void set_supported_custom_fan_modes(const char *const (&modes)[N]) { + this->ensure_custom_fan_modes_().assign(modes, modes + N); + } + + /// Set the supported custom presets (stored on Climate, referenced by ClimateTraits). + void set_supported_custom_presets(std::initializer_list presets) { + this->ensure_custom_presets_().assign(presets.begin(), presets.end()); + } + void set_supported_custom_presets(const std::vector &presets) { + this->ensure_custom_presets_() = presets; + } + template void set_supported_custom_presets(const char *const (&presets)[N]) { + this->ensure_custom_presets_().assign(presets, presets + N); + } + /// Check if a custom fan mode is currently active. bool has_custom_fan_mode() const { return this->custom_fan_mode_ != nullptr; } @@ -331,13 +358,15 @@ class Climate : public EntityBase { /** Internal method to save the state of the climate device to recover memory. This is automatically * called from publish_state() */ - void save_state_(); + void save_state_(const ClimateTraits &traits); + void save_state_() { this->save_state_(this->get_traits()); } void dump_traits_(const char *tag); LazyCallbackManager state_callback_{}; LazyCallbackManager control_callback_{}; ESPPreferenceObject rtc_; + #ifdef USE_CLIMATE_VISUAL_OVERRIDES float visual_min_temperature_override_{NAN}; float visual_max_temperature_override_{NAN}; @@ -348,16 +377,33 @@ class Climate : public EntityBase { #endif private: + /// Lazy-allocate custom mode vectors (never freed — entity lives forever). + std::vector &ensure_custom_fan_modes_() { + if (!this->supported_custom_fan_modes_) { + this->supported_custom_fan_modes_ = new std::vector(); // NOLINT + } + return *this->supported_custom_fan_modes_; + } + std::vector &ensure_custom_presets_() { + if (!this->supported_custom_presets_) { + this->supported_custom_presets_ = new std::vector(); // NOLINT + } + return *this->supported_custom_presets_; + } + + std::vector *supported_custom_fan_modes_{nullptr}; + std::vector *supported_custom_presets_{nullptr}; + /** The active custom fan mode (private - enforces use of safe setters). * - * Points to an entry in traits.supported_custom_fan_modes_ or nullptr. + * Points to an entry in supported_custom_fan_modes_ or nullptr. * Use get_custom_fan_mode() to read, set_custom_fan_mode_() to modify. */ const char *custom_fan_mode_{nullptr}; /** The active custom preset (private - enforces use of safe setters). * - * Points to an entry in traits.supported_custom_presets_ or nullptr. + * Points to an entry in supported_custom_presets_ or nullptr. * Use get_custom_preset() to read, set_custom_preset_() to modify. */ const char *custom_preset_{nullptr}; diff --git a/esphome/components/climate/climate_traits.cpp b/esphome/components/climate/climate_traits.cpp index 9bf2d9acd3..398e25f69e 100644 --- a/esphome/components/climate/climate_traits.cpp +++ b/esphome/components/climate/climate_traits.cpp @@ -2,6 +2,33 @@ namespace esphome::climate { +// Compat: shared empty vector for getters when no custom modes are set. +// Remove in 2026.11.0 when deprecated ClimateTraits setters are removed +// and getters can return const vector * instead of const vector &. +static const std::vector EMPTY_CUSTOM_MODES; // NOLINT + +const std::vector &ClimateTraits::get_supported_custom_fan_modes() const { + if (this->supported_custom_fan_modes_) { + return *this->supported_custom_fan_modes_; + } + // Compat: fall back to owned vector from deprecated setters. Remove in 2026.11.0. + if (!this->compat_custom_fan_modes_.empty()) { + return this->compat_custom_fan_modes_; + } + return EMPTY_CUSTOM_MODES; +} + +const std::vector &ClimateTraits::get_supported_custom_presets() const { + if (this->supported_custom_presets_) { + return *this->supported_custom_presets_; + } + // Compat: fall back to owned vector from deprecated setters. Remove in 2026.11.0. + if (!this->compat_custom_presets_.empty()) { + return this->compat_custom_presets_; + } + return EMPTY_CUSTOM_MODES; +} + int8_t ClimateTraits::get_target_temperature_accuracy_decimals() const { return step_to_accuracy_decimals(this->visual_target_temperature_step_); } diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index 80ef0854d5..082b2127a9 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -147,27 +147,45 @@ class ClimateTraits { void add_supported_fan_mode(ClimateFanMode mode) { this->supported_fan_modes_.insert(mode); } bool supports_fan_mode(ClimateFanMode fan_mode) const { return this->supported_fan_modes_.count(fan_mode); } bool get_supports_fan_modes() const { - return !this->supported_fan_modes_.empty() || !this->supported_custom_fan_modes_.empty(); + if (!this->supported_fan_modes_.empty()) { + return true; + } + // Same precedence as get_supported_custom_fan_modes() getter + if (this->supported_custom_fan_modes_) { + return !this->supported_custom_fan_modes_->empty(); + } + return !this->compat_custom_fan_modes_.empty(); // Compat: remove in 2026.11.0 } const ClimateFanModeMask &get_supported_fan_modes() const { return this->supported_fan_modes_; } + // Remove before 2026.11.0 + ESPDEPRECATED("Call set_supported_custom_fan_modes() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0") void set_supported_custom_fan_modes(std::initializer_list modes) { - this->supported_custom_fan_modes_ = modes; + // Compat: store in owned vector. Copies copy the vector (deprecated path still copies this vector). + this->compat_custom_fan_modes_ = modes; } + // Remove before 2026.11.0 + ESPDEPRECATED("Call set_supported_custom_fan_modes() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0") void set_supported_custom_fan_modes(const std::vector &modes) { - this->supported_custom_fan_modes_ = modes; + this->compat_custom_fan_modes_ = modes; } - template void set_supported_custom_fan_modes(const char *const (&modes)[N]) { - this->supported_custom_fan_modes_.assign(modes, modes + N); + // Remove before 2026.11.0 + template + ESPDEPRECATED("Call set_supported_custom_fan_modes() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0") + void set_supported_custom_fan_modes(const char *const (&modes)[N]) { + this->compat_custom_fan_modes_.assign(modes, modes + N); } // Deleted overloads to catch incorrect std::string usage at compile time with clear error messages void set_supported_custom_fan_modes(const std::vector &modes) = delete; void set_supported_custom_fan_modes(std::initializer_list modes) = delete; - const std::vector &get_supported_custom_fan_modes() const { return this->supported_custom_fan_modes_; } + // Compat: returns const ref with empty fallback. In 2026.11.0 change to return const vector *. + const std::vector &get_supported_custom_fan_modes() const; bool supports_custom_fan_mode(const char *custom_fan_mode) const { - return vector_contains(this->supported_custom_fan_modes_, custom_fan_mode); + return (this->supported_custom_fan_modes_ && + vector_contains(*this->supported_custom_fan_modes_, custom_fan_mode)) || + vector_contains(this->compat_custom_fan_modes_, custom_fan_mode); // Compat: remove in 2026.11.0 } bool supports_custom_fan_mode(const std::string &custom_fan_mode) const { return this->supports_custom_fan_mode(custom_fan_mode.c_str()); @@ -179,23 +197,32 @@ class ClimateTraits { bool get_supports_presets() const { return !this->supported_presets_.empty(); } const ClimatePresetMask &get_supported_presets() const { return this->supported_presets_; } + // Remove before 2026.11.0 + ESPDEPRECATED("Call set_supported_custom_presets() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0") void set_supported_custom_presets(std::initializer_list presets) { - this->supported_custom_presets_ = presets; + this->compat_custom_presets_ = presets; } + // Remove before 2026.11.0 + ESPDEPRECATED("Call set_supported_custom_presets() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0") void set_supported_custom_presets(const std::vector &presets) { - this->supported_custom_presets_ = presets; + this->compat_custom_presets_ = presets; } - template void set_supported_custom_presets(const char *const (&presets)[N]) { - this->supported_custom_presets_.assign(presets, presets + N); + // Remove before 2026.11.0 + template + ESPDEPRECATED("Call set_supported_custom_presets() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0") + void set_supported_custom_presets(const char *const (&presets)[N]) { + this->compat_custom_presets_.assign(presets, presets + N); } // Deleted overloads to catch incorrect std::string usage at compile time with clear error messages void set_supported_custom_presets(const std::vector &presets) = delete; void set_supported_custom_presets(std::initializer_list presets) = delete; - const std::vector &get_supported_custom_presets() const { return this->supported_custom_presets_; } + // Compat: returns const ref with empty fallback. In 2026.11.0 change to return const vector *. + const std::vector &get_supported_custom_presets() const; bool supports_custom_preset(const char *custom_preset) const { - return vector_contains(this->supported_custom_presets_, custom_preset); + return (this->supported_custom_presets_ && vector_contains(*this->supported_custom_presets_, custom_preset)) || + vector_contains(this->compat_custom_presets_, custom_preset); // Compat: remove in 2026.11.0 } bool supports_custom_preset(const std::string &custom_preset) const { return this->supports_custom_preset(custom_preset.c_str()); @@ -258,13 +285,25 @@ class ClimateTraits { } } + /// Set custom mode pointers (only Climate::get_traits() should call these). + void set_supported_custom_fan_modes_(const std::vector *modes) { + this->supported_custom_fan_modes_ = modes; + } + void set_supported_custom_presets_(const std::vector *presets) { + this->supported_custom_presets_ = presets; + } + /// Find and return the matching custom fan mode pointer from supported modes, or nullptr if not found /// This is protected as it's an implementation detail - use Climate::find_custom_fan_mode_() instead const char *find_custom_fan_mode_(const char *custom_fan_mode) const { return this->find_custom_fan_mode_(custom_fan_mode, strlen(custom_fan_mode)); } const char *find_custom_fan_mode_(const char *custom_fan_mode, size_t len) const { - return vector_find(this->supported_custom_fan_modes_, custom_fan_mode, len); + if (this->supported_custom_fan_modes_) { + return vector_find(*this->supported_custom_fan_modes_, custom_fan_mode, len); + } + // Compat: check owned vector from deprecated setters. Remove in 2026.11.0. + return vector_find(this->compat_custom_fan_modes_, custom_fan_mode, len); } /// Find and return the matching custom preset pointer from supported presets, or nullptr if not found @@ -273,7 +312,11 @@ class ClimateTraits { return this->find_custom_preset_(custom_preset, strlen(custom_preset)); } const char *find_custom_preset_(const char *custom_preset, size_t len) const { - return vector_find(this->supported_custom_presets_, custom_preset, len); + if (this->supported_custom_presets_) { + return vector_find(*this->supported_custom_presets_, custom_preset, len); + } + // Compat: check owned vector from deprecated setters. Remove in 2026.11.0. + return vector_find(this->compat_custom_presets_, custom_preset, len); } uint32_t feature_flags_{0}; @@ -289,16 +332,17 @@ class ClimateTraits { climate::ClimateSwingModeMask supported_swing_modes_; climate::ClimatePresetMask supported_presets_; - /** Custom mode storage using const char* pointers to eliminate std::string overhead. + /** Custom mode storage - pointers to vectors owned by the Climate base class. * - * Pointers must remain valid for the ClimateTraits lifetime. Safe patterns: - * - String literals: set_supported_custom_fan_modes({"Turbo", "Silent"}) - * - Static const data: static const char* MODE = "Eco"; - * - * Climate class setters validate pointers are from these vectors before storing. + * ClimateTraits does not own this data; Climate stores the vectors and + * get_traits() wires these pointers automatically. */ - std::vector supported_custom_fan_modes_; - std::vector supported_custom_presets_; + const std::vector *supported_custom_fan_modes_{nullptr}; + const std::vector *supported_custom_presets_{nullptr}; + // Compat: owned storage for deprecated setters. Copies copy the vector (copies include this vector). + // Remove in 2026.11.0. + std::vector compat_custom_fan_modes_; + std::vector compat_custom_presets_; }; } // namespace esphome::climate diff --git a/esphome/components/combination/combination.cpp b/esphome/components/combination/combination.cpp index 2f0bd26a02..b858eee4ee 100644 --- a/esphome/components/combination/combination.cpp +++ b/esphome/components/combination/combination.cpp @@ -4,8 +4,6 @@ #include "esphome/core/hal.h" #include -#include -#include namespace esphome { namespace combination { @@ -20,12 +18,12 @@ void CombinationComponent::log_config_(const LogString *combo_type) { void CombinationNoParameterComponent::add_source(Sensor *sensor) { this->sensors_.emplace_back(sensor); } -void CombinationOneParameterComponent::add_source(Sensor *sensor, std::function const &stddev) { - this->sensor_pairs_.emplace_back(sensor, stddev); +void CombinationOneParameterComponent::add_source(Sensor *sensor, std::function const &compute) { + this->sensor_sources_.push_back({sensor, compute, this}); } -void CombinationOneParameterComponent::add_source(Sensor *sensor, float stddev) { - this->add_source(sensor, std::function{[stddev](float x) -> float { return stddev; }}); +void CombinationOneParameterComponent::add_source(Sensor *sensor, float value) { + this->add_source(sensor, std::function{[value](float x) -> float { return value; }}); } void CombinationNoParameterComponent::log_source_sensors() { @@ -37,9 +35,8 @@ void CombinationNoParameterComponent::log_source_sensors() { void CombinationOneParameterComponent::log_source_sensors() { ESP_LOGCONFIG(TAG, " Source Sensors:"); - for (const auto &sensor : this->sensor_pairs_) { - auto &entity = *sensor.first; - ESP_LOGCONFIG(TAG, " - %s", entity.get_name().c_str()); + for (const auto &source : this->sensor_sources_) { + ESP_LOGCONFIG(TAG, " - %s", source.sensor->get_name().c_str()); } } @@ -62,9 +59,12 @@ void KalmanCombinationComponent::dump_config() { } void KalmanCombinationComponent::setup() { - for (const auto &sensor : this->sensor_pairs_) { - const auto stddev = sensor.second; - sensor.first->add_on_state_callback([this, stddev](float x) -> void { this->correct_(x, stddev(x)); }); + for (auto &source : this->sensor_sources_) { + // [&source] is safe: source refers to a FixedVector element that never reallocates, + // so the reference remains valid for the component's lifetime. + source.sensor->add_on_state_callback([&source](float x) -> void { + static_cast(source.parent)->correct_(x, source.compute(x)); + }); } } @@ -117,10 +117,10 @@ void KalmanCombinationComponent::correct_(float value, float stddev) { } void LinearCombinationComponent::setup() { - for (const auto &sensor : this->sensor_pairs_) { + for (auto &source : this->sensor_sources_) { // All sensor updates are deferred until the next loop. This avoids publishing the combined sensor's result // repeatedly in the same loop if multiple source senors update. - sensor.first->add_on_state_callback( + source.sensor->add_on_state_callback( [this](float value) -> void { this->defer("update", [this, value]() { this->handle_new_value(value); }); }); } } @@ -133,10 +133,10 @@ void LinearCombinationComponent::handle_new_value(float value) { float sum = 0.0; - for (const auto &sensor : this->sensor_pairs_) { - const float sensor_state = sensor.first->state; + for (const auto &source : this->sensor_sources_) { + const float sensor_state = source.sensor->state; if (std::isfinite(sensor_state)) { - sum += sensor_state * sensor.second(sensor_state); + sum += sensor_state * source.compute(sensor_state); } } diff --git a/esphome/components/combination/combination.h b/esphome/components/combination/combination.h index fb5e156da9..463eedc564 100644 --- a/esphome/components/combination/combination.h +++ b/esphome/components/combination/combination.h @@ -1,9 +1,10 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/helpers.h" #include "esphome/components/sensor/sensor.h" -#include +#include namespace esphome { namespace combination { @@ -41,14 +42,21 @@ class CombinationNoParameterComponent : public CombinationComponent { // Base class for opertions that require one parameter to compute the combination class CombinationOneParameterComponent : public CombinationComponent { public: - void add_source(Sensor *sensor, std::function const &stddev); - void add_source(Sensor *sensor, float stddev); + void set_source_count(size_t count) { this->sensor_sources_.init(count); } + void add_source(Sensor *sensor, std::function const &compute); + void add_source(Sensor *sensor, float value); - /// @brief Logs all source sensor's names in sensor_pairs_ + /// @brief Logs all source sensors' names in sensor_sources_ void log_source_sensors() override; protected: - std::vector>> sensor_pairs_; + struct SensorSource { + sensor::Sensor *sensor; + std::function compute; + CombinationOneParameterComponent *parent; + }; + + FixedVector sensor_sources_; }; class KalmanCombinationComponent : public CombinationOneParameterComponent { diff --git a/esphome/components/combination/sensor.py b/esphome/components/combination/sensor.py index 0204162e8d..327cedee1e 100644 --- a/esphome/components/combination/sensor.py +++ b/esphome/components/combination/sensor.py @@ -180,6 +180,9 @@ async def to_code(config): if proces_std_dev := config.get(CONF_PROCESS_STD_DEV): cg.add(var.set_process_std_dev(proces_std_dev)) + if config[CONF_TYPE] in (CONF_KALMAN, CONF_LINEAR): + cg.add(var.set_source_count(len(config[CONF_SOURCES]))) + for source_conf in config[CONF_SOURCES]: source = await cg.get_variable(source_conf[CONF_SOURCE]) if config[CONF_TYPE] == CONF_KALMAN: diff --git a/esphome/components/const/__init__.py b/esphome/components/const/__init__.py index 059bf3f26a..846d3fd883 100644 --- a/esphome/components/const/__init__.py +++ b/esphome/components/const/__init__.py @@ -3,6 +3,7 @@ CODEOWNERS = ["@esphome/core"] CONF_BYTE_ORDER = "byte_order" +CONF_CLIMATE_ID = "climate_id" BYTE_ORDER_LITTLE = "little_endian" BYTE_ORDER_BIG = "big_endian" @@ -12,17 +13,25 @@ CONF_DATA_BITS = "data_bits" CONF_DRAW_ROUNDING = "draw_rounding" CONF_ENABLED = "enabled" CONF_IGNORE_NOT_FOUND = "ignore_not_found" +CONF_LIBRETINY = "libretiny" CONF_ON_PACKET = "on_packet" CONF_ON_RECEIVE = "on_receive" CONF_ON_STATE_CHANGE = "on_state_change" CONF_PARITY = "parity" +CONF_RECEIVER_FREQUENCY = "receiver_frequency" CONF_REQUEST_HEADERS = "request_headers" CONF_ROWS = "rows" CONF_STOP_BITS = "stop_bits" CONF_USE_PSRAM = "use_psram" +CONF_VOLUME_INCREMENT = "volume_increment" +CONF_VOLUME_INITIAL = "volume_initial" +CONF_VOLUME_MAX = "volume_max" +CONF_VOLUME_MIN = "volume_min" ICON_CURRENT_DC = "mdi:current-dc" ICON_SOLAR_PANEL = "mdi:solar-panel" ICON_SOLAR_POWER = "mdi:solar-power" +KEY_METADATA = "metadata" + UNIT_AMPERE_HOUR = "Ah" diff --git a/esphome/components/copy/fan/copy_fan.cpp b/esphome/components/copy/fan/copy_fan.cpp index 14c600d71f..bdaa35c467 100644 --- a/esphome/components/copy/fan/copy_fan.cpp +++ b/esphome/components/copy/fan/copy_fan.cpp @@ -7,6 +7,12 @@ namespace copy { static const char *const TAG = "copy.fan"; void CopyFan::setup() { + // Copy preset modes once from source fan — stored on Fan base class + auto source_traits = source_->get_traits(); + if (source_traits.supports_preset_modes()) { + this->set_supported_preset_modes(source_traits.supported_preset_modes()); + } + source_->add_on_state_callback([this]() { this->copy_state_from_source_(); this->publish_state(); @@ -39,7 +45,8 @@ fan::FanTraits CopyFan::get_traits() { traits.set_speed(base.supports_speed()); traits.set_supported_speed_count(base.supported_speed_count()); traits.set_direction(base.supports_direction()); - traits.set_supported_preset_modes(base.supported_preset_modes()); + // Preset modes are set once in setup() and wired via wire_preset_modes_() + this->wire_preset_modes_(traits); return traits; } diff --git a/esphome/components/copy/lock/copy_lock.cpp b/esphome/components/copy/lock/copy_lock.cpp index 25bd8c33ef..c846954510 100644 --- a/esphome/components/copy/lock/copy_lock.cpp +++ b/esphome/components/copy/lock/copy_lock.cpp @@ -7,7 +7,7 @@ namespace copy { static const char *const TAG = "copy.lock"; void CopyLock::setup() { - source_->add_on_state_callback([this]() { this->publish_state(source_->state); }); + source_->add_on_state_callback([this](lock::LockState state) { this->publish_state(state); }); traits.set_assumed_state(source_->traits.get_assumed_state()); traits.set_requires_code(source_->traits.get_requires_code()); diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index c330241f4d..fdfca55f0f 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -300,16 +300,16 @@ async def cover_control_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) if (stop := config.get(CONF_STOP)) is not None: - template_ = await cg.templatable(stop, args, bool) + template_ = await cg.templatable(stop, args, cg.bool_) cg.add(var.set_stop(template_)) if (state := config.get(CONF_STATE)) is not None: - template_ = await cg.templatable(state, args, float) + template_ = await cg.templatable(state, args, cg.float_) cg.add(var.set_position(template_)) if (position := config.get(CONF_POSITION)) is not None: - template_ = await cg.templatable(position, args, float) + template_ = await cg.templatable(position, args, cg.float_) cg.add(var.set_position(template_)) if (tilt := config.get(CONF_TILT)) is not None: - template_ = await cg.templatable(tilt, args, float) + template_ = await cg.templatable(tilt, args, cg.float_) cg.add(var.set_tilt(template_)) return var diff --git a/esphome/components/cover/automation.h b/esphome/components/cover/automation.h index 12ec46725d..f121e5c2d6 100644 --- a/esphome/components/cover/automation.h +++ b/esphome/components/cover/automation.h @@ -105,17 +105,18 @@ template using CoverIsClosedCondition = CoverPositionCondition class CoverPositionTrigger : public Trigger<> { public: - CoverPositionTrigger(Cover *a_cover) { - a_cover->add_on_state_callback([this, a_cover]() { - if (a_cover->position != this->last_position_) { - this->last_position_ = a_cover->position; - if (a_cover->position == (OPEN ? COVER_OPEN : COVER_CLOSED)) + CoverPositionTrigger(Cover *a_cover) : cover_(a_cover) { + a_cover->add_on_state_callback([this]() { + if (this->cover_->position != this->last_position_) { + this->last_position_ = this->cover_->position; + if (this->cover_->position == (OPEN ? COVER_OPEN : COVER_CLOSED)) this->trigger(); } }); } protected: + Cover *cover_; float last_position_{NAN}; }; @@ -124,9 +125,9 @@ using CoverClosedTrigger = CoverPositionTrigger; template class CoverTrigger : public Trigger<> { public: - CoverTrigger(Cover *a_cover) { - a_cover->add_on_state_callback([this, a_cover]() { - auto current_op = a_cover->current_operation; + CoverTrigger(Cover *a_cover) : cover_(a_cover) { + a_cover->add_on_state_callback([this]() { + auto current_op = this->cover_->current_operation; if (current_op == OP) { if (!this->last_operation_.has_value() || this->last_operation_.value() != OP) { this->trigger(); @@ -137,6 +138,7 @@ template class CoverTrigger : public Trigger<> { } protected: + Cover *cover_; optional last_operation_{}; }; } // namespace esphome::cover diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index 0589aa2379..e98a555fe5 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -68,24 +68,24 @@ CoverCall &CoverCall::set_tilt(float tilt) { return *this; } void CoverCall::perform() { - ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); + ESP_LOGV(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); auto traits = this->parent_->get_traits(); this->validate_(); if (this->stop_) { - ESP_LOGD(TAG, " Command: STOP"); + ESP_LOGV(TAG, " Command: STOP"); } if (this->position_.has_value()) { if (traits.get_supports_position()) { - ESP_LOGD(TAG, " Position: %.0f%%", *this->position_ * 100.0f); + ESP_LOGV(TAG, " Position: %.0f%%", *this->position_ * 100.0f); } else { - ESP_LOGD(TAG, " Command: %s", LOG_STR_ARG(cover_command_to_str(*this->position_))); + ESP_LOGV(TAG, " Command: %s", LOG_STR_ARG(cover_command_to_str(*this->position_))); } } if (this->tilt_.has_value()) { - ESP_LOGD(TAG, " Tilt: %.0f%%", *this->tilt_ * 100.0f); + ESP_LOGV(TAG, " Tilt: %.0f%%", *this->tilt_ * 100.0f); } if (this->toggle_.has_value()) { - ESP_LOGD(TAG, " Command: TOGGLE"); + ESP_LOGV(TAG, " Command: TOGGLE"); } this->parent_->control(*this); } @@ -139,28 +139,27 @@ bool CoverCall::get_stop() const { return this->stop_; } CoverCall Cover::make_call() { return {this}; } -void Cover::add_on_state_callback(std::function &&f) { this->state_callback_.add(std::move(f)); } void Cover::publish_state(bool save) { this->position = clamp(this->position, 0.0f, 1.0f); this->tilt = clamp(this->tilt, 0.0f, 1.0f); - ESP_LOGD(TAG, "'%s' >>", this->name_.c_str()); + ESP_LOGV(TAG, "'%s' >>", this->name_.c_str()); auto traits = this->get_traits(); if (traits.get_supports_position()) { - ESP_LOGD(TAG, " Position: %.0f%%", this->position * 100.0f); + ESP_LOGV(TAG, " Position: %.0f%%", this->position * 100.0f); } else { if (this->position == COVER_OPEN) { - ESP_LOGD(TAG, " State: OPEN"); + ESP_LOGV(TAG, " State: OPEN"); } else if (this->position == COVER_CLOSED) { - ESP_LOGD(TAG, " State: CLOSED"); + ESP_LOGV(TAG, " State: CLOSED"); } else { - ESP_LOGD(TAG, " State: UNKNOWN"); + ESP_LOGV(TAG, " State: UNKNOWN"); } } if (traits.get_supports_tilt()) { - ESP_LOGD(TAG, " Tilt: %.0f%%", this->tilt * 100.0f); + ESP_LOGV(TAG, " Tilt: %.0f%%", this->tilt * 100.0f); } - ESP_LOGD(TAG, " Current Operation: %s", LOG_STR_ARG(cover_operation_to_str(this->current_operation))); + ESP_LOGV(TAG, " Current Operation: %s", LOG_STR_ARG(cover_operation_to_str(this->current_operation))); this->state_callback_.call(); #if defined(USE_COVER) && defined(USE_CONTROLLER_REGISTRY) diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index 8cf9aa092a..9a75e68487 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -125,7 +125,7 @@ class Cover : public EntityBase { /// Construct a new cover call used to control the cover. CoverCall make_call(); - void add_on_state_callback(std::function &&f); + template void add_on_state_callback(F &&f) { this->state_callback_.add(std::forward(f)); } /** Publish the current state of the cover. * diff --git a/esphome/components/cs5460a/sensor.py b/esphome/components/cs5460a/sensor.py index d2383bd01b..0c6ae0d821 100644 --- a/esphome/components/cs5460a/sensor.py +++ b/esphome/components/cs5460a/sensor.py @@ -12,6 +12,7 @@ from esphome.const import ( DEVICE_CLASS_CURRENT, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, UNIT_AMPERE, UNIT_VOLT, UNIT_WATT, @@ -82,16 +83,19 @@ CONFIG_SCHEMA = cv.All( unit_of_measurement=UNIT_VOLT, accuracy_decimals=0, device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CURRENT): sensor.sensor_schema( unit_of_measurement=UNIT_AMPERE, accuracy_decimals=1, device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_POWER): sensor.sensor_schema( unit_of_measurement=UNIT_WATT, accuracy_decimals=0, device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/cse7766/sensor.py b/esphome/components/cse7766/sensor.py index 6572a914aa..94ed66d7cc 100644 --- a/esphome/components/cse7766/sensor.py +++ b/esphome/components/cse7766/sensor.py @@ -32,52 +32,56 @@ DEPENDENCIES = ["uart"] cse7766_ns = cg.esphome_ns.namespace("cse7766") CSE7766Component = cse7766_ns.class_("CSE7766Component", cg.Component, uart.UARTDevice) -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(CSE7766Component), - cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( - unit_of_measurement=UNIT_VOLT, - accuracy_decimals=1, - device_class=DEVICE_CLASS_VOLTAGE, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_CURRENT): sensor.sensor_schema( - unit_of_measurement=UNIT_AMPERE, - accuracy_decimals=2, - device_class=DEVICE_CLASS_CURRENT, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_POWER): sensor.sensor_schema( - unit_of_measurement=UNIT_WATT, - accuracy_decimals=1, - device_class=DEVICE_CLASS_POWER, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_ENERGY): sensor.sensor_schema( - unit_of_measurement=UNIT_WATT_HOURS, - accuracy_decimals=3, - device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_TOTAL_INCREASING, - ), - cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema( - unit_of_measurement=UNIT_VOLT_AMPS, - accuracy_decimals=1, - device_class=DEVICE_CLASS_APPARENT_POWER, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema( - unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, - accuracy_decimals=1, - device_class=DEVICE_CLASS_REACTIVE_POWER, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema( - accuracy_decimals=2, - device_class=DEVICE_CLASS_POWER_FACTOR, - state_class=STATE_CLASS_MEASUREMENT, - ), - } -).extend(uart.UART_DEVICE_SCHEMA) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(CSE7766Component), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ENERGY): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT_AMPS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_REACTIVE_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema( + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER_FACTOR, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( "cse7766", baud_rate=4800, parity="EVEN", require_rx=True ) diff --git a/esphome/components/cst226/binary_sensor/__init__.py b/esphome/components/cst226/binary_sensor/__init__.py index d95f0d2b4d..324d794772 100644 --- a/esphome/components/cst226/binary_sensor/__init__.py +++ b/esphome/components/cst226/binary_sensor/__init__.py @@ -15,10 +15,14 @@ CST226Button = cst226_ns.class_( cg.Parented.template(CST226Touchscreen), ) -CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(CST226Button).extend( - { - cv.GenerateID(CONF_CST226_ID): cv.use_id(CST226Touchscreen), - } +CONFIG_SCHEMA = ( + binary_sensor.binary_sensor_schema(CST226Button) + .extend( + { + cv.GenerateID(CONF_CST226_ID): cv.use_id(CST226Touchscreen), + } + ) + .extend(cv.COMPONENT_SCHEMA) ) diff --git a/esphome/components/datetime/__init__.py b/esphome/components/datetime/__init__.py index 90835624bf..895ac4e243 100644 --- a/esphome/components/datetime/__init__.py +++ b/esphome/components/datetime/__init__.py @@ -204,7 +204,8 @@ async def datetime_date_set_to_code(config, action_id, template_arg, args): ("month", date_config[CONF_MONTH]), ("year", date_config[CONF_YEAR]), ) - cg.add(action_var.set_date(date_struct)) + template_ = await cg.templatable(date_struct, args, cg.ESPTime) + cg.add(action_var.set_date(template_)) return action_var @@ -236,7 +237,8 @@ async def datetime_time_set_to_code(config, action_id, template_arg, args): ("minute", time_config[CONF_MINUTE]), ("hour", time_config[CONF_HOUR]), ) - cg.add(action_var.set_time(time_struct)) + template_ = await cg.templatable(time_struct, args, cg.ESPTime) + cg.add(action_var.set_time(template_)) return action_var @@ -271,5 +273,6 @@ async def datetime_datetime_set_to_code(config, action_id, template_arg, args): ("month", datetime_config[CONF_MONTH]), ("year", datetime_config[CONF_YEAR]), ) - cg.add(action_var.set_datetime(datetime_struct)) + template_ = await cg.templatable(datetime_struct, args, cg.ESPTime) + cg.add(action_var.set_datetime(template_)) return action_var diff --git a/esphome/components/datetime/date_entity.cpp b/esphome/components/datetime/date_entity.cpp index 3ba488c0aa..997aec3f69 100644 --- a/esphome/components/datetime/date_entity.cpp +++ b/esphome/components/datetime/date_entity.cpp @@ -30,7 +30,7 @@ void DateEntity::publish_state() { return; } this->set_has_state(true); - ESP_LOGD(TAG, "'%s' >> %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_); + ESP_LOGV(TAG, "'%s' >> %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_); this->state_callback_.call(); #if defined(USE_DATETIME_DATE) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_date_update(this); @@ -83,16 +83,16 @@ void DateCall::validate_() { void DateCall::perform() { this->validate_(); - ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); + ESP_LOGV(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); if (this->year_.has_value()) { - ESP_LOGD(TAG, " Year: %d", *this->year_); + ESP_LOGV(TAG, " Year: %d", *this->year_); } if (this->month_.has_value()) { - ESP_LOGD(TAG, " Month: %d", *this->month_); + ESP_LOGV(TAG, " Month: %d", *this->month_); } if (this->day_.has_value()) { - ESP_LOGD(TAG, " Day: %d", *this->day_); + ESP_LOGV(TAG, " Day: %d", *this->day_); } this->parent_->control(*this); } diff --git a/esphome/components/datetime/datetime_base.h b/esphome/components/datetime/datetime_base.h index 1b0b3d5463..6c0a33c842 100644 --- a/esphome/components/datetime/datetime_base.h +++ b/esphome/components/datetime/datetime_base.h @@ -14,7 +14,9 @@ class DateTimeBase : public EntityBase { public: virtual ESPTime state_as_esptime() const = 0; - void add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } + template void add_on_state_callback(F &&callback) { + this->state_callback_.add(std::forward(callback)); + } #ifdef USE_TIME void set_rtc(time::RealTimeClock *rtc) { this->rtc_ = rtc; } @@ -31,9 +33,12 @@ class DateTimeBase : public EntityBase { class DateTimeStateTrigger : public Trigger { public: - explicit DateTimeStateTrigger(DateTimeBase *parent) { - parent->add_on_state_callback([this, parent]() { this->trigger(parent->state_as_esptime()); }); + explicit DateTimeStateTrigger(DateTimeBase *parent) : parent_(parent) { + parent->add_on_state_callback([this]() { this->trigger(this->parent_->state_as_esptime()); }); } + + protected: + DateTimeBase *parent_; }; } // namespace esphome::datetime diff --git a/esphome/components/datetime/datetime_entity.cpp b/esphome/components/datetime/datetime_entity.cpp index fa50271f04..a8e00d6eb3 100644 --- a/esphome/components/datetime/datetime_entity.cpp +++ b/esphome/components/datetime/datetime_entity.cpp @@ -45,7 +45,7 @@ void DateTimeEntity::publish_state() { return; } this->set_has_state(true); - ESP_LOGD(TAG, "'%s' >> %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_, this->month_, + ESP_LOGV(TAG, "'%s' >> %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_, this->month_, this->day_, this->hour_, this->minute_, this->second_); this->state_callback_.call(); #if defined(USE_DATETIME_DATETIME) && defined(USE_CONTROLLER_REGISTRY) @@ -127,25 +127,25 @@ void DateTimeCall::validate_() { void DateTimeCall::perform() { this->validate_(); - ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); + ESP_LOGV(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); if (this->year_.has_value()) { - ESP_LOGD(TAG, " Year: %d", *this->year_); + ESP_LOGV(TAG, " Year: %d", *this->year_); } if (this->month_.has_value()) { - ESP_LOGD(TAG, " Month: %d", *this->month_); + ESP_LOGV(TAG, " Month: %d", *this->month_); } if (this->day_.has_value()) { - ESP_LOGD(TAG, " Day: %d", *this->day_); + ESP_LOGV(TAG, " Day: %d", *this->day_); } if (this->hour_.has_value()) { - ESP_LOGD(TAG, " Hour: %d", *this->hour_); + ESP_LOGV(TAG, " Hour: %d", *this->hour_); } if (this->minute_.has_value()) { - ESP_LOGD(TAG, " Minute: %d", *this->minute_); + ESP_LOGV(TAG, " Minute: %d", *this->minute_); } if (this->second_.has_value()) { - ESP_LOGD(TAG, " Second: %d", *this->second_); + ESP_LOGV(TAG, " Second: %d", *this->second_); } this->parent_->control(*this); } diff --git a/esphome/components/datetime/time_entity.cpp b/esphome/components/datetime/time_entity.cpp index 74e43fbbe7..1cc9eaf2fb 100644 --- a/esphome/components/datetime/time_entity.cpp +++ b/esphome/components/datetime/time_entity.cpp @@ -26,7 +26,7 @@ void TimeEntity::publish_state() { return; } this->set_has_state(true); - ESP_LOGD(TAG, "'%s' >> %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_, this->second_); + ESP_LOGV(TAG, "'%s' >> %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_, this->second_); this->state_callback_.call(); #if defined(USE_DATETIME_TIME) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_time_update(this); @@ -52,15 +52,15 @@ void TimeCall::validate_() { void TimeCall::perform() { this->validate_(); - ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); + ESP_LOGV(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); if (this->hour_.has_value()) { - ESP_LOGD(TAG, " Hour: %d", *this->hour_); + ESP_LOGV(TAG, " Hour: %d", *this->hour_); } if (this->minute_.has_value()) { - ESP_LOGD(TAG, " Minute: %d", *this->minute_); + ESP_LOGV(TAG, " Minute: %d", *this->minute_); } if (this->second_.has_value()) { - ESP_LOGD(TAG, " Second: %d", *this->second_); + ESP_LOGV(TAG, " Second: %d", *this->second_); } this->parent_->control(*this); } diff --git a/esphome/components/debug/debug_esp32.cpp b/esphome/components/debug/debug_esp32.cpp index c9df4fdf21..2e04090749 100644 --- a/esphome/components/debug/debug_esp32.cpp +++ b/esphome/components/debug/debug_esp32.cpp @@ -5,6 +5,7 @@ #include "esphome/core/log.h" #include "esphome/core/hal.h" #include +#include #include #include @@ -48,7 +49,8 @@ static const size_t REBOOT_MAX_LEN = 24; void DebugComponent::on_shutdown() { auto *component = App.get_current_component(); char buffer[REBOOT_MAX_LEN]{}; - auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name())); + auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, + fnv1_hash_extend(fnv1_hash(REBOOT_KEY), App.get_name().c_str())); if (component != nullptr) { strncpy(buffer, LOG_STR_ARG(component->get_component_log_str()), REBOOT_MAX_LEN - 1); buffer[REBOOT_MAX_LEN - 1] = '\0'; @@ -65,7 +67,8 @@ const char *DebugComponent::get_reset_reason_(std::spanmake_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name())); + auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, + fnv1_hash_extend(fnv1_hash(REBOOT_KEY), App.get_name().c_str())); char reboot_source[REBOOT_MAX_LEN]{}; if (pref.load(&reboot_source)) { reboot_source[REBOOT_MAX_LEN - 1] = '\0'; @@ -82,32 +85,74 @@ const char *DebugComponent::get_reset_reason_(std::span= ESP_IDF_VERSION_VAL(6, 0, 0) static const char *const WAKEUP_CAUSES[] = { - "undefined", - "undefined", - "external signal using RTC_IO", - "external signal using RTC_CNTL", - "timer", - "touchpad", - "ULP program", - "GPIO", - "UART", - "WIFI", - "COCPU int", - "COCPU crash", - "BT", + "undefined", // ESP_SLEEP_WAKEUP_UNDEFINED (0) + "undefined", // ESP_SLEEP_WAKEUP_ALL (1) + "external signal using RTC_IO", // ESP_SLEEP_WAKEUP_EXT0 (2) + "external signal using RTC_CNTL", // ESP_SLEEP_WAKEUP_EXT1 (3) + "timer", // ESP_SLEEP_WAKEUP_TIMER (4) + "touchpad", // ESP_SLEEP_WAKEUP_TOUCHPAD (5) + "ULP program", // ESP_SLEEP_WAKEUP_ULP (6) + "GPIO", // ESP_SLEEP_WAKEUP_GPIO (7) + "UART", // ESP_SLEEP_WAKEUP_UART (8) + "UART1", // ESP_SLEEP_WAKEUP_UART1 (9) + "UART2", // ESP_SLEEP_WAKEUP_UART2 (10) + "WIFI", // ESP_SLEEP_WAKEUP_WIFI (11) + "COCPU int", // ESP_SLEEP_WAKEUP_COCPU (12) + "COCPU crash", // ESP_SLEEP_WAKEUP_COCPU_TRAP_TRIG (13) + "BT", // ESP_SLEEP_WAKEUP_BT (14) + "VAD", // ESP_SLEEP_WAKEUP_VAD (15) + "VBAT under voltage", // ESP_SLEEP_WAKEUP_VBAT_UNDER_VOLT (16) }; +#else +static const char *const WAKEUP_CAUSES[] = { + "undefined", // ESP_SLEEP_WAKEUP_UNDEFINED (0) + "undefined", // ESP_SLEEP_WAKEUP_ALL (1) + "external signal using RTC_IO", // ESP_SLEEP_WAKEUP_EXT0 (2) + "external signal using RTC_CNTL", // ESP_SLEEP_WAKEUP_EXT1 (3) + "timer", // ESP_SLEEP_WAKEUP_TIMER (4) + "touchpad", // ESP_SLEEP_WAKEUP_TOUCHPAD (5) + "ULP program", // ESP_SLEEP_WAKEUP_ULP (6) + "GPIO", // ESP_SLEEP_WAKEUP_GPIO (7) + "UART", // ESP_SLEEP_WAKEUP_UART (8) + "WIFI", // ESP_SLEEP_WAKEUP_WIFI (9) + "COCPU int", // ESP_SLEEP_WAKEUP_COCPU (10) + "COCPU crash", // ESP_SLEEP_WAKEUP_COCPU_TRAP_TRIG (11) + "BT", // ESP_SLEEP_WAKEUP_BT (12) +}; +#endif const char *DebugComponent::get_wakeup_cause_(std::span buffer) { - const char *wake_reason; - unsigned reason = esp_sleep_get_wakeup_cause(); - if (reason < sizeof(WAKEUP_CAUSES) / sizeof(WAKEUP_CAUSES[0])) { - wake_reason = WAKEUP_CAUSES[reason]; - } else { - wake_reason = "unknown source"; + static constexpr auto NUM_CAUSES = sizeof(WAKEUP_CAUSES) / sizeof(WAKEUP_CAUSES[0]); +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + // IDF 6.0+ returns a bitmap of all wakeup sources + uint32_t causes = esp_sleep_get_wakeup_causes(); + if (causes == 0) { + return WAKEUP_CAUSES[0]; // "undefined" } - // Return the static string directly - no need to copy to buffer - return wake_reason; + char *p = buffer.data(); + char *end = p + buffer.size(); + *p = '\0'; + const char *sep = ""; + for (unsigned i = 0; i < NUM_CAUSES && p < end; i++) { + if (causes & (1U << i)) { + size_t needed = strlen(sep) + strlen(WAKEUP_CAUSES[i]); + if (p + needed >= end) { + break; + } + p += snprintf(p, end - p, "%s%s", sep, WAKEUP_CAUSES[i]); + sep = ", "; + } + } + return buffer.data(); +#else + unsigned reason = esp_sleep_get_wakeup_cause(); + if (reason < NUM_CAUSES) { + return WAKEUP_CAUSES[reason]; + } + return "unknown source"; +#endif } void DebugComponent::log_partition_info_() { diff --git a/esphome/components/debug/debug_zephyr.cpp b/esphome/components/debug/debug_zephyr.cpp index bf87b7ae3d..d1580dae80 100644 --- a/esphome/components/debug/debug_zephyr.cpp +++ b/esphome/components/debug/debug_zephyr.cpp @@ -91,6 +91,49 @@ void DebugComponent::log_partition_info_() { flash_area_foreach(fa_cb, nullptr); } +#ifdef ESPHOME_LOG_HAS_VERBOSE +// Check if an nRF peripheral's ENABLE register indicates it is enabled. +// periph: peripheral register prefix (e.g. USBD, UARTE, SPI) +// reg: register block pointer (e.g. NRF_USBD, NRF_UARTE0) +#define NRF_PERIPH_ENABLED(periph, reg) \ + YESNO(((reg)->ENABLE & periph##_ENABLE_ENABLE_Msk) == (periph##_ENABLE_ENABLE_Enabled << periph##_ENABLE_ENABLE_Pos)) + +static void log_peripherals_info() { + // most peripherals are enabled only when in use so ESP_LOGV is enough + ESP_LOGV(TAG, "Peripherals status:"); + ESP_LOGV(TAG, " USBD: %-3s| UARTE0: %-3s| UARTE1: %-3s| UART0: %-3s", // + NRF_PERIPH_ENABLED(USBD, NRF_USBD), NRF_PERIPH_ENABLED(UARTE, NRF_UARTE0), + NRF_PERIPH_ENABLED(UARTE, NRF_UARTE1), NRF_PERIPH_ENABLED(UART, NRF_UART0)); + ESP_LOGV(TAG, " TWIS0: %-3s| TWIS1: %-3s| TWIM0: %-3s| TWIM1: %-3s", // + NRF_PERIPH_ENABLED(TWIS, NRF_TWIS0), NRF_PERIPH_ENABLED(TWIS, NRF_TWIS1), + NRF_PERIPH_ENABLED(TWIM, NRF_TWIM0), NRF_PERIPH_ENABLED(TWIM, NRF_TWIM1)); + ESP_LOGV(TAG, " TWI0: %-3s| TWI1: %-3s| COMP: %-3s| CCM: %-3s", // + NRF_PERIPH_ENABLED(TWI, NRF_TWI0), NRF_PERIPH_ENABLED(TWI, NRF_TWI1), NRF_PERIPH_ENABLED(COMP, NRF_COMP), + NRF_PERIPH_ENABLED(CCM, NRF_CCM)); + ESP_LOGV(TAG, " PDM: %-3s| SPIS0: %-3s| SPIS1: %-3s| SPIS2: %-3s", // + NRF_PERIPH_ENABLED(PDM, NRF_PDM), NRF_PERIPH_ENABLED(SPIS, NRF_SPIS0), NRF_PERIPH_ENABLED(SPIS, NRF_SPIS1), + NRF_PERIPH_ENABLED(SPIS, NRF_SPIS2)); + ESP_LOGV(TAG, " SPIM0: %-3s| SPIM1: %-3s| SPIM2: %-3s| SPIM3: %-3s", // + NRF_PERIPH_ENABLED(SPIM, NRF_SPIM0), NRF_PERIPH_ENABLED(SPIM, NRF_SPIM1), + NRF_PERIPH_ENABLED(SPIM, NRF_SPIM2), NRF_PERIPH_ENABLED(SPIM, NRF_SPIM3)); + ESP_LOGV(TAG, " SPI0: %-3s| SPI1: %-3s| SPI2: %-3s| SAADC: %-3s", // + NRF_PERIPH_ENABLED(SPI, NRF_SPI0), NRF_PERIPH_ENABLED(SPI, NRF_SPI1), NRF_PERIPH_ENABLED(SPI, NRF_SPI2), + NRF_PERIPH_ENABLED(SAADC, NRF_SAADC)); + ESP_LOGV(TAG, " QSPI: %-3s| QDEC: %-3s| LPCOMP: %-3s| I2S: %-3s", // + NRF_PERIPH_ENABLED(QSPI, NRF_QSPI), NRF_PERIPH_ENABLED(QDEC, NRF_QDEC), + NRF_PERIPH_ENABLED(LPCOMP, NRF_LPCOMP), NRF_PERIPH_ENABLED(I2S, NRF_I2S)); + ESP_LOGV(TAG, " PWM0: %-3s| PWM1: %-3s| PWM2: %-3s| PWM3: %-3s", // + NRF_PERIPH_ENABLED(PWM, NRF_PWM0), NRF_PERIPH_ENABLED(PWM, NRF_PWM1), NRF_PERIPH_ENABLED(PWM, NRF_PWM2), + NRF_PERIPH_ENABLED(PWM, NRF_PWM3)); + ESP_LOGV(TAG, " AAR: %-3s| QSPI deep power-down:%-3s| CRYPTOCELL: %-3s", NRF_PERIPH_ENABLED(AAR, NRF_AAR), + YESNO((NRF_QSPI->IFCONFIG0 & QSPI_IFCONFIG0_DPMENABLE_Msk) == + (QSPI_IFCONFIG0_DPMENABLE_Enable << QSPI_IFCONFIG0_DPMENABLE_Pos)), + YESNO((NRF_CRYPTOCELL->ENABLE & CRYPTOCELL_ENABLE_ENABLE_Msk) == + (CRYPTOCELL_ENABLE_ENABLE_Enabled << CRYPTOCELL_ENABLE_ENABLE_Pos))); +} +#undef NRF_PERIPH_ENABLED +#endif + static const char *regout0_to_str(uint32_t value) { switch (value) { case (UICR_REGOUT0_VOUT_DEFAULT): @@ -354,7 +397,9 @@ size_t DebugComponent::get_device_info_(std::span }; ESP_LOGD(TAG, " NRFFW %s", uicr(NRF_UICR->NRFFW, 13).c_str()); ESP_LOGD(TAG, " NRFHW %s", uicr(NRF_UICR->NRFHW, 12).c_str()); - +#ifdef ESPHOME_LOG_HAS_VERBOSE + log_peripherals_info(); +#endif return pos; } diff --git a/esphome/components/debug/sensor.py b/esphome/components/debug/sensor.py index 0a716d666e..a018ce5c3b 100644 --- a/esphome/components/debug/sensor.py +++ b/esphome/components/debug/sensor.py @@ -8,12 +8,14 @@ from esphome.const import ( CONF_FRAGMENTATION, CONF_FREE, CONF_LOOP_TIME, + DEVICE_CLASS_FREQUENCY, ENTITY_CATEGORY_DIAGNOSTIC, ICON_COUNTER, ICON_TIMER, PLATFORM_BK72XX, PLATFORM_LN882X, PLATFORM_RTL87XX, + STATE_CLASS_MEASUREMENT, UNIT_BYTES, UNIT_HERTZ, UNIT_MILLISECOND, @@ -38,12 +40,14 @@ CONFIG_SCHEMA = { icon=ICON_COUNTER, accuracy_decimals=0, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BLOCK): sensor.sensor_schema( unit_of_measurement=UNIT_BYTES, icon=ICON_COUNTER, accuracy_decimals=0, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_FRAGMENTATION): cv.All( cv.Any( @@ -59,6 +63,7 @@ CONFIG_SCHEMA = { icon=ICON_COUNTER, accuracy_decimals=1, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + state_class=STATE_CLASS_MEASUREMENT, ), ), cv.Optional(CONF_MIN_FREE): cv.All( @@ -72,6 +77,7 @@ CONFIG_SCHEMA = { icon=ICON_COUNTER, accuracy_decimals=0, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + state_class=STATE_CLASS_MEASUREMENT, ), ), cv.Optional(CONF_LOOP_TIME): sensor.sensor_schema( @@ -79,6 +85,7 @@ CONFIG_SCHEMA = { icon=ICON_TIMER, accuracy_decimals=0, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PSRAM): cv.All( cv.only_on_esp32, @@ -88,6 +95,7 @@ CONFIG_SCHEMA = { icon=ICON_COUNTER, accuracy_decimals=0, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + state_class=STATE_CLASS_MEASUREMENT, ), ), cv.Optional(CONF_CPU_FREQUENCY): cv.All( @@ -95,7 +103,9 @@ CONFIG_SCHEMA = { unit_of_measurement=UNIT_HERTZ, icon="mdi:speedometer", accuracy_decimals=0, + device_class=DEVICE_CLASS_FREQUENCY, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + state_class=STATE_CLASS_MEASUREMENT, ), ), } diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index 4098fd3fb8..16329bb0fa 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -266,8 +266,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_WAKEUP_PIN): validate_wakeup_pin, cv.Optional(CONF_WAKEUP_PIN_MODE): cv.All( cv.only_on([PLATFORM_ESP32, PLATFORM_BK72XX]), - cv.enum(WAKEUP_PIN_MODES), - upper=True, + cv.enum(WAKEUP_PIN_MODES, upper=True), ), cv.Optional(CONF_ESP32_EXT1_WAKEUP): cv.All( cv.only_on_esp32, diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index 0511518419..3dd1b70930 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -40,12 +40,6 @@ void DeepSleepComponent::loop() { this->begin_sleep(); } -#ifdef USE_LOOP_PRIORITY -float DeepSleepComponent::get_loop_priority() const { - return -100.0f; // run after everything else is ready -} -#endif - void DeepSleepComponent::set_sleep_duration(uint32_t time_ms) { this->sleep_duration_ = uint64_t(time_ms) * 1000; } void DeepSleepComponent::set_run_duration(uint32_t time_ms) { this->run_duration_ = time_ms; } diff --git a/esphome/components/deep_sleep/deep_sleep_component.h b/esphome/components/deep_sleep/deep_sleep_component.h index 14713d51a1..9090f91876 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.h +++ b/esphome/components/deep_sleep/deep_sleep_component.h @@ -113,9 +113,6 @@ class DeepSleepComponent : public Component { void setup() override; void dump_config() override; void loop() override; -#ifdef USE_LOOP_PRIORITY - float get_loop_priority() const override; -#endif float get_setup_priority() const override; /// Helper to enter deep sleep mode diff --git a/esphome/components/deep_sleep/deep_sleep_esp32.cpp b/esphome/components/deep_sleep/deep_sleep_esp32.cpp index 79c34f627a..4f4d262d30 100644 --- a/esphome/components/deep_sleep/deep_sleep_esp32.cpp +++ b/esphome/components/deep_sleep/deep_sleep_esp32.cpp @@ -3,6 +3,7 @@ #include "driver/gpio.h" #include "deep_sleep_component.h" #include "esphome/core/log.h" +#include namespace esphome { namespace deep_sleep { @@ -26,7 +27,7 @@ namespace deep_sleep { // - ext0: Single pin wakeup using RTC GPIO (esp_sleep_enable_ext0_wakeup) // - ext1: Multiple pin wakeup (esp_sleep_enable_ext1_wakeup) // - Touch: Touch pad wakeup (esp_sleep_enable_touchpad_wakeup) -// - GPIO wakeup: GPIO wakeup for RTC pins (esp_deep_sleep_enable_gpio_wakeup) +// - GPIO wakeup: GPIO wakeup for RTC pins static const char *const TAG = "deep_sleep"; @@ -135,8 +136,13 @@ void DeepSleepComponent::deep_sleep_() { } // Internal pullup/pulldown resistors are enabled automatically, when // ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS is set (by default it is) - esp_deep_sleep_enable_gpio_wakeup(1 << this->wakeup_pin_->get_pin(), +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + esp_sleep_enable_gpio_wakeup_on_hp_periph_powerdown(1ULL << this->wakeup_pin_->get_pin(), + static_cast(level)); +#else + esp_deep_sleep_enable_gpio_wakeup(1ULL << this->wakeup_pin_->get_pin(), static_cast(level)); +#endif } #endif diff --git a/esphome/components/demo/demo_climate.h b/esphome/components/demo/demo_climate.h index c5f07ac114..c6d328b1bc 100644 --- a/esphome/components/demo/demo_climate.h +++ b/esphome/components/demo/demo_climate.h @@ -16,6 +16,19 @@ class DemoClimate : public climate::Climate, public Component { public: void set_type(DemoClimateType type) { type_ = type; } void setup() override { + // Set custom modes once during setup — stored on Climate base class, wired via get_traits() + switch (type_) { + case DemoClimateType::TYPE_1: + break; + case DemoClimateType::TYPE_2: + this->set_supported_custom_fan_modes({"Auto Low", "Auto High"}); + this->set_supported_custom_presets({"My Preset"}); + break; + case DemoClimateType::TYPE_3: + this->set_supported_custom_fan_modes({"Auto Low", "Auto High"}); + break; + } + // Set initial state switch (type_) { case DemoClimateType::TYPE_1: this->current_temperature = 20.0; @@ -105,14 +118,13 @@ class DemoClimate : public climate::Climate, public Component { climate::CLIMATE_FAN_DIFFUSE, climate::CLIMATE_FAN_QUIET, }); - traits.set_supported_custom_fan_modes({"Auto Low", "Auto High"}); + // Custom fan modes and presets are set once in setup() traits.set_supported_swing_modes({ climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_HORIZONTAL, }); - traits.set_supported_custom_presets({"My Preset"}); break; case DemoClimateType::TYPE_3: traits.add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE | @@ -123,7 +135,7 @@ class DemoClimate : public climate::Climate, public Component { climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_HEAT_COOL, }); - traits.set_supported_custom_fan_modes({"Auto Low", "Auto High"}); + // Custom fan modes are set once in setup() traits.set_supported_swing_modes({ climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL, diff --git a/esphome/components/dfplayer/__init__.py b/esphome/components/dfplayer/__init__.py index 9df108c9c0..7796f5d891 100644 --- a/esphome/components/dfplayer/__init__.py +++ b/esphome/components/dfplayer/__init__.py @@ -2,16 +2,13 @@ from esphome import automation import esphome.codegen as cg from esphome.components import uart import esphome.config_validation as cv -from esphome.const import CONF_DEVICE, CONF_FILE, CONF_ID, CONF_TRIGGER_ID, CONF_VOLUME +from esphome.const import CONF_DEVICE, CONF_FILE, CONF_ID, CONF_VOLUME DEPENDENCIES = ["uart"] CODEOWNERS = ["@glmnet"] dfplayer_ns = cg.esphome_ns.namespace("dfplayer") DFPlayer = dfplayer_ns.class_("DFPlayer", cg.Component) -DFPlayerFinishedPlaybackTrigger = dfplayer_ns.class_( - "DFPlayerFinishedPlaybackTrigger", automation.Trigger.template() -) DFPlayerIsPlayingCondition = dfplayer_ns.class_( "DFPlayerIsPlayingCondition", automation.Condition ) @@ -58,13 +55,7 @@ CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(DFPlayer), - cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - DFPlayerFinishedPlaybackTrigger - ), - } - ), + cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation({}), } ).extend(uart.UART_DEVICE_SCHEMA) ) @@ -73,14 +64,19 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( ) +_CALLBACK_AUTOMATIONS = ( + automation.CallbackAutomation( + CONF_ON_FINISHED_PLAYBACK, "add_on_finished_playback_callback" + ), +) + + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await uart.register_uart_device(var, config) - for conf in config.get(CONF_ON_FINISHED_PLAYBACK, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS) @automation.register_action( @@ -130,7 +126,7 @@ async def dfplayer_previous_to_code(config, action_id, template_arg, args): async def dfplayer_play_mp3_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) - template_ = await cg.templatable(config[CONF_FILE], args, float) + template_ = await cg.templatable(config[CONF_FILE], args, cg.uint16) cg.add(var.set_file(template_)) return var @@ -151,10 +147,10 @@ async def dfplayer_play_mp3_to_code(config, action_id, template_arg, args): async def dfplayer_play_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) - template_ = await cg.templatable(config[CONF_FILE], args, float) + template_ = await cg.templatable(config[CONF_FILE], args, cg.uint16) cg.add(var.set_file(template_)) if CONF_LOOP in config: - template_ = await cg.templatable(config[CONF_LOOP], args, float) + template_ = await cg.templatable(config[CONF_LOOP], args, cg.bool_) cg.add(var.set_loop(template_)) return var @@ -175,13 +171,13 @@ async def dfplayer_play_to_code(config, action_id, template_arg, args): async def dfplayer_play_folder_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) - template_ = await cg.templatable(config[CONF_FOLDER], args, float) + template_ = await cg.templatable(config[CONF_FOLDER], args, cg.uint16) cg.add(var.set_folder(template_)) if CONF_FILE in config: - template_ = await cg.templatable(config[CONF_FILE], args, float) + template_ = await cg.templatable(config[CONF_FILE], args, cg.uint16) cg.add(var.set_file(template_)) if CONF_LOOP in config: - template_ = await cg.templatable(config[CONF_LOOP], args, float) + template_ = await cg.templatable(config[CONF_LOOP], args, cg.bool_) cg.add(var.set_loop(template_)) return var @@ -221,7 +217,7 @@ async def dfplayer_set_device_to_code(config, action_id, template_arg, args): async def dfplayer_set_volume_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) - template_ = await cg.templatable(config[CONF_VOLUME], args, float) + template_ = await cg.templatable(config[CONF_VOLUME], args, cg.uint8) cg.add(var.set_volume(template_)) return var diff --git a/esphome/components/dfplayer/dfplayer.h b/esphome/components/dfplayer/dfplayer.h index 03d2230ca6..0d240566c3 100644 --- a/esphome/components/dfplayer/dfplayer.h +++ b/esphome/components/dfplayer/dfplayer.h @@ -51,8 +51,8 @@ class DFPlayer : public uart::UARTDevice, public Component { bool is_playing() { return is_playing_; } void dump_config() override; - void add_on_finished_playback_callback(std::function callback) { - this->on_finished_playback_callback_.add(std::move(callback)); + template void add_on_finished_playback_callback(F &&callback) { + this->on_finished_playback_callback_.add(std::forward(callback)); } protected: @@ -171,12 +171,5 @@ template class DFPlayerIsPlayingCondition : public Conditionparent_->is_playing(); } }; -class DFPlayerFinishedPlaybackTrigger : public Trigger<> { - public: - explicit DFPlayerFinishedPlaybackTrigger(DFPlayer *parent) { - parent->add_on_finished_playback_callback([this]() { this->trigger(); }); - } -}; - } // namespace dfplayer } // namespace esphome diff --git a/esphome/components/dfrobot_sen0395/__init__.py b/esphome/components/dfrobot_sen0395/__init__.py index ba77e56abb..943c510279 100644 --- a/esphome/components/dfrobot_sen0395/__init__.py +++ b/esphome/components/dfrobot_sen0395/__init__.py @@ -97,7 +97,7 @@ def range_segment_list(input): ) largest_distance = -1 - for distance in input: + for i, distance in enumerate(input): if isinstance(distance, cv.Lambda): continue m = cv.distance(distance) @@ -112,7 +112,7 @@ def range_segment_list(input): ) largest_distance = m # Replace distance object with meters float - input[input.index(distance)] = m + input[i] = m return input @@ -159,31 +159,31 @@ async def dfrobot_sen0395_settings_to_code(config, action_id, template_arg, args await cg.register_parented(var, config[CONF_ID]) if factory_reset_config := config.get(CONF_FACTORY_RESET): - template_ = await cg.templatable(factory_reset_config, args, int) + template_ = await cg.templatable(factory_reset_config, args, cg.int8) cg.add(var.set_factory_reset(template_)) if CONF_DETECTION_SEGMENTS in config: segments = config[CONF_DETECTION_SEGMENTS] if len(segments) >= 2: - template_ = await cg.templatable(segments[0], args, float) + template_ = await cg.templatable(segments[0], args, cg.float_) cg.add(var.set_det_min1(template_)) - template_ = await cg.templatable(segments[1], args, float) + template_ = await cg.templatable(segments[1], args, cg.float_) cg.add(var.set_det_max1(template_)) if len(segments) >= 4: - template_ = await cg.templatable(segments[2], args, float) + template_ = await cg.templatable(segments[2], args, cg.float_) cg.add(var.set_det_min2(template_)) - template_ = await cg.templatable(segments[3], args, float) + template_ = await cg.templatable(segments[3], args, cg.float_) cg.add(var.set_det_max2(template_)) if len(segments) >= 6: - template_ = await cg.templatable(segments[4], args, float) + template_ = await cg.templatable(segments[4], args, cg.float_) cg.add(var.set_det_min3(template_)) - template_ = await cg.templatable(segments[5], args, float) + template_ = await cg.templatable(segments[5], args, cg.float_) cg.add(var.set_det_max3(template_)) if len(segments) >= 8: - template_ = await cg.templatable(segments[6], args, float) + template_ = await cg.templatable(segments[6], args, cg.float_) cg.add(var.set_det_min4(template_)) - template_ = await cg.templatable(segments[7], args, float) + template_ = await cg.templatable(segments[7], args, cg.float_) cg.add(var.set_det_max4(template_)) if CONF_OUTPUT_LATENCY in config: template_ = await cg.templatable( @@ -200,7 +200,7 @@ async def dfrobot_sen0395_settings_to_code(config, action_id, template_arg, args template_ = template_.total_milliseconds / 1000 cg.add(var.set_delay_after_disappear(template_)) if CONF_SENSITIVITY in config: - template_ = await cg.templatable(config[CONF_SENSITIVITY], args, int) + template_ = await cg.templatable(config[CONF_SENSITIVITY], args, cg.int8) cg.add(var.set_sensitivity(template_)) return var diff --git a/esphome/components/dht/dht.cpp b/esphome/components/dht/dht.cpp index fef247f168..5b7b6a268f 100644 --- a/esphome/components/dht/dht.cpp +++ b/esphome/components/dht/dht.cpp @@ -2,8 +2,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -namespace esphome { -namespace dht { +namespace esphome::dht { static const char *const TAG = "dht"; @@ -45,16 +44,13 @@ void DHT::update() { } if (success) { - ESP_LOGD(TAG, "Temperature %.1f°C Humidity %.1f%%", temperature, humidity); - if (this->temperature_sensor_ != nullptr) this->temperature_sensor_->publish_state(temperature); if (this->humidity_sensor_ != nullptr) this->humidity_sensor_->publish_state(humidity); this->status_clear_warning(); } else { - ESP_LOGW(TAG, "Invalid readings! Check pin number and pull-up resistor%s.", - this->is_auto_detect_ ? " and try manually specifying the model" : ""); + ESP_LOGW(TAG, "Invalid readings"); if (this->temperature_sensor_ != nullptr) this->temperature_sensor_->publish_state(NAN); if (this->humidity_sensor_ != nullptr) @@ -73,8 +69,7 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r *temperature = NAN; int error_code = 0; - int8_t i = 0; - uint8_t data[5] = {0, 0, 0, 0, 0}; + uint8_t data[5] = {}; #ifndef USE_ESP32 this->pin_.pin_mode(gpio::FLAG_OUTPUT); @@ -107,7 +102,9 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r uint8_t bit = 7; uint8_t byte = 0; - for (i = -1; i < 40; i++) { + // On 32-bit Xtensa/RISC-V cores, int8_t would require masking/sign-extension for comparisons + // vs. native int. Using int i is native word size — small win in the timing-critical section. + for (int i = -1; i < 40; i++) { uint32_t start_time = micros(); // Wait for rising edge @@ -156,11 +153,9 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r } } } - if (!report_errors && error_code != 0) - return false; - - if (error_code) { - ESP_LOGW(TAG, ESP_LOG_MSG_COMM_FAIL); + if (error_code != 0) { + if (report_errors) + ESP_LOGW(TAG, ESP_LOG_MSG_COMM_FAIL); return false; } @@ -177,7 +172,7 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r if (checksum_a != data[4] && checksum_b != data[4]) { if (report_errors) { - ESP_LOGW(TAG, "Checksum invalid: %u!=%u", checksum_a, data[4]); + ESP_LOGW(TAG, "Invalid checksum"); } return false; } @@ -234,5 +229,4 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r return true; } -} // namespace dht -} // namespace esphome +} // namespace esphome::dht diff --git a/esphome/components/dht/dht.h b/esphome/components/dht/dht.h index 4671ee6f27..0c535f7cf6 100644 --- a/esphome/components/dht/dht.h +++ b/esphome/components/dht/dht.h @@ -4,10 +4,9 @@ #include "esphome/core/hal.h" #include "esphome/components/sensor/sensor.h" -namespace esphome { -namespace dht { +namespace esphome::dht { -enum DHTModel { +enum DHTModel : uint8_t { DHT_MODEL_AUTO_DETECT = 0, DHT_MODEL_DHT11, DHT_MODEL_DHT22, @@ -42,7 +41,6 @@ class DHT : public PollingComponent { this->t_pin_ = pin; this->pin_ = pin->to_isr(); } - void set_model(DHTModel model) { model_ = model; } void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } @@ -55,13 +53,12 @@ class DHT : public PollingComponent { protected: bool read_sensor_(float *temperature, float *humidity, bool report_errors); + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; InternalGPIOPin *t_pin_; ISRInternalGPIOPin pin_; DHTModel model_{DHT_MODEL_AUTO_DETECT}; bool is_auto_detect_{false}; - sensor::Sensor *temperature_sensor_{nullptr}; - sensor::Sensor *humidity_sensor_{nullptr}; }; -} // namespace dht -} // namespace esphome +} // namespace esphome::dht diff --git a/esphome/components/display/__init__.py b/esphome/components/display/__init__.py index 6367f88acc..744b5d16c4 100644 --- a/esphome/components/display/__init__.py +++ b/esphome/components/display/__init__.py @@ -1,6 +1,9 @@ +from dataclasses import dataclass + from esphome import automation, core from esphome.automation import maybe_simple_id import esphome.codegen as cg +from esphome.components.const import KEY_METADATA import esphome.config_validation as cv from esphome.const import ( CONF_AUTO_CLEAR_ENABLED, @@ -16,7 +19,9 @@ from esphome.const import ( SCHEDULER_DONT_RUN, ) from esphome.core import CORE, CoroPriority, coroutine_with_priority +from esphome.cpp_generator import MockObj +DOMAIN = "display" IS_PLATFORM_COMPONENT = True display_ns = cg.esphome_ns.namespace("display") @@ -112,8 +117,9 @@ FULL_DISPLAY_SCHEMA.add_extra(_validate_test_card) async def setup_display_core_(var, config): - if CONF_ROTATION in config: - cg.add(var.set_rotation(DISPLAY_ROTATIONS[config[CONF_ROTATION]])) + if rotation := config.get(CONF_ROTATION, 0): + # Default initialised value for rotation is 0 + cg.add(var.set_rotation(DISPLAY_ROTATIONS[rotation])) if (auto_clear := config.get(CONF_AUTO_CLEAR_ENABLED)) is not None: # Default to true if pages or lambda is specified. Ideally this would be done during validation, but @@ -146,6 +152,39 @@ async def setup_display_core_(var, config): cg.add(var.show_test_card()) +# Storage of display metadata in a central location, accessible via the id + + +@dataclass(frozen=True) +class DisplayMetaData: + width: int = 0 + height: int = 0 + has_writer: bool = False + has_hardware_rotation: bool = False + + +def get_all_display_metadata() -> dict[str, DisplayMetaData]: + """Get all display metadata.""" + return CORE.data.setdefault(DOMAIN, {}).setdefault(KEY_METADATA, {}) + + +def get_display_metadata(display_id: str) -> DisplayMetaData | None: + """Get display metadata by ID for use by other components.""" + return get_all_display_metadata().get(display_id, DisplayMetaData()) + + +def add_metadata( + id: str | MockObj, + width: int, + height: int, + has_writer: bool, + has_hardware_rotation: bool = False, +): + get_all_display_metadata()[str(id)] = DisplayMetaData( + width, height, has_writer, has_hardware_rotation + ) + + async def register_display(var, config): await cg.register_component(var, config) await setup_display_core_(var, config) @@ -168,7 +207,8 @@ async def display_page_show_to_code(config, action_id, template_arg, args): cg.add(var.set_page(template_)) else: paren = await cg.get_variable(config[CONF_ID]) - cg.add(var.set_page(paren)) + template_ = await cg.templatable(paren, args, DisplayPagePtr) + cg.add(var.set_page(template_)) return var diff --git a/esphome/components/display/display.h b/esphome/components/display/display.h index e40f6ec963..6e38300d0e 100644 --- a/esphome/components/display/display.h +++ b/esphome/components/display/display.h @@ -704,7 +704,7 @@ class Display : public PollingComponent { void add_on_page_change_trigger(DisplayOnPageChangeTrigger *t) { this->on_page_change_triggers_.push_back(t); } /// Internal method to set the display rotation with. - void set_rotation(DisplayRotation rotation); + virtual void set_rotation(DisplayRotation rotation); // Internal method to set display auto clearing. void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; } diff --git a/esphome/components/display_menu_base/__init__.py b/esphome/components/display_menu_base/__init__.py index c9a0c7ee93..9125c43f0c 100644 --- a/esphome/components/display_menu_base/__init__.py +++ b/esphome/components/display_menu_base/__init__.py @@ -119,7 +119,7 @@ DisplayMenuOnPrevTrigger = display_menu_base_ns.class_( def validate_format(format): - if re.search(r"^%([+-])*(\d+)*(\.\d+)*[fg]$", format) is None: + if re.search(r"^%[+-]*(\d+)?(\.\d+)?[fg]$", format) is None: raise cv.Invalid( f"{CONF_FORMAT}: has to specify a printf-like format string specifying exactly one f or g type conversion, '{format}' provided" ) diff --git a/esphome/components/display_menu_base/automation.h b/esphome/components/display_menu_base/automation.h index 9c64794cef..50c26c344c 100644 --- a/esphome/components/display_menu_base/automation.h +++ b/esphome/components/display_menu_base/automation.h @@ -96,37 +96,52 @@ template class IsActiveCondition : public Condition { class DisplayMenuOnEnterTrigger : public Trigger { public: - explicit DisplayMenuOnEnterTrigger(MenuItem *parent) { - parent->add_on_enter_callback([this, parent]() { this->trigger(parent); }); + explicit DisplayMenuOnEnterTrigger(MenuItem *parent) : parent_(parent) { + parent->add_on_enter_callback([this]() { this->trigger(this->parent_); }); } + + protected: + MenuItem *parent_; }; class DisplayMenuOnLeaveTrigger : public Trigger { public: - explicit DisplayMenuOnLeaveTrigger(MenuItem *parent) { - parent->add_on_leave_callback([this, parent]() { this->trigger(parent); }); + explicit DisplayMenuOnLeaveTrigger(MenuItem *parent) : parent_(parent) { + parent->add_on_leave_callback([this]() { this->trigger(this->parent_); }); } + + protected: + MenuItem *parent_; }; class DisplayMenuOnValueTrigger : public Trigger { public: - explicit DisplayMenuOnValueTrigger(MenuItem *parent) { - parent->add_on_value_callback([this, parent]() { this->trigger(parent); }); + explicit DisplayMenuOnValueTrigger(MenuItem *parent) : parent_(parent) { + parent->add_on_value_callback([this]() { this->trigger(this->parent_); }); } + + protected: + MenuItem *parent_; }; class DisplayMenuOnNextTrigger : public Trigger { public: - explicit DisplayMenuOnNextTrigger(MenuItemCustom *parent) { - parent->add_on_next_callback([this, parent]() { this->trigger(parent); }); + explicit DisplayMenuOnNextTrigger(MenuItemCustom *parent) : parent_(parent) { + parent->add_on_next_callback([this]() { this->trigger(this->parent_); }); } + + protected: + MenuItemCustom *parent_; }; class DisplayMenuOnPrevTrigger : public Trigger { public: - explicit DisplayMenuOnPrevTrigger(MenuItemCustom *parent) { - parent->add_on_prev_callback([this, parent]() { this->trigger(parent); }); + explicit DisplayMenuOnPrevTrigger(MenuItemCustom *parent) : parent_(parent) { + parent->add_on_prev_callback([this]() { this->trigger(this->parent_); }); } + + protected: + MenuItemCustom *parent_; }; } // namespace display_menu_base diff --git a/esphome/components/display_menu_base/menu_item.h b/esphome/components/display_menu_base/menu_item.h index 36de146031..57d7350b9e 100644 --- a/esphome/components/display_menu_base/menu_item.h +++ b/esphome/components/display_menu_base/menu_item.h @@ -44,9 +44,9 @@ class MenuItem { MenuItemMenu *get_parent() { return this->parent_; } MenuItemType get_type() const { return this->item_type_; } template void set_text(V val) { this->text_ = val; } - void add_on_enter_callback(std::function &&cb) { this->on_enter_callbacks_.add(std::move(cb)); } - void add_on_leave_callback(std::function &&cb) { this->on_leave_callbacks_.add(std::move(cb)); } - void add_on_value_callback(std::function &&cb) { this->on_value_callbacks_.add(std::move(cb)); } + template void add_on_enter_callback(F &&cb) { this->on_enter_callbacks_.add(std::forward(cb)); } + template void add_on_leave_callback(F &&cb) { this->on_leave_callbacks_.add(std::forward(cb)); } + template void add_on_value_callback(F &&cb) { this->on_value_callbacks_.add(std::forward(cb)); } std::string get_text() const { return const_cast(this)->text_.value(this); } virtual bool get_immediate_edit() const { return false; } @@ -170,8 +170,8 @@ class MenuItemCommand : public MenuItem { class MenuItemCustom : public MenuItemEditable { public: explicit MenuItemCustom() : MenuItemEditable(MENU_ITEM_CUSTOM) {} - void add_on_next_callback(std::function &&cb) { this->on_next_callbacks_.add(std::move(cb)); } - void add_on_prev_callback(std::function &&cb) { this->on_prev_callbacks_.add(std::move(cb)); } + template void add_on_next_callback(F &&cb) { this->on_next_callbacks_.add(std::forward(cb)); } + template void add_on_prev_callback(F &&cb) { this->on_prev_callbacks_.add(std::forward(cb)); } bool has_value() const override { return this->value_getter_.has_value(); } std::string get_value_text() const override; diff --git a/esphome/components/dlms_meter/dlms_meter.cpp b/esphome/components/dlms_meter/dlms_meter.cpp index bd2150e8dd..b732e71d24 100644 --- a/esphome/components/dlms_meter/dlms_meter.cpp +++ b/esphome/components/dlms_meter/dlms_meter.cpp @@ -1,11 +1,18 @@ #include "dlms_meter.h" +#include + #if defined(USE_ESP8266_FRAMEWORK_ARDUINO) #include #elif defined(USE_ESP32) +#include +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) +#include +#else #include "mbedtls/esp_config.h" #include "mbedtls/gcm.h" #endif +#endif namespace esphome::dlms_meter { @@ -16,7 +23,7 @@ void DlmsMeterComponent::dump_config() { ESP_LOGCONFIG(TAG, "DLMS Meter:\n" " Provider: %s\n" - " Read Timeout: %u ms", + " Read Timeout: %" PRIu32 " ms", provider_name, this->read_timeout_); #define DLMS_METER_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s##_sensor_); DLMS_METER_SENSOR_LIST(DLMS_METER_LOG_SENSOR, ) @@ -240,6 +247,35 @@ bool DlmsMeterComponent::decrypt_(std::vector &mbus_payload, uint16_t m br_gcm_flip(&gcm_ctx); br_gcm_run(&gcm_ctx, 0, payload_ptr, message_length); #elif defined(USE_ESP32) +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + // PSA Crypto multipart AEAD (no tag verification, matching legacy behavior) + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + psa_set_key_type(&attributes, PSA_KEY_TYPE_AES); + psa_set_key_bits(&attributes, this->decryption_key_.size() * 8); + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DECRYPT); + psa_set_key_algorithm(&attributes, PSA_ALG_GCM); + + mbedtls_svc_key_id_t key_id; + bool decrypt_failed = true; + if (psa_import_key(&attributes, this->decryption_key_.data(), this->decryption_key_.size(), &key_id) == PSA_SUCCESS) { + psa_aead_operation_t op = PSA_AEAD_OPERATION_INIT; + if (psa_aead_decrypt_setup(&op, key_id, PSA_ALG_GCM) == PSA_SUCCESS && + psa_aead_set_nonce(&op, iv, sizeof(iv)) == PSA_SUCCESS) { + size_t outlen = 0; + if (psa_aead_update(&op, payload_ptr, message_length, payload_ptr, message_length, &outlen) == PSA_SUCCESS && + outlen == message_length) { + decrypt_failed = false; + } + } + psa_aead_abort(&op); + psa_destroy_key(key_id); + } + if (decrypt_failed) { + ESP_LOGE(TAG, "Decryption failed"); + this->receive_buffer_.clear(); + return false; + } +#else size_t outlen = 0; mbedtls_gcm_context gcm_ctx; mbedtls_gcm_init(&gcm_ctx); @@ -252,6 +288,7 @@ bool DlmsMeterComponent::decrypt_(std::vector &mbus_payload, uint16_t m this->receive_buffer_.clear(); return false; } +#endif #else #error "Invalid Platform" #endif diff --git a/esphome/components/dps310/dps310.cpp b/esphome/components/dps310/dps310.cpp index aa0a77cdd8..b1366cd069 100644 --- a/esphome/components/dps310/dps310.cpp +++ b/esphome/components/dps310/dps310.cpp @@ -127,8 +127,7 @@ void DPS310Component::read_() { this->update_in_progress_ = false; this->status_clear_warning(); } else { - auto f = std::bind(&DPS310Component::read_, this); - this->set_timeout("dps310", 10, f); + this->set_timeout("dps310", 10, [this]() { this->read_(); }); } } diff --git a/esphome/components/ds1307/ds1307.cpp b/esphome/components/ds1307/ds1307.cpp index 5c0e98290b..8fff4213b4 100644 --- a/esphome/components/ds1307/ds1307.cpp +++ b/esphome/components/ds1307/ds1307.cpp @@ -40,11 +40,8 @@ void DS1307Component::read_time() { .hour = uint8_t(ds1307_.reg.hour + 10u * ds1307_.reg.hour_10), .day_of_week = uint8_t(ds1307_.reg.weekday), .day_of_month = uint8_t(ds1307_.reg.day + 10u * ds1307_.reg.day_10), - .day_of_year = 1, // ignored by recalc_timestamp_utc(false) .month = uint8_t(ds1307_.reg.month + 10u * ds1307_.reg.month_10), .year = uint16_t(ds1307_.reg.year + 10u * ds1307_.reg.year_10 + 2000), - .is_dst = false, // not used - .timestamp = 0 // overwritten by recalc_timestamp_utc(false) }; rtc_time.recalc_timestamp_utc(false); if (!rtc_time.is_valid()) { diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py index 7d76856f28..9c493bfcff 100644 --- a/esphome/components/dsmr/__init__.py +++ b/esphome/components/dsmr/__init__.py @@ -4,7 +4,7 @@ from esphome.components import uart import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_RECEIVE_TIMEOUT, CONF_UART_ID -CODEOWNERS = ["@glmnet", "@zuidwijk", "@PolarGoose"] +CODEOWNERS = ["@glmnet", "@PolarGoose"] MULTI_CONF = True @@ -16,6 +16,7 @@ CONF_DECRYPTION_KEY = "decryption_key" CONF_DSMR_ID = "dsmr_id" CONF_GAS_MBUS_ID = "gas_mbus_id" CONF_WATER_MBUS_ID = "water_mbus_id" +CONF_THERMAL_MBUS_ID = "thermal_mbus_id" CONF_MAX_TELEGRAM_LENGTH = "max_telegram_length" CONF_REQUEST_INTERVAL = "request_interval" CONF_REQUEST_PIN = "request_pin" @@ -35,7 +36,8 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean, cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_, cv.Optional(CONF_WATER_MBUS_ID, default=2): cv.int_, - cv.Optional(CONF_MAX_TELEGRAM_LENGTH, default=1500): cv.int_, + cv.Optional(CONF_THERMAL_MBUS_ID, default=3): cv.int_, + cv.Optional(CONF_MAX_TELEGRAM_LENGTH, default=1500): cv.int_range(min=1), cv.Optional(CONF_REQUEST_PIN): pins.gpio_output_pin_schema, cv.Optional( CONF_REQUEST_INTERVAL, default="0ms" @@ -44,7 +46,9 @@ CONFIG_SCHEMA = cv.All( CONF_RECEIVE_TIMEOUT, default="200ms" ): cv.positive_time_period_milliseconds, } - ).extend(uart.UART_DEVICE_SCHEMA), + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA), ) @@ -64,6 +68,7 @@ async def to_code(config): cg.add_build_flag("-DDSMR_GAS_MBUS_ID=" + str(config[CONF_GAS_MBUS_ID])) cg.add_build_flag("-DDSMR_WATER_MBUS_ID=" + str(config[CONF_WATER_MBUS_ID])) + cg.add_build_flag("-DDSMR_THERMAL_MBUS_ID=" + str(config[CONF_THERMAL_MBUS_ID])) # DSMR Parser cg.add_library("esphome/dsmr_parser", "1.1.0") diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index 863af42d1b..c49614eaa9 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -122,42 +122,52 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional("total_imported_energy"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=3, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("reactive_energy_delivered_tariff1"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=3, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("reactive_energy_delivered_tariff2"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=3, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("reactive_energy_delivered_tariff3"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=3, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("reactive_energy_delivered_tariff4"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=3, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("total_exported_energy"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=3, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("reactive_energy_returned_tariff1"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=3, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("reactive_energy_returned_tariff2"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=3, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("reactive_energy_returned_tariff3"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=3, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("reactive_energy_returned_tariff4"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=3, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("power_delivered"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT, diff --git a/esphome/components/e131/__init__.py b/esphome/components/e131/__init__.py index 301812e314..a1a8e0aec5 100644 --- a/esphome/components/e131/__init__.py +++ b/esphome/components/e131/__init__.py @@ -29,7 +29,7 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(): cv.declare_id(E131Component), cv.Optional(CONF_METHOD, default="MULTICAST"): cv.one_of(*METHODS, upper=True), } -) +).extend(cv.COMPONENT_SCHEMA) async def to_code(config): diff --git a/esphome/components/emontx/__init__.py b/esphome/components/emontx/__init__.py new file mode 100644 index 0000000000..a2d4349698 --- /dev/null +++ b/esphome/components/emontx/__init__.py @@ -0,0 +1,152 @@ +from dataclasses import dataclass, field + +from esphome import automation +import esphome.codegen as cg +from esphome.components import uart +import esphome.config_validation as cv +from esphome.const import ( + CONF_COMMAND, + CONF_ID, + CONF_ON_DATA, + CONF_RX_BUFFER_SIZE, + CONF_UART_ID, +) +from esphome.core import CORE +import esphome.final_validate as fv +from esphome.types import ConfigType + +AUTO_LOAD = ["json"] +CODEOWNERS = ["@FredM67", "@TrystanLea", "@glynhudson"] +DEPENDENCIES = ["uart"] + +emontx_ns = cg.esphome_ns.namespace("emontx") +EmonTx = emontx_ns.class_("EmonTx", cg.Component, uart.UARTDevice) + +# Action to send command to emonTx +EmonTxSendCommandAction = emontx_ns.class_("EmonTxSendCommandAction", automation.Action) + +CONF_EMONTX_ID = "emontx_id" +CONF_TAG_NAME = "tag_name" +CONF_ON_JSON = "on_json" + +DOMAIN = "emontx" + +MINIMUM_RX_BUFFER_SIZE = 2048 + + +@dataclass +class EmonTxData: + sensor_counts: dict[str, int] = field(default_factory=dict) + + +def _get_data() -> EmonTxData: + if DOMAIN not in CORE.data: + CORE.data[DOMAIN] = EmonTxData() + return CORE.data[DOMAIN] + + +# Main configuration schema +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(EmonTx), + cv.Optional(CONF_ON_JSON): automation.validate_automation({}), + cv.Optional(CONF_ON_DATA): automation.validate_automation({}), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(uart.UART_DEVICE_SCHEMA) +) + + +def final_validate(config: ConfigType) -> ConfigType: + full_config = fv.full_config.get() + + # Count sensors registered to this hub (IDs are resolved at final_validate stage) + hub_id = str(config[CONF_ID]) + sensor_count = sum( + 1 + for s in full_config.get("sensor", []) + if s.get("platform") == "emontx" and str(s.get(CONF_EMONTX_ID)) == hub_id + ) + _get_data().sensor_counts[hub_id] = sensor_count + + # Ensure UART RX buffer size is large enough to handle data bursts from firmware + for uart_conf in full_config["uart"]: + if uart_conf[CONF_ID] == config[CONF_UART_ID]: + current_buffer_size = uart_conf[CONF_RX_BUFFER_SIZE] + if current_buffer_size < MINIMUM_RX_BUFFER_SIZE: + raise cv.Invalid( + f"Component emontx requires UART '{config[CONF_UART_ID]}' to have " + f"rx_buffer_size of at least {MINIMUM_RX_BUFFER_SIZE} bytes " + f"(currently set to {current_buffer_size} bytes). " + f"Please add 'rx_buffer_size: {MINIMUM_RX_BUFFER_SIZE}' to your uart configuration.", + path=[CONF_UART_ID], + ) + break + + # Validate UART settings + schema = uart.final_validate_device_schema( + "emontx", + baud_rate=115200, + require_tx=False, + require_rx=True, + data_bits=8, + parity="NONE", + stop_bits=1, + ) + return schema(config) + + +FINAL_VALIDATE_SCHEMA = final_validate + + +_CALLBACK_AUTOMATIONS = ( + automation.CallbackAutomation( + CONF_ON_JSON, + "add_on_json_callback", + [(cg.JsonObject, "json"), (cg.std_string, "raw_json")], + ), + automation.CallbackAutomation( + CONF_ON_DATA, "add_on_data_callback", [(cg.std_string, "data")] + ), +) + + +async def to_code(config: ConfigType) -> None: + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + + # Initialize sensor storage with count from final_validate + sensor_count = _get_data().sensor_counts.get(str(config[CONF_ID]), 0) + if sensor_count > 0: + cg.add(var.init_sensors(sensor_count)) + + await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS) + + +# Action: emontx.send_command + +EMONTX_SEND_COMMAND_ACTION_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(EmonTx), + cv.Required(CONF_COMMAND): cv.templatable(cv.string), + } +) + + +@automation.register_action( + "emontx.send_command", + EmonTxSendCommandAction, + EMONTX_SEND_COMMAND_ACTION_SCHEMA, + synchronous=True, +) +async def emontx_send_command_action_to_code( + config: ConfigType, action_id, template_arg, args +) -> None: + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + template_ = await cg.templatable(config[CONF_COMMAND], args, cg.std_string) + cg.add(var.set_command(template_)) + return var diff --git a/esphome/components/emontx/emontx.cpp b/esphome/components/emontx/emontx.cpp new file mode 100644 index 0000000000..7a1b084fe0 --- /dev/null +++ b/esphome/components/emontx/emontx.cpp @@ -0,0 +1,116 @@ +#include "emontx.h" +#include "esphome/core/log.h" +#include "esphome/components/json/json_util.h" + +namespace esphome::emontx { + +static const char *const TAG = "emontx"; + +void EmonTx::setup() { this->buffer_pos_ = 0; } + +/** + * @brief Implements the main loop for parsing data from the serial port. + * + * @details Continuously processes incoming UART data line-by-line: + * 1. Fire on_data callbacks for all received lines + * 2. If line starts with '{', parse as JSON and update sensors/callbacks + */ +void EmonTx::loop() { + // Read all available data to prevent UART buffer overflow + while (this->available() > 0) { + uint8_t received = this->read(); + + if (received == '\r') { + continue; // Ignore CR + } else if (received == '\n') { + // End of line - process the buffer + if (this->buffer_pos_ > 0) { + // Null-terminate for safe logging and c_str() use + size_t len = this->buffer_pos_; + this->buffer_[len] = '\0'; + this->buffer_pos_ = 0; + + StringRef line(this->buffer_.data(), len); + ESP_LOGD(TAG, "Received line: %s", line.c_str()); + + // Fire data callbacks for all received lines + this->data_callbacks_.call(line); + + // Check if this line is JSON (starts with '{') + if (this->buffer_[0] == '{') { + ESP_LOGV(TAG, "Line is JSON, parsing..."); + this->parse_json_(this->buffer_.data(), len); + } + } + } else if (this->buffer_pos_ >= MAX_LINE_LENGTH) { + ESP_LOGW(TAG, "Buffer overflow (>%zu bytes), discarding buffer", MAX_LINE_LENGTH); + this->buffer_pos_ = 0; + } else { + this->buffer_[this->buffer_pos_++] = static_cast(received); + } + } +} + +void EmonTx::parse_json_(const char *data, size_t len) { + bool success = json::parse_json(reinterpret_cast(data), len, [this, data, len](JsonObject root) { +#ifdef USE_SENSOR + for (auto &sensor_pair : this->sensors_) { + auto val = root[sensor_pair.first]; + if (val.is()) { + float value = val; + ESP_LOGV(TAG, "Updating sensor '%s' with value: %.2f", sensor_pair.first, value); + sensor_pair.second->publish_state(value); + } + } +#endif + + this->json_callbacks_.call(root, StringRef(data, len)); + return true; + }); + + if (!success) { + ESP_LOGW(TAG, "Failed to parse JSON"); + } +} + +/** + * @brief Logs the EmonTx component configuration details. + */ +void EmonTx::dump_config() { + ESP_LOGCONFIG(TAG, "EmonTx:"); + +#ifdef USE_SENSOR + ESP_LOGCONFIG(TAG, " Registered sensors: %zu", this->sensors_.size()); + for (const auto &sensor_pair : this->sensors_) { + ESP_LOGCONFIG(TAG, " Sensor: %s", sensor_pair.first); + } +#else + ESP_LOGCONFIG(TAG, " Sensor support: DISABLED"); +#endif +} + +/** + * @brief Sends a command string to the emonTx device via UART. + * + * @param command The command string to send (LF will be appended automatically). + */ +void EmonTx::send_command(const std::string &command) { + ESP_LOGD(TAG, "Sending command to emonTx: %s", command.c_str()); + this->write_str(command.c_str()); + this->write_byte('\n'); +} + +#ifdef USE_SENSOR +/** + * @brief Registers a sensor to receive updates for a specific JSON tag. + * + * @param tag_name The JSON key to monitor for this sensor (must be a string literal). + * @param sensor Pointer to the sensor that will receive value updates. + */ +void EmonTx::register_sensor(const char *tag_name, sensor::Sensor *sensor) { + ESP_LOGCONFIG(TAG, "Registering sensor for tag: %s", tag_name); + this->sensors_.emplace_back(tag_name, sensor); +} +#endif + +} // namespace esphome::emontx diff --git a/esphome/components/emontx/emontx.h b/esphome/components/emontx/emontx.h new file mode 100644 index 0000000000..67e7f5bffc --- /dev/null +++ b/esphome/components/emontx/emontx.h @@ -0,0 +1,69 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/defines.h" +#include "esphome/core/automation.h" +#include "esphome/core/helpers.h" +#include "esphome/core/string_ref.h" +#include "esphome/components/uart/uart.h" +#include "esphome/components/json/json_util.h" + +#include + +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif + +namespace esphome::emontx { + +/// Maximum line length in bytes (plus one byte reserved for null terminator) +static constexpr size_t MAX_LINE_LENGTH = 1024; + +/** + * @class EmonTx + * @brief Main class for the EmonTx component. + * + * The EmonTx processes incoming data frames via UART, + * extracts tags and values, and publishes them to registered sensors. + */ +class EmonTx : public Component, public uart::UARTDevice { + public: + EmonTx() = default; + + void loop() override; + void setup() override; + void dump_config() override; + + template void add_on_json_callback(F &&callback) { this->json_callbacks_.add(std::forward(callback)); } + + template void add_on_data_callback(F &&callback) { this->data_callbacks_.add(std::forward(callback)); } + + // Send command to emonTx via UART + void send_command(const std::string &command); + +#ifdef USE_SENSOR + void init_sensors(size_t count) { this->sensors_.init(count); } + void register_sensor(const char *tag_name, sensor::Sensor *sensor); +#endif + + protected: + void parse_json_(const char *data, size_t len); + +#ifdef USE_SENSOR + FixedVector> sensors_{}; +#endif + LazyCallbackManager json_callbacks_; + LazyCallbackManager data_callbacks_; + uint16_t buffer_pos_{0}; + std::array buffer_{}; +}; + +// Action to send command to emonTx +template class EmonTxSendCommandAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(std::string, command) + + void play(const Ts &...x) override { this->parent_->send_command(this->command_.value(x...)); } +}; + +} // namespace esphome::emontx diff --git a/esphome/components/emontx/sensor/__init__.py b/esphome/components/emontx/sensor/__init__.py new file mode 100644 index 0000000000..83a972c5e0 --- /dev/null +++ b/esphome/components/emontx/sensor/__init__.py @@ -0,0 +1,133 @@ +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_ACCURACY_DECIMALS, + CONF_DEVICE_CLASS, + CONF_ID, + CONF_STATE_CLASS, + CONF_UNIT_OF_MEASUREMENT, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_POWER_FACTOR, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, + UNIT_AMPERE, + UNIT_CELSIUS, + UNIT_EMPTY, + UNIT_PULSES, + UNIT_VOLT, + UNIT_WATT, + UNIT_WATT_HOURS, +) +from esphome.types import ConfigType + +from .. import CONF_EMONTX_ID, CONF_TAG_NAME, EmonTx, emontx_ns + +EmonTxSensor = emontx_ns.class_("EmonTxSensor", sensor.Sensor, cg.Component) + +# Define sensor type configurations by prefix +SENSOR_CONFIGS = { + "P": { + CONF_UNIT_OF_MEASUREMENT: UNIT_WATT, + CONF_DEVICE_CLASS: DEVICE_CLASS_POWER, + CONF_STATE_CLASS: STATE_CLASS_MEASUREMENT, + CONF_ACCURACY_DECIMALS: 0, + }, + "E": { + CONF_UNIT_OF_MEASUREMENT: UNIT_WATT_HOURS, + CONF_DEVICE_CLASS: DEVICE_CLASS_ENERGY, + CONF_STATE_CLASS: STATE_CLASS_TOTAL_INCREASING, + CONF_ACCURACY_DECIMALS: 0, + }, + "V": { + CONF_UNIT_OF_MEASUREMENT: UNIT_VOLT, + CONF_DEVICE_CLASS: DEVICE_CLASS_VOLTAGE, + CONF_STATE_CLASS: STATE_CLASS_MEASUREMENT, + CONF_ACCURACY_DECIMALS: 2, + }, + "I": { + CONF_UNIT_OF_MEASUREMENT: UNIT_AMPERE, + CONF_DEVICE_CLASS: DEVICE_CLASS_CURRENT, + CONF_STATE_CLASS: STATE_CLASS_MEASUREMENT, + CONF_ACCURACY_DECIMALS: 2, + }, + "T": { + CONF_UNIT_OF_MEASUREMENT: UNIT_CELSIUS, + CONF_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, + CONF_STATE_CLASS: STATE_CLASS_MEASUREMENT, + CONF_ACCURACY_DECIMALS: 2, + }, +} + +# Pattern-based configurations +PATTERN_CONFIGS = { + "PULSE": { + CONF_UNIT_OF_MEASUREMENT: UNIT_PULSES, + CONF_DEVICE_CLASS: DEVICE_CLASS_ENERGY, + CONF_ACCURACY_DECIMALS: 0, + }, + "PF": { + CONF_UNIT_OF_MEASUREMENT: UNIT_EMPTY, + CONF_DEVICE_CLASS: DEVICE_CLASS_POWER_FACTOR, + CONF_STATE_CLASS: STATE_CLASS_MEASUREMENT, + CONF_ACCURACY_DECIMALS: 2, + }, +} + +# Create a base schema that's flexible for any tag +BASE_SCHEMA = sensor.sensor_schema( + EmonTxSensor, + state_class=STATE_CLASS_MEASUREMENT, + accuracy_decimals=0, +).extend( + { + cv.GenerateID(CONF_EMONTX_ID): cv.use_id(EmonTx), + cv.Required(CONF_TAG_NAME): cv.string, + } +) + + +def apply_tag_defaults(config: ConfigType) -> ConfigType: + """Apply defaults based on tag prefix if applicable, but don't restrict any tags.""" + tag = config[CONF_TAG_NAME] + + # Skip if tag is too short + if len(tag) < 2: + return config + + # Check if this tag starts with a known prefix + tag_upper = tag.upper() + + for pattern, pattern_config in PATTERN_CONFIGS.items(): + if tag_upper.startswith(pattern): + # Apply pattern defaults if not overridden by user + for key, value in pattern_config.items(): + if key not in config: + config[key] = value + return config + + # Only apply defaults for known prefixes with numeric indices + prefix = tag_upper[0] + if prefix in SENSOR_CONFIGS and len(tag) > 1 and tag[1:].isdigit(): + # Apply defaults for known tag types, but only if not overridden by user + defaults = SENSOR_CONFIGS[prefix] + for key, value in defaults.items(): + if key not in config: + config[key] = value + + return config + + +CONFIG_SCHEMA = cv.All(BASE_SCHEMA, apply_tag_defaults) + + +async def to_code(config: ConfigType) -> None: + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await sensor.register_sensor(var, config) + hub = await cg.get_variable(config[CONF_EMONTX_ID]) + cg.add(hub.register_sensor(config[CONF_TAG_NAME], var)) diff --git a/esphome/components/emontx/sensor/emontx_sensor.cpp b/esphome/components/emontx/sensor/emontx_sensor.cpp new file mode 100644 index 0000000000..142df0150e --- /dev/null +++ b/esphome/components/emontx/sensor/emontx_sensor.cpp @@ -0,0 +1,10 @@ +#include "emontx_sensor.h" +#include "esphome/core/log.h" + +namespace esphome::emontx { + +static const char *const TAG = "emontx_sensor"; + +void EmonTxSensor::dump_config() { LOG_SENSOR(" ", "EmonTx Sensor", this); } + +} // namespace esphome::emontx diff --git a/esphome/components/emontx/sensor/emontx_sensor.h b/esphome/components/emontx/sensor/emontx_sensor.h new file mode 100644 index 0000000000..9714acdf0d --- /dev/null +++ b/esphome/components/emontx/sensor/emontx_sensor.h @@ -0,0 +1,13 @@ +#pragma once + +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" + +namespace esphome::emontx { + +class EmonTxSensor : public sensor::Sensor, public Component { + public: + void dump_config() override; +}; + +} // namespace esphome::emontx diff --git a/esphome/components/ens160_base/__init__.py b/esphome/components/ens160_base/__init__.py index 3b6ad8a4ee..46c53c3b10 100644 --- a/esphome/components/ens160_base/__init__.py +++ b/esphome/components/ens160_base/__init__.py @@ -24,7 +24,6 @@ CODEOWNERS = ["@vincentscode", "@latonita"] ens160_ns = cg.esphome_ns.namespace("ens160_base") CONF_AQI = "aqi" -UNIT_INDEX = "index" CONFIG_SCHEMA_BASE = cv.Schema( { diff --git a/esphome/components/epaper_spi/display.py b/esphome/components/epaper_spi/display.py index 2657071f45..658f9e2c4a 100644 --- a/esphome/components/epaper_spi/display.py +++ b/esphome/components/epaper_spi/display.py @@ -175,9 +175,7 @@ async def to_code(config): *model.get_constructor_args(config), ) - # Rotation is handled by setting the transform - display_config = {k: v for k, v in config.items() if k != CONF_ROTATION} - await display.register_display(var, display_config) + await display.register_display(var, config) await spi.register_spi_device(var, config, write_only=True) dc = await cg.gpio_pin_expression(config[CONF_DC_PIN]) @@ -201,16 +199,6 @@ async def to_code(config): transform[CONF_SWAP_XY] = False else: transform = {x: model.get_default(x, False) for x in TRANSFORM_OPTIONS} - rotation = config[CONF_ROTATION] - if rotation == 180: - transform[CONF_MIRROR_X] = not transform[CONF_MIRROR_X] - transform[CONF_MIRROR_Y] = not transform[CONF_MIRROR_Y] - elif rotation == 90: - transform[CONF_SWAP_XY] = not transform[CONF_SWAP_XY] - transform[CONF_MIRROR_X] = not transform[CONF_MIRROR_X] - elif rotation == 270: - transform[CONF_SWAP_XY] = not transform[CONF_SWAP_XY] - transform[CONF_MIRROR_Y] = not transform[CONF_MIRROR_Y] transform_str = "|".join( { str(getattr(Transform, x.upper())) diff --git a/esphome/components/epaper_spi/epaper_spi.cpp b/esphome/components/epaper_spi/epaper_spi.cpp index ae1923a916..a2ca311b30 100644 --- a/esphome/components/epaper_spi/epaper_spi.cpp +++ b/esphome/components/epaper_spi/epaper_spi.cpp @@ -97,6 +97,23 @@ bool EPaperBase::reset() { return true; } +void EPaperBase::update_effective_transform_() { + switch (this->rotation_) { + case DISPLAY_ROTATION_90_DEGREES: + this->effective_transform_ = this->transform_ ^ (SWAP_XY | MIRROR_X); + break; + case DISPLAY_ROTATION_180_DEGREES: + this->effective_transform_ = this->transform_ ^ (MIRROR_Y | MIRROR_X); + break; + case DISPLAY_ROTATION_270_DEGREES: + this->effective_transform_ = this->transform_ ^ (SWAP_XY | MIRROR_Y); + break; + default: + this->effective_transform_ = this->transform_; + break; + } +} + void EPaperBase::update() { if (this->state_ != EPaperState::IDLE) { ESP_LOGE(TAG, "Display already in state %s", epaper_state_to_string_()); @@ -280,11 +297,11 @@ bool EPaperBase::initialise(bool partial) { bool EPaperBase::rotate_coordinates_(int &x, int &y) { if (!this->get_clipping().inside(x, y)) return false; - if (this->transform_ & SWAP_XY) + if (this->effective_transform_ & SWAP_XY) std::swap(x, y); - if (this->transform_ & MIRROR_X) + if (this->effective_transform_ & MIRROR_X) x = this->width_ - x - 1; - if (this->transform_ & MIRROR_Y) + if (this->effective_transform_ & MIRROR_Y) y = this->height_ - y - 1; if (x >= this->width_ || y >= this->height_ || x < 0 || y < 0) return false; diff --git a/esphome/components/epaper_spi/epaper_spi.h b/esphome/components/epaper_spi/epaper_spi.h index a743985518..2992ca5afd 100644 --- a/esphome/components/epaper_spi/epaper_spi.h +++ b/esphome/components/epaper_spi/epaper_spi.h @@ -1,6 +1,6 @@ #pragma once -#include "esphome/components/display/display_buffer.h" +#include "esphome/components/display/display.h" #include "esphome/components/spi/spi.h" #include "esphome/components/split_buffer/split_buffer.h" #include "esphome/core/component.h" @@ -51,7 +51,14 @@ class EPaperBase : public Display, void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } void set_busy_pin(GPIOPin *busy) { this->busy_pin_ = busy; } void set_reset_duration(uint32_t reset_duration) { this->reset_duration_ = reset_duration; } - void set_transform(uint8_t transform) { this->transform_ = transform; } + void set_transform(uint8_t transform) { + this->transform_ = transform; + this->update_effective_transform_(); + } + void set_rotation(DisplayRotation rotation) override { + Display::set_rotation(rotation); + this->update_effective_transform_(); + } void set_full_update_every(uint8_t full_update_every) { this->full_update_every_ = full_update_every; } void dump_config() override; @@ -103,12 +110,14 @@ class EPaperBase : public Display, this->fill(COLOR_ON); } + int get_width() override { return this->effective_transform_ & SWAP_XY ? this->height_ : this->width_; } + int get_height() override { return this->effective_transform_ & SWAP_XY ? this->width_ : this->height_; } + void draw_pixel_at(int x, int y, Color color) override; + protected: int get_height_internal() override { return this->height_; }; int get_width_internal() override { return this->width_; }; - int get_width() override { return this->transform_ & SWAP_XY ? this->height_ : this->width_; } - int get_height() override { return this->transform_ & SWAP_XY ? this->width_ : this->height_; } - void draw_pixel_at(int x, int y, Color color) override; + bool is_using_partial_update_() const { return this->full_update_every_ > 1; } void process_state_(); const char *epaper_state_to_string_(); @@ -119,6 +128,7 @@ class EPaperBase : public Display, void send_init_sequence_(const uint8_t *sequence, size_t length); void wait_for_idle_(bool should_wait); bool init_buffer_(size_t buffer_length); + void update_effective_transform_(); bool rotate_coordinates_(int &x, int &y); /** @@ -171,6 +181,7 @@ class EPaperBase : public Display, uint32_t delay_until_{}; // timestamp until which to delay processing uint16_t next_delay_{}; // milliseconds to delay before next state uint8_t transform_{}; + uint8_t effective_transform_{}; uint8_t update_count_{}; // these values represent the bounds of the updated buffer. Note that x_high and y_high // point to the pixel past the last one updated, i.e. may range up to width/height. diff --git a/esphome/components/epaper_spi/epaper_spi_mono.cpp b/esphome/components/epaper_spi/epaper_spi_mono.cpp index d10022c4ac..ee117304c4 100644 --- a/esphome/components/epaper_spi/epaper_spi_mono.cpp +++ b/esphome/components/epaper_spi/epaper_spi_mono.cpp @@ -15,7 +15,11 @@ void EPaperMono::refresh_screen(bool partial) { void EPaperMono::deep_sleep() { ESP_LOGV(TAG, "Deep sleep"); - this->command(0x10); + if (this->is_using_partial_update_()) { + this->cmd_data(0x10, {0x00}); // sleep in power on mode + } else { + this->cmd_data(0x10, {0x03}); // deep sleep + } } bool EPaperMono::reset() { @@ -27,6 +31,14 @@ bool EPaperMono::reset() { } void EPaperMono::set_window() { + // if not using partial update, the display will go into deep sleep, so must rewrite entire + // buffer since the display RAM will not retain contents + if (!this->is_using_partial_update_()) { + this->x_low_ = 0; + this->x_high_ = this->width_; + this->y_low_ = 0; + this->y_high_ = this->height_; + } // round x-coordinates to byte boundaries this->x_low_ &= ~7; this->x_high_ += 7; diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 72d0e42971..f27690c97b 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -28,6 +28,7 @@ from esphome.const import ( CONF_PLATFORMIO_OPTIONS, CONF_REF, CONF_SAFE_MODE, + CONF_SIZE, CONF_SOURCE, CONF_TYPE, CONF_VARIANT, @@ -43,6 +44,7 @@ from esphome.const import ( __version__, ) from esphome.core import CORE, HexInt +from esphome.core.config import BOARD_MAX_LENGTH from esphome.coroutine import CoroPriority, coroutine_with_priority import esphome.final_validate as fv from esphome.helpers import copy_file_if_changed, rmtree, write_file_if_changed @@ -59,6 +61,7 @@ from .const import ( # noqa KEY_EXTRA_BUILD_FILES, KEY_FLASH_SIZE, KEY_FULL_CERT_BUNDLE, + KEY_IDF_VERSION, KEY_PATH, KEY_REF, KEY_REPO, @@ -95,6 +98,12 @@ CONF_ENABLE_LWIP_ASSERT = "enable_lwip_assert" CONF_EXECUTE_FROM_PSRAM = "execute_from_psram" CONF_MINIMUM_CHIP_REVISION = "minimum_chip_revision" CONF_RELEASE = "release" +CONF_SIGNED_OTA_VERIFICATION = "signed_ota_verification" +CONF_SIGNING_KEY = "signing_key" +CONF_SIGNING_SCHEME = "signing_scheme" +CONF_SRAM1_AS_IRAM = "sram1_as_iram" +CONF_SUBTYPE = "subtype" +CONF_VERIFICATION_KEY = "verification_key" ARDUINO_FRAMEWORK_NAME = "framework-arduinoespressif32" ARDUINO_FRAMEWORK_PKG = f"pioarduino/{ARDUINO_FRAMEWORK_NAME}" @@ -116,6 +125,27 @@ ASSERTION_LEVELS = { "SILENT": "CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT", } +SIGNING_SCHEMES = { + "rsa3072": "CONFIG_SECURE_SIGNED_APPS_RSA_SCHEME", + "ecdsa256": "CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME", +} + +# Chip variants that only support one signing scheme for Secure Boot V2. +# Based on SOC_SECURE_BOOT_V2_RSA / SOC_SECURE_BOOT_V2_ECC in soc_caps.h. +# Variants not listed in either set support both RSA and ECDSA +# (e.g. C5, C6, H2, P4). New variants should be added to the +# appropriate set if they only support one scheme. +SIGNED_OTA_RSA_ONLY_VARIANTS = { + VARIANT_ESP32, + VARIANT_ESP32S2, + VARIANT_ESP32S3, + VARIANT_ESP32C3, +} +SIGNED_OTA_ECC_ONLY_VARIANTS = { + VARIANT_ESP32C2, + VARIANT_ESP32C61, +} + COMPILER_OPTIMIZATIONS = { "DEBUG": "CONFIG_COMPILER_OPTIMIZATION_DEBUG", "NONE": "CONFIG_COMPILER_OPTIMIZATION_NONE", @@ -375,12 +405,11 @@ FULL_CPU_FREQUENCIES = set(itertools.chain.from_iterable(CPU_FREQUENCIES.values( def set_core_data(config): cpu_frequency = config.get(CONF_CPU_FREQUENCY, None) variant = config[CONF_VARIANT] - # if not specified in config, set to 160MHz if supported, the fastest otherwise + # if not specified in config, default to the maximum supported frequency + # (ESP32-P4 engineering samples are limited to 360MHz, non-engineering can do 400MHz) if cpu_frequency is None: choices = CPU_FREQUENCIES[variant] - if "160MHZ" in choices: - cpu_frequency = "160MHZ" - elif "360MHZ" in choices: + if variant == VARIANT_ESP32P4 and config.get(CONF_ENGINEERING_SAMPLE): cpu_frequency = "360MHZ" else: cpu_frequency = choices[-1] @@ -420,9 +449,20 @@ def set_core_data(config): CORE.data[KEY_ESP32][KEY_EXCLUDE_COMPONENTS] = excluded # Initialize Arduino library tracking - cg.add_library() auto-enables libraries CORE.data[KEY_ESP32][KEY_ARDUINO_LIBRARIES] = set() - CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse( - config[CONF_FRAMEWORK][CONF_VERSION] - ) + framework_ver = cv.Version.parse(config[CONF_FRAMEWORK][CONF_VERSION]) + CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = framework_ver + + # Store the underlying IDF version for framework-agnostic checks + if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF: + CORE.data[KEY_ESP32][KEY_IDF_VERSION] = framework_ver + elif (idf_ver := ARDUINO_IDF_VERSION_LOOKUP.get(framework_ver)) is not None: + CORE.data[KEY_ESP32][KEY_IDF_VERSION] = idf_ver + else: + raise cv.Invalid( + f"Arduino version {framework_ver} has no known ESP-IDF version mapping. " + "Please update ARDUINO_IDF_VERSION_LOOKUP.", + path=[CONF_FRAMEWORK, CONF_VERSION], + ) CORE.data[KEY_ESP32][KEY_BOARD] = config[CONF_BOARD] CORE.data[KEY_ESP32][KEY_FLASH_SIZE] = config[CONF_FLASH_SIZE] @@ -600,10 +640,12 @@ def _format_framework_espidf_version( ext = "tar.xz" else: ext = "zip" - # Build version string with dot-separated extra (e.g., "5.5.3.1" not "5.5.3-1") + # Build version string with extra separator based on type: + # numeric extra uses dot (e.g., "5.5.3.1"), string extra uses dash (e.g., "6.0.0-rc1") ver_str = f"{ver.major}.{ver.minor}.{ver.patch}" if ver.extra: - ver_str += f".{ver.extra}" + sep = "." if str(ver.extra).isdigit() else "-" + ver_str += f"{sep}{ver.extra}" if release: return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{ver_str}.{release}/esp-idf-v{ver_str}.{ext}" return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{ver_str}/esp-idf-v{ver_str}.{ext}" @@ -674,9 +716,15 @@ ARDUINO_IDF_VERSION_LOOKUP = { ESP_IDF_FRAMEWORK_VERSION_LOOKUP = { "recommended": cv.Version(5, 5, 3, "1"), "latest": cv.Version(5, 5, 3, "1"), - "dev": cv.Version(5, 5, 3, "1"), + "dev": cv.Version(5, 5, 4), } ESP_IDF_PLATFORM_VERSION_LOOKUP = { + cv.Version( + 6, 0, 0 + ): "https://github.com/pioarduino/platform-espressif32.git#prep_IDF6", + cv.Version( + 5, 5, 4 + ): "https://github.com/pioarduino/platform-espressif32.git#develop", cv.Version(5, 5, 3, "1"): cv.Version(55, 3, 37), cv.Version(5, 5, 3): cv.Version(55, 3, 37), cv.Version(5, 5, 2): cv.Version(55, 3, 37), @@ -868,6 +916,13 @@ def final_validate(config): path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_MINIMUM_CHIP_REVISION], ) ) + if config[CONF_VARIANT] != VARIANT_ESP32 and advanced[CONF_SRAM1_AS_IRAM]: + errs.append( + cv.Invalid( + f"'{CONF_SRAM1_AS_IRAM}' is only supported on {VARIANT_ESP32}", + path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_SRAM1_AS_IRAM], + ) + ) if ( config[CONF_VARIANT] != VARIANT_ESP32P4 and config.get(CONF_ENGINEERING_SAMPLE) is not None @@ -933,6 +988,47 @@ def final_validate(config): ) # disable the rollback feature anyway since it can't be used. advanced[CONF_ENABLE_OTA_ROLLBACK] = False + if signed_ota := advanced.get(CONF_SIGNED_OTA_VERIFICATION): + scheme = signed_ota[CONF_SIGNING_SCHEME] + variant = config[CONF_VARIANT] + scheme_variant_conflicts = { + "ecdsa256": (SIGNED_OTA_RSA_ONLY_VARIANTS, "rsa3072"), + "rsa3072": (SIGNED_OTA_ECC_ONLY_VARIANTS, "ecdsa256"), + } + if (conflict := scheme_variant_conflicts.get(scheme)) and variant in conflict[ + 0 + ]: + errs.append( + cv.Invalid( + f"Signing scheme '{scheme}' is not supported on " + f"{VARIANT_FRIENDLY[variant]}. Use '{conflict[1]}' instead.", + path=[ + CONF_FRAMEWORK, + CONF_ADVANCED, + CONF_SIGNED_OTA_VERIFICATION, + CONF_SIGNING_SCHEME, + ], + ) + ) + if CONF_OTA not in full_config: + _LOGGER.warning( + "Signed OTA verification is enabled but no OTA component is configured. " + "The initial firmware will be signed but OTA updates won't be possible " + "until an OTA component is added." + ) + if CONF_SIGNING_KEY in signed_ota: + _LOGGER.info( + "Signed OTA verification is enabled. Keep your signing key safe! " + "If you lose the signing key, you will NOT be able to OTA update " + "devices running firmware signed with this key. " + "Without the key, you'll need to reflash via serial." + ) + else: + _LOGGER.info( + "Signed OTA verification is configured with a public verification key. " + "Binaries will NOT be signed automatically during build. " + "You must sign them externally before flashing." + ) if errs: raise cv.MultipleInvalid(errs) @@ -974,6 +1070,7 @@ KEY_USB_SERIAL_JTAG_SECONDARY_REQUIRED = "usb_serial_jtag_secondary_required" KEY_MBEDTLS_PEER_CERT_REQUIRED = "mbedtls_peer_cert_required" KEY_MBEDTLS_PKCS7_REQUIRED = "mbedtls_pkcs7_required" KEY_FATFS_REQUIRED = "fatfs_required" +KEY_MBEDTLS_SHA512_REQUIRED = "mbedtls_sha512_required" def require_vfs_select() -> None: @@ -1043,6 +1140,25 @@ def require_mbedtls_pkcs7() -> None: CORE.data[KEY_ESP32][KEY_MBEDTLS_PKCS7_REQUIRED] = True +def require_mbedtls_sha512() -> None: + """Mark that mbedTLS SHA-384/SHA-512 support is required by a component. + + Call this from components that need to verify TLS certificates or signatures + using SHA-384 or SHA-512 algorithms. This prevents CONFIG_MBEDTLS_SHA384_C + and CONFIG_MBEDTLS_SHA512_C from being disabled. + """ + CORE.data[KEY_ESP32][KEY_MBEDTLS_SHA512_REQUIRED] = True + + +def idf_version() -> cv.Version: + """Return the underlying ESP-IDF version regardless of framework choice. + + For ESP-IDF builds this is the framework version directly. + For Arduino builds this is the mapped IDF version from ARDUINO_IDF_VERSION_LOOKUP. + """ + return CORE.data[KEY_ESP32][KEY_IDF_VERSION] + + def require_fatfs() -> None: """Mark that FATFS support is required by a component. @@ -1095,6 +1211,7 @@ FRAMEWORK_SCHEMA = cv.Schema( cv.Optional(CONF_MINIMUM_CHIP_REVISION): cv.one_of( *ESP32_CHIP_REVISIONS ), + cv.Optional(CONF_SRAM1_AS_IRAM, default=False): cv.boolean, # DHCP server is needed for WiFi AP mode. When WiFi component is used, # it will handle disabling DHCP server when AP is not configured. # Default to false (disabled) when WiFi is not used. @@ -1123,6 +1240,18 @@ FRAMEWORK_SCHEMA = cv.Schema( min=8192, max=32768 ), cv.Optional(CONF_ENABLE_OTA_ROLLBACK, default=True): cv.boolean, + cv.Optional(CONF_SIGNED_OTA_VERIFICATION): cv.All( + cv.Schema( + { + cv.Optional(CONF_SIGNING_KEY): cv.file_, + cv.Optional(CONF_VERIFICATION_KEY): cv.file_, + cv.Optional( + CONF_SIGNING_SCHEME, default="rsa3072" + ): cv.one_of(*SIGNING_SCHEMES, lower=True), + } + ), + cv.has_exactly_one_key(CONF_SIGNING_KEY, CONF_VERIFICATION_KEY), + ), cv.Optional( CONF_USE_FULL_CERTIFICATE_BUNDLE, default=False ): cv.boolean, @@ -1224,6 +1353,43 @@ def _set_default_framework(config): return config +RESERVED_PARTITION_NAMES = { + "nvs", + "app0", + "app1", + "otadata", + "eeprom", + "spiffs", + "phy_init", +} + +VALID_APP_SUBTYPES = {"factory", "test"} +VALID_DATA_SUBTYPES = { + "nvs", + "nvs_keys", + "spiffs", + "coredump", + "efuse", + "fat", + "undefined", + "littlefs", +} + + +def _validate_custom_partition(config: ConfigType) -> ConfigType: + """Voluptuous validator for custom partition schema.""" + try: + _validate_partition( + config[CONF_NAME], + config[CONF_TYPE], + config[CONF_SUBTYPE], + config[CONF_SIZE], + ) + except ValueError as e: + raise cv.Invalid(str(e)) from e + return config + + FLASH_SIZES = [ "2MB", "4MB", @@ -1238,7 +1404,9 @@ CONF_PARTITIONS = "partitions" CONFIG_SCHEMA = cv.All( cv.Schema( { - cv.Optional(CONF_BOARD): cv.string_strict, + cv.Optional(CONF_BOARD): cv.All( + cv.string_strict, cv.ByteLength(max=BOARD_MAX_LENGTH) + ), cv.Optional(CONF_CPU_FREQUENCY): cv.one_of( *FULL_CPU_FREQUENCIES, upper=True ), @@ -1246,7 +1414,28 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_FLASH_SIZE, default="4MB"): cv.one_of( *FLASH_SIZES, upper=True ), - cv.Optional(CONF_PARTITIONS): cv.file_, + cv.Optional(CONF_PARTITIONS): cv.Any( + cv.file_, + cv.ensure_list( + cv.All( + cv.Schema( + { + cv.Required(CONF_NAME): cv.string_strict, + cv.Required(CONF_TYPE): cv.All( + cv.Any(cv.string_strict, cv.int_range(0x40, 0xFE)), + cv.int_to_hex_string, + ), + cv.Required(CONF_SUBTYPE): cv.All( + cv.Any(cv.string_strict, cv.int_range(0, 0xFE)), + cv.int_to_hex_string, + ), + cv.Required(CONF_SIZE): cv.int_range(min=0x1000), + } + ), + _validate_custom_partition, + ), + ), + ), cv.Optional(CONF_VARIANT): cv.one_of(*VARIANTS, upper=True), cv.Optional(CONF_FRAMEWORK): FRAMEWORK_SCHEMA, } @@ -1485,7 +1674,7 @@ async def to_code(config): if conf[CONF_ADVANCED][CONF_ENABLE_FULL_PRINTF]: cg.add_define("USE_FULL_PRINTF") else: - for symbol in ("vprintf", "printf", "fprintf"): + for symbol in ("vprintf", "printf", "fprintf", "vfprintf"): cg.add_build_flag(f"-Wl,--wrap={symbol}") else: cg.add_build_flag("-DUSE_ARDUINO") @@ -1561,6 +1750,16 @@ async def to_code(config): for rev, flag in ESP32_CHIP_REVISIONS.items(): add_idf_sdkconfig_option(flag, rev == min_rev) cg.add_define("USE_ESP32_MIN_CHIP_REVISION_SET") + + # Use SRAM1 region as IRAM on ESP32 (original) variant + # This provides an additional 40KB of IRAM by using SRAM1 memory that was previously + # reserved for bootloader DRAM. Requires a bootloader from ESP-IDF v5.1 or later. + # WARNING: If the device has an old bootloader (pre-v5.1), the app will fail to boot. + # A USB flash will update the bootloader automatically. OTA updates do not. + # See: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/performance/ram-usage.html + if variant == VARIANT_ESP32 and conf[CONF_ADVANCED][CONF_SRAM1_AS_IRAM]: + add_idf_sdkconfig_option("CONFIG_ESP_SYSTEM_ESP32_SRAM1_REGION_AS_IRAM", True) + cg.add_define("USE_ESP32_SRAM1_AS_IRAM") add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM_FILENAME", "partitions.csv") @@ -1715,9 +1914,18 @@ async def to_code(config): if use_platformio: cg.add_platformio_option("board_build.partitions", "partitions.csv") if CONF_PARTITIONS in config: - add_extra_build_file( - "partitions.csv", CORE.relative_config_path(config[CONF_PARTITIONS]) - ) + if isinstance(config[CONF_PARTITIONS], list): + for partition in config[CONF_PARTITIONS]: + add_partition( + partition[CONF_NAME], + partition[CONF_TYPE], + partition[CONF_SUBTYPE], + partition[CONF_SIZE], + ) + else: + add_extra_build_file( + "partitions.csv", CORE.relative_config_path(config[CONF_PARTITIONS]) + ) if assertion_level := advanced.get(CONF_ASSERTION_LEVEL): for key, flag in ASSERTION_LEVELS.items(): @@ -1751,6 +1959,32 @@ async def to_code(config): add_idf_sdkconfig_option("CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE", True) cg.add_define("USE_OTA_ROLLBACK") + # Enable signed app verification without hardware secure boot + if signed_ota := advanced.get(CONF_SIGNED_OTA_VERIFICATION): + add_idf_sdkconfig_option("CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT", True) + add_idf_sdkconfig_option("CONFIG_SECURE_SIGNED_ON_UPDATE_NO_SECURE_BOOT", True) + + scheme = signed_ota[CONF_SIGNING_SCHEME] + for key, flag in SIGNING_SCHEMES.items(): + add_idf_sdkconfig_option(flag, scheme == key) + + if CONF_SIGNING_KEY in signed_ota: + # Private key mode — auto-sign binaries during build + add_idf_sdkconfig_option("CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES", True) + add_idf_sdkconfig_option( + "CONFIG_SECURE_BOOT_SIGNING_KEY", + str(signed_ota[CONF_SIGNING_KEY].resolve()), + ) + else: + # Public key mode — verification only, external signing required + add_idf_sdkconfig_option("CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES", False) + add_idf_sdkconfig_option( + "CONFIG_SECURE_BOOT_VERIFICATION_KEY", + str(signed_ota[CONF_VERIFICATION_KEY].resolve()), + ) + + cg.add_define("USE_OTA_SIGNED_VERIFICATION") + cg.add_define("ESPHOME_LOOP_TASK_STACK_SIZE", advanced[CONF_LOOP_TASK_STACK_SIZE]) cg.add_define( @@ -1802,6 +2036,33 @@ async def to_code(config): elif advanced[CONF_DISABLE_MBEDTLS_PKCS7]: add_idf_sdkconfig_option("CONFIG_MBEDTLS_PKCS7_C", False) + # Disable SHA-384 and SHA-512 in mbedTLS + # ESPHome doesn't use either algorithm. SHA-384 shares the same + # compression function as SHA-512 (mbedtls_internal_sha512_process), + # so both must be disabled to eliminate the ~3KB software fallback + # that IDF 6.0's PSA parallel engine always links in. + # On IDF < 6.0 these are a single config and hardware-only (no + # software fallback), so there was no code size cost to leaving + # them enabled. + # Components that need SHA-384/SHA-512 can call require_mbedtls_sha512() + if idf_version() >= cv.Version(6, 0, 0) and not CORE.data[KEY_ESP32].get( + KEY_MBEDTLS_SHA512_REQUIRED, False + ): + add_idf_sdkconfig_option("CONFIG_MBEDTLS_SHA384_C", False) + add_idf_sdkconfig_option("CONFIG_MBEDTLS_SHA512_C", False) + + # Disable PicolibC Newlib compatibility shim on IDF 6.0+ + # IDF 6.0 switched from Newlib to PicolibC. The shim provides thread-local + # stdin/stdout/stderr and getreent() for code compiled against Newlib. + # ESPHome doesn't link against Newlib-built libraries that use stdio. + # If a component needs it (e.g. precompiled Newlib binaries), re-enable via: + # esp32: + # framework: + # sdkconfig_options: + # CONFIG_LIBC_PICOLIBC_NEWLIB_COMPATIBILITY: "y" + if idf_version() >= cv.Version(6, 0, 0): + add_idf_sdkconfig_option("CONFIG_LIBC_PICOLIBC_NEWLIB_COMPATIBILITY", False) + # Disable regi2c control functions in IRAM # Only needed if using analog peripherals (ADC, DAC, etc.) from ISRs while cache is disabled if advanced[CONF_DISABLE_REGI2C_IN_IRAM]: @@ -1836,45 +2097,175 @@ async def to_code(config): CORE.add_job(_write_arduino_libraries_sdkconfig) -APP_PARTITION_SIZES = { - "2MB": 0x0C0000, # 768 KB - "4MB": 0x1C0000, # 1792 KB - "8MB": 0x3C0000, # 3840 KB - "16MB": 0x7C0000, # 7936 KB - "32MB": 0xFC0000, # 16128 KB +KEY_CUSTOM_PARTITIONS = "custom_partitions" + + +@dataclass +class PartitionEntry: + name: str + type: str + subtype: str + size: int + + +# Partition sizes (offsets auto-placed by gen_esp32part.py). +# These constants are the single source of truth — used in both +# the CSV generation and the overhead calculation. +BOOTLOADER_SIZE = 0x8000 +PARTITION_TABLE_SIZE = 0x1000 +FIRST_PARTITION_OFFSET = BOOTLOADER_SIZE + PARTITION_TABLE_SIZE +OTADATA_SIZE = 0x2000 +PHY_INIT_SIZE = 0x1000 +EEPROM_SIZE = 0x1000 # Arduino only +SPIFFS_SIZE = 0xF000 # Arduino only +ARDUINO_NVS_SIZE = 0x60000 +IDF_NVS_SIZE = 0x70000 + + +def _get_partition_overhead() -> int: + """Total non-app partition budget (system partitions + nvs + padding). + + Custom partitions are appended at the end and steal from app. + """ + # otadata + phy_init are followed by app0 which requires 64KB alignment, + # so pad up to the next 64KB boundary. + overhead = ( + FIRST_PARTITION_OFFSET + OTADATA_SIZE + PHY_INIT_SIZE + 0xFFFF + ) & ~0xFFFF + if CORE.using_arduino: + overhead += EEPROM_SIZE + SPIFFS_SIZE + ARDUINO_NVS_SIZE + else: + overhead += IDF_NVS_SIZE + return overhead + + +VALID_SUBTYPES: dict[str, set[str]] = { + "app": VALID_APP_SUBTYPES, + "data": VALID_DATA_SUBTYPES, } -def get_arduino_partition_csv(flash_size: str): - app_partition_size = APP_PARTITION_SIZES[flash_size] - eeprom_partition_size = 0x1000 # 4 KB - spiffs_partition_size = 0xF000 # 60 KB - - app0_partition_start = 0x010000 # 64 KB - app1_partition_start = app0_partition_start + app_partition_size - eeprom_partition_start = app1_partition_start + app_partition_size - spiffs_partition_start = eeprom_partition_start + eeprom_partition_size - - return f"""\ -nvs, data, nvs, 0x9000, 0x5000, -otadata, data, ota, 0xE000, 0x2000, -app0, app, ota_0, 0x{app0_partition_start:X}, 0x{app_partition_size:X}, -app1, app, ota_1, 0x{app1_partition_start:X}, 0x{app_partition_size:X}, -eeprom, data, 0x99, 0x{eeprom_partition_start:X}, 0x{eeprom_partition_size:X}, -spiffs, data, spiffs, 0x{spiffs_partition_start:X}, 0x{spiffs_partition_size:X} -""" +def _validate_partition( + name: str, p_type: str | int, subtype: str | int, size: int +) -> None: + """Validate partition parameters. Raises ValueError on invalid input.""" + if name in RESERVED_PARTITION_NAMES: + raise ValueError(f"Partition name '{name}' is reserved.") + if size % 0x1000 != 0: + raise ValueError("Partition size must be 4KB (0x1000) aligned.") + # Numeric or already-normalized hex types/subtypes skip string validation + if not isinstance(p_type, str) or p_type.startswith("0x"): + return + if p_type not in VALID_SUBTYPES: + raise ValueError( + f"Type '{p_type}' is invalid. Only 'app' and 'data' are allowed." + " Use numbers for custom types." + ) + if not isinstance(subtype, str) or subtype.startswith("0x"): + return + valid = VALID_SUBTYPES[p_type] + if subtype not in valid: + raise ValueError( + f"Subtype '{subtype}' is invalid for {p_type} type." + f" Only {', '.join(sorted(valid))} are allowed." + " Use numbers for custom subtypes." + ) -def get_idf_partition_csv(flash_size: str): - app_partition_size = APP_PARTITION_SIZES[flash_size] +def add_partition(name: str, p_type: str | int, subtype: str | int, size: int) -> None: + """Register a custom partition to be appended to the partition table. - return f"""\ -otadata, data, ota, , 0x2000, -phy_init, data, phy, , 0x1000, -app0, app, ota_0, , 0x{app_partition_size:X}, -app1, app, ota_1, , 0x{app_partition_size:X}, -nvs, data, nvs, , 0x6D000, -""" + Called from component to_code() to request additional flash partitions. + Size must be 4KB aligned. Integer types/subtypes are converted to hex strings. + """ + if name in CORE.data[KEY_ESP32].get(KEY_CUSTOM_PARTITIONS, {}): + raise ValueError(f"Partition name '{name}' is already defined.") + _validate_partition(name, p_type, subtype, size) + p_type_str = f"0x{p_type:X}" if isinstance(p_type, int) else p_type + subtype_str = f"0x{subtype:X}" if isinstance(subtype, int) else subtype + custom_partitions = CORE.data[KEY_ESP32].setdefault(KEY_CUSTOM_PARTITIONS, {}) + custom_partitions[name] = PartitionEntry( + name=name, type=p_type_str, subtype=subtype_str, size=size + ) + + +def _flash_size_to_bytes(flash_size_mb: str) -> int: + """Convert flash size string (e.g. '4MB') to bytes.""" + return int(flash_size_mb.removesuffix("MB")) * 1024 * 1024 + + +def _get_custom_partitions_total_size() -> int: + """Total size of custom partitions including alignment padding.""" + size = 0 + for partition in CORE.data[KEY_ESP32].get(KEY_CUSTOM_PARTITIONS, {}).values(): + if partition.type == "app": + size = (size + 0xFFFF) & ~0xFFFF # align to 64KB + size += partition.size + return size + + +def _get_app_partition_size(flash_size_mb: str) -> int: + flash_bytes = _flash_size_to_bytes(flash_size_mb) + custom_total = _get_custom_partitions_total_size() + # Align down to 64KB — app partitions require 64KB-aligned offsets, + # so the size must also be aligned to avoid unbudgeted padding. + raw_size = (flash_bytes - _get_partition_overhead() - custom_total) // 2 + app_size = raw_size & ~0xFFFF + wasted = (raw_size - app_size) * 2 + if wasted: + _LOGGER.info( + "Custom partitions cause %dKB of wasted flash due to 64KB app partition alignment.", + wasted // 1024, + ) + if app_size <= 0x10000: # 64 KB + raise ValueError( + "Custom partitions are too large to fit in the available flash size. " + "Reduce custom partition sizes." + ) + if app_size <= 0x80000: # 512 KB + _LOGGER.warning( + "App partition size is only %dKB. This may be too small for firmware with " + "many components. Consider reducing custom partition sizes or using a " + "larger flash chip.", + app_size // 1024, + ) + return app_size + + +def get_partition_csv(flash_size_mb: str) -> str: + app_size = _get_app_partition_size(flash_size_mb) + + partitions: list[PartitionEntry] = [ + PartitionEntry(name="otadata", type="data", subtype="ota", size=OTADATA_SIZE), + PartitionEntry(name="phy_init", type="data", subtype="phy", size=PHY_INIT_SIZE), + PartitionEntry(name="app0", type="app", subtype="ota_0", size=app_size), + PartitionEntry(name="app1", type="app", subtype="ota_1", size=app_size), + ] + if CORE.using_arduino: + partitions.append( + PartitionEntry(name="eeprom", type="data", subtype="0x99", size=EEPROM_SIZE) + ) + partitions.append( + PartitionEntry( + name="spiffs", type="data", subtype="spiffs", size=SPIFFS_SIZE + ) + ) + partitions.append( + PartitionEntry( + name="nvs", type="data", subtype="nvs", size=ARDUINO_NVS_SIZE + ) + ) + else: + partitions.append( + PartitionEntry(name="nvs", type="data", subtype="nvs", size=IDF_NVS_SIZE) + ) + partitions.extend(CORE.data[KEY_ESP32].get(KEY_CUSTOM_PARTITIONS, {}).values()) + + csv = "".join( + f"{p.name}, {p.type}, {p.subtype}, , 0x{p.size:X},\n" for p in partitions + ) + _LOGGER.debug("Partition table:\n%s", csv) + return csv def _format_sdkconfig_val(value: SdkconfigValueType) -> str: @@ -1982,16 +2373,10 @@ def copy_files(): if "partitions.csv" not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]: flash_size = CORE.data[KEY_ESP32][KEY_FLASH_SIZE] - if CORE.using_arduino: - write_file_if_changed( - CORE.relative_build_path("partitions.csv"), - get_arduino_partition_csv(flash_size), - ) - else: - write_file_if_changed( - CORE.relative_build_path("partitions.csv"), - get_idf_partition_csv(flash_size), - ) + write_file_if_changed( + CORE.relative_build_path("partitions.csv"), + get_partition_csv(flash_size), + ) # IDF build scripts look for version string to put in the build. # However, if the build path does not have an initialized git repo, # and no version.txt file exists, the CMake script fails for some setups. diff --git a/esphome/components/esp32/const.py b/esphome/components/esp32/const.py index 7874c1c759..d0d00723fc 100644 --- a/esphome/components/esp32/const.py +++ b/esphome/components/esp32/const.py @@ -15,6 +15,7 @@ KEY_PATH = "path" KEY_SUBMODULES = "submodules" KEY_EXTRA_BUILD_FILES = "extra_build_files" KEY_FULL_CERT_BUNDLE = "full_cert_bundle" +KEY_IDF_VERSION = "idf_version" VARIANT_ESP32 = "ESP32" VARIANT_ESP32C2 = "ESP32C2" diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index cba25bca2b..313818e601 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" #include "crash_handler.h" +#include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "preferences.h" @@ -15,7 +16,6 @@ #include void setup(); // NOLINT(readability-redundant-declaration) -void loop(); // NOLINT(readability-redundant-declaration) // Weak stub for initArduino - overridden when the Arduino component is present extern "C" __attribute__((weak)) void initArduino() {} @@ -53,9 +53,6 @@ void arch_init() { } void HOT arch_feed_wdt() { esp_task_wdt_reset(); } -uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; } -const char *progmem_read_ptr(const char *const *addr) { return *addr; } -uint16_t progmem_read_uint16(const uint16_t *addr) { return *addr; } uint32_t arch_get_cpu_cycle_count() { return esp_cpu_get_cycle_count(); } uint32_t arch_get_cpu_freq_hz() { uint32_t freq = 0; @@ -68,7 +65,7 @@ TaskHandle_t loop_task_handle = nullptr; // NOLINT(cppcoreguidelines-avoid-non- void loop_task(void *pv_params) { setup(); while (true) { - loop(); + App.loop(); } } diff --git a/esphome/components/esp32/crash_handler.cpp b/esphome/components/esp32/crash_handler.cpp index ecf30d7878..ed61b61936 100644 --- a/esphome/components/esp32/crash_handler.cpp +++ b/esphome/components/esp32/crash_handler.cpp @@ -59,6 +59,59 @@ static inline bool is_return_addr(uint32_t addr) { } #endif +// --- Architecture-specific backtrace helpers --- +// These run from IRAM during panic (no flash access). + +#if CONFIG_IDF_TARGET_ARCH_XTENSA +// Walk Xtensa backtrace from an exception frame, writing PCs to out[]. +// Returns number of entries written. +static uint8_t IRAM_ATTR walk_xtensa_backtrace(XtExcFrame *frame, uint32_t *out, uint8_t max) { + esp_backtrace_frame_t bt_frame = { + .pc = (uint32_t) frame->pc, + .sp = (uint32_t) frame->a1, + .next_pc = (uint32_t) frame->a0, + .exc_frame = frame, + }; + uint8_t count = 0; + uint32_t first_pc = esp_cpu_process_stack_pc(bt_frame.pc); + if (is_code_addr(first_pc)) { + out[count++] = first_pc; + } + while (count < max && bt_frame.next_pc != 0) { + if (!esp_backtrace_get_next_frame(&bt_frame)) + break; + uint32_t pc = esp_cpu_process_stack_pc(bt_frame.pc); + if (is_code_addr(pc)) { + out[count++] = pc; + } + } + return count; +} +#endif + +#if CONFIG_IDF_TARGET_ARCH_RISCV +// Capture RISC-V backtrace: MEPC + RA from registers, then stack scan. +// Returns total count; *reg_count receives number of register-sourced entries. +static uint8_t IRAM_ATTR capture_riscv_backtrace(RvExcFrame *frame, uint32_t *out, uint8_t max, uint8_t *reg_count) { + uint8_t count = 0; + if (is_code_addr(frame->mepc)) { + out[count++] = frame->mepc; + } + if (is_code_addr(frame->ra) && frame->ra != frame->mepc) { + out[count++] = frame->ra; + } + *reg_count = count; + auto *scan_start = (uint32_t *) frame->sp; + for (uint32_t i = 0; i < 64 && count < max; i++) { + uint32_t val = scan_start[i]; + if (is_code_addr(val) && val != frame->mepc && val != frame->ra) { + out[count++] = val; + } + } + return count; +} +#endif + // Raw crash data written by the panic handler wrapper. // Lives in .noinit so it survives software reset but contains garbage after power cycle. // Validated by magic marker. Static linkage since it's only used within this file. @@ -66,7 +119,7 @@ static inline bool is_return_addr(uint32_t addr) { // Magic is second to validate the data. Remaining fields can change between versions. // Version is uint32_t because it would be padded to 4 bytes anyway before the next // uint32_t field, so we use the full width rather than wasting 3 bytes of padding. -static constexpr uint32_t CRASH_DATA_VERSION = 1; +static constexpr uint32_t CRASH_DATA_VERSION = 2; struct RawCrashData { uint32_t version; uint32_t magic; @@ -77,6 +130,13 @@ struct RawCrashData { uint8_t pseudo_excause; // Whether cause is a pseudo exception (Xtensa SoC-level panic) uint32_t backtrace[MAX_BACKTRACE]; uint32_t cause; // Architecture-specific: exccause (Xtensa) or mcause (RISC-V) + uint8_t crashed_core; +#if SOC_CPU_CORES_NUM > 1 + static_assert(SOC_CPU_CORES_NUM == 2, "Dual-core logic assumes exactly 2 cores"); + uint8_t other_backtrace_count; + uint8_t other_reg_frame_count; + uint32_t other_backtrace[MAX_BACKTRACE]; +#endif }; static RawCrashData __attribute__((section(".noinit"))) s_raw_crash_data; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -100,13 +160,28 @@ void crash_handler_read_and_clear() { s_raw_crash_data.exception = 4; // Default to PANIC_EXCEPTION_FAULT if (s_raw_crash_data.pseudo_excause > 1) s_raw_crash_data.pseudo_excause = 0; + if (s_raw_crash_data.crashed_core >= SOC_CPU_CORES_NUM) + s_raw_crash_data.crashed_core = 0; +#if SOC_CPU_CORES_NUM > 1 + if (s_raw_crash_data.other_backtrace_count > MAX_BACKTRACE) + s_raw_crash_data.other_backtrace_count = MAX_BACKTRACE; + if (s_raw_crash_data.other_reg_frame_count > s_raw_crash_data.other_backtrace_count) + s_raw_crash_data.other_reg_frame_count = s_raw_crash_data.other_backtrace_count; +#endif } - // Clear magic regardless so we don't re-report on next normal reboot - s_raw_crash_data.magic = 0; + // Don't clear magic here — crash data must survive OTA rollback reboots. + // Magic is cleared by crash_handler_clear() after an API client receives the data. } bool crash_handler_has_data() { return s_crash_data_valid; } +void crash_handler_clear() { + // Only clear the magic so data doesn't survive the next reboot. + // Keep s_crash_data_valid so crash_handler_log() still works for + // additional API clients connecting during this boot session. + s_raw_crash_data.magic = 0; +} + // Look up the exception cause as a human-readable string. // Tables mirror ESP-IDF's panic_arch_fill_info() which uses local static arrays // not exposed via any public API. @@ -212,6 +287,36 @@ static const char *get_exception_type() { return "Unknown"; } +// Log backtrace entries, filtering stack-scanned addresses on RISC-V. +static void log_backtrace(const uint32_t *addrs, uint8_t count, uint8_t reg_frame_count) { + uint8_t bt_num = 0; + for (uint8_t i = 0; i < count; i++) { + uint32_t addr = addrs[i]; +#if CONFIG_IDF_TARGET_ARCH_RISCV + if (i >= reg_frame_count && !is_return_addr(addr)) + continue; + const char *source = (i < reg_frame_count) ? "backtrace" : "stack scan"; +#else + const char *source = "backtrace"; +#endif + ESP_LOGE(TAG, " BT%d: 0x%08" PRIX32 " (%s)", bt_num++, addr, source); + } +} + +// Append backtrace addresses to the addr2line hint buffer. +static int append_addrs_to_hint(char *buf, int size, int pos, const uint32_t *addrs, uint8_t count, + uint8_t reg_frame_count) { + for (uint8_t i = 0; i < count && pos < size - 12; i++) { + uint32_t addr = addrs[i]; +#if CONFIG_IDF_TARGET_ARCH_RISCV + if (i >= reg_frame_count && !is_return_addr(addr)) + continue; +#endif + pos += snprintf(buf + pos, size - pos, " 0x%08" PRIX32, addr); + } + return pos; +} + // Intentionally uses separate ESP_LOGE calls per line instead of combining into // one multi-line log message. This ensures each address appears as its own line // on the serial console, making it possible to see partial output if the device @@ -228,33 +333,28 @@ void crash_handler_log() { } else { ESP_LOGE(TAG, " Reason: %s", get_exception_type()); } + ESP_LOGE(TAG, " Crashed core: %d", s_raw_crash_data.crashed_core); ESP_LOGE(TAG, " PC: 0x%08" PRIX32 " (fault location)", s_raw_crash_data.pc); - uint8_t bt_num = 0; - for (uint8_t i = 0; i < s_raw_crash_data.backtrace_count; i++) { - uint32_t addr = s_raw_crash_data.backtrace[i]; -#if CONFIG_IDF_TARGET_ARCH_RISCV - // Register-sourced entries (MEPC/RA) are trusted; only filter stack-scanned ones. - if (i >= s_raw_crash_data.reg_frame_count && !is_return_addr(addr)) - continue; -#endif -#if CONFIG_IDF_TARGET_ARCH_RISCV - const char *source = (i < s_raw_crash_data.reg_frame_count) ? "backtrace" : "stack scan"; -#else - const char *source = "backtrace"; -#endif - ESP_LOGE(TAG, " BT%d: 0x%08" PRIX32 " (%s)", bt_num++, addr, source); + log_backtrace(s_raw_crash_data.backtrace, s_raw_crash_data.backtrace_count, s_raw_crash_data.reg_frame_count); + +#if SOC_CPU_CORES_NUM > 1 + if (s_raw_crash_data.other_backtrace_count > 0) { + int other_core = 1 - s_raw_crash_data.crashed_core; + ESP_LOGE(TAG, " Other core (%d) backtrace:", other_core); + log_backtrace(s_raw_crash_data.other_backtrace, s_raw_crash_data.other_backtrace_count, + s_raw_crash_data.other_reg_frame_count); } +#endif + // Build addr2line hint with all captured addresses for easy copy-paste char hint[256]; int pos = snprintf(hint, sizeof(hint), "Use: addr2line -pfiaC -e firmware.elf 0x%08" PRIX32, s_raw_crash_data.pc); - for (uint8_t i = 0; i < s_raw_crash_data.backtrace_count && pos < (int) sizeof(hint) - 12; i++) { - uint32_t addr = s_raw_crash_data.backtrace[i]; -#if CONFIG_IDF_TARGET_ARCH_RISCV - if (i >= s_raw_crash_data.reg_frame_count && !is_return_addr(addr)) - continue; + pos = append_addrs_to_hint(hint, sizeof(hint), pos, s_raw_crash_data.backtrace, s_raw_crash_data.backtrace_count, + s_raw_crash_data.reg_frame_count); +#if SOC_CPU_CORES_NUM > 1 + append_addrs_to_hint(hint, sizeof(hint), pos, s_raw_crash_data.other_backtrace, + s_raw_crash_data.other_backtrace_count, s_raw_crash_data.other_reg_frame_count); #endif - pos += snprintf(hint + pos, sizeof(hint) - pos, " 0x%08" PRIX32, addr); - } ESP_LOGE(TAG, "%s", hint); } @@ -276,68 +376,54 @@ void IRAM_ATTR __wrap_esp_panic_handler(panic_info_t *info) { s_raw_crash_data.reg_frame_count = 0; s_raw_crash_data.exception = (uint8_t) info->exception; s_raw_crash_data.pseudo_excause = info->pseudo_excause ? 1 : 0; + s_raw_crash_data.crashed_core = (uint8_t) info->core; +#if SOC_CPU_CORES_NUM > 1 + s_raw_crash_data.other_backtrace_count = 0; + s_raw_crash_data.other_reg_frame_count = 0; +#endif #if CONFIG_IDF_TARGET_ARCH_XTENSA // Xtensa: walk the backtrace using the public API if (info->frame != nullptr) { auto *xt_frame = (XtExcFrame *) info->frame; s_raw_crash_data.cause = xt_frame->exccause; - esp_backtrace_frame_t bt_frame = { - .pc = (uint32_t) xt_frame->pc, - .sp = (uint32_t) xt_frame->a1, - .next_pc = (uint32_t) xt_frame->a0, - .exc_frame = xt_frame, - }; - - uint8_t count = 0; - // First frame PC - uint32_t first_pc = esp_cpu_process_stack_pc(bt_frame.pc); - if (is_code_addr(first_pc)) { - s_raw_crash_data.backtrace[count++] = first_pc; - } - // Walk remaining frames - while (count < MAX_BACKTRACE && bt_frame.next_pc != 0) { - if (!esp_backtrace_get_next_frame(&bt_frame)) { - break; - } - uint32_t pc = esp_cpu_process_stack_pc(bt_frame.pc); - if (is_code_addr(pc)) { - s_raw_crash_data.backtrace[count++] = pc; - } - } - s_raw_crash_data.backtrace_count = count; + s_raw_crash_data.backtrace_count = walk_xtensa_backtrace(xt_frame, s_raw_crash_data.backtrace, MAX_BACKTRACE); } +#if SOC_CPU_CORES_NUM > 1 + // Capture the other core's backtrace from the global frame array. + // Both cores save their frames to g_exc_frames[] before esp_panic_handler + // is called, so the other core's frame is available here. + if (info->core >= 0 && info->core < SOC_CPU_CORES_NUM) { + int other_core = 1 - info->core; + auto *other_frame = (XtExcFrame *) g_exc_frames[other_core]; + if (other_frame != nullptr) { + s_raw_crash_data.other_backtrace_count = + walk_xtensa_backtrace(other_frame, s_raw_crash_data.other_backtrace, MAX_BACKTRACE); + } + } +#endif + #elif CONFIG_IDF_TARGET_ARCH_RISCV // RISC-V: capture MEPC + RA, then scan stack for code addresses if (info->frame != nullptr) { auto *rv_frame = (RvExcFrame *) info->frame; s_raw_crash_data.cause = rv_frame->mcause; - uint8_t count = 0; - - // Save MEPC (fault PC) and RA (return address) - if (is_code_addr(rv_frame->mepc)) { - s_raw_crash_data.backtrace[count++] = rv_frame->mepc; - } - if (is_code_addr(rv_frame->ra) && rv_frame->ra != rv_frame->mepc) { - s_raw_crash_data.backtrace[count++] = rv_frame->ra; - } - - // Track how many entries came from registers (MEPC/RA) so we can - // skip return-address validation for them at log time. - s_raw_crash_data.reg_frame_count = count; - - // Scan stack for code addresses — captures broadly during panic, - // filtered by is_return_addr() at log time when flash is accessible. - auto *scan_start = (uint32_t *) rv_frame->sp; - for (uint32_t i = 0; i < 64 && count < MAX_BACKTRACE; i++) { - uint32_t val = scan_start[i]; - if (is_code_addr(val) && val != rv_frame->mepc && val != rv_frame->ra) { - s_raw_crash_data.backtrace[count++] = val; - } - } - s_raw_crash_data.backtrace_count = count; + s_raw_crash_data.backtrace_count = + capture_riscv_backtrace(rv_frame, s_raw_crash_data.backtrace, MAX_BACKTRACE, &s_raw_crash_data.reg_frame_count); } + +#if SOC_CPU_CORES_NUM > 1 + // Capture the other core's backtrace from the global frame array. + if (info->core >= 0 && info->core < SOC_CPU_CORES_NUM) { + int other_core = 1 - info->core; + auto *other_frame = (RvExcFrame *) g_exc_frames[other_core]; + if (other_frame != nullptr) { + s_raw_crash_data.other_backtrace_count = capture_riscv_backtrace( + other_frame, s_raw_crash_data.other_backtrace, MAX_BACKTRACE, &s_raw_crash_data.other_reg_frame_count); + } + } +#endif #endif // Write version and magic last — ensures all data is written before we mark it valid diff --git a/esphome/components/esp32/crash_handler.h b/esphome/components/esp32/crash_handler.h index 97a4d4e116..c5e7d145ec 100644 --- a/esphome/components/esp32/crash_handler.h +++ b/esphome/components/esp32/crash_handler.h @@ -4,12 +4,18 @@ namespace esphome::esp32 { -/// Read crash data from NOINIT memory and clear the magic marker. +/// Read and validate crash data from NOINIT memory. +/// Does not clear the magic marker — call crash_handler_clear() after +/// the data has been delivered to an API client so it survives OTA rollback reboots. void crash_handler_read_and_clear(); /// Log crash data if a crash was detected on previous boot. void crash_handler_log(); +/// Clear the magic marker and mark crash data as consumed. +/// Call after the data has been delivered to an API client. +void crash_handler_clear(); + /// Returns true if crash data was found this boot. bool crash_handler_has_data(); diff --git a/esphome/components/esp32/gpio_esp32.py b/esphome/components/esp32/gpio_esp32.py index b3166cf822..fec257f90b 100644 --- a/esphome/components/esp32/gpio_esp32.py +++ b/esphome/components/esp32/gpio_esp32.py @@ -28,7 +28,11 @@ def esp32_validate_gpio_pin(value: int) -> int: raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)") if value in _ESP_SDIO_PINS: raise cv.Invalid( - f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})" + f"This pin cannot be used on ESP32s and is already used by the flash interface" + f" (function: {_ESP_SDIO_PINS[value]})." + f" If you are using an ESP32 module that uses a different flash pin" + f" configuration (e.g. ESP32-PICO-V3-02), you can set" + f" 'ignore_pin_validation_error: true' to bypass this check." ) if 9 <= value <= 10: _LOGGER.warning( diff --git a/esphome/components/esp32/gpio_esp32_c3.py b/esphome/components/esp32/gpio_esp32_c3.py index 93e0b97093..6eb002f3f0 100644 --- a/esphome/components/esp32/gpio_esp32_c3.py +++ b/esphome/components/esp32/gpio_esp32_c3.py @@ -14,6 +14,8 @@ _ESP32C3_SPI_PSRAM_PINS = { 17: "SPIQ", } +_ESP32C3_USB_JTAG_PINS = {18, 19} + _ESP32C3_STRAPPING_PINS = {2, 8, 9} _LOGGER = logging.getLogger(__name__) @@ -26,6 +28,12 @@ def esp32_c3_validate_gpio_pin(value: int) -> int: raise cv.Invalid( f"This pin cannot be used on ESP32-C3s and is already used by the SPI/PSRAM interface (function: {_ESP32C3_SPI_PSRAM_PINS[value]})" ) + if value in _ESP32C3_USB_JTAG_PINS: + _LOGGER.warning( + "GPIO%d is used by the USB-Serial-JTAG interface." + " Using this pin as GPIO will conflict with USB-Serial-JTAG.", + value, + ) return value diff --git a/esphome/components/esp32/gpio_esp32_c6.py b/esphome/components/esp32/gpio_esp32_c6.py index cfd3bca833..bd7bb9e220 100644 --- a/esphome/components/esp32/gpio_esp32_c6.py +++ b/esphome/components/esp32/gpio_esp32_c6.py @@ -18,18 +18,26 @@ _ESP32C6_SPI_PSRAM_PINS = { 30: "SPID", } -_ESP32C6_STRAPPING_PINS = {8, 9, 15} +_ESP32C6_USB_JTAG_PINS = {12, 13} + +_ESP32C6_STRAPPING_PINS = {4, 5, 8, 9, 15} _LOGGER = logging.getLogger(__name__) def esp32_c6_validate_gpio_pin(value: int) -> int: - if value < 0 or value > 23: - raise cv.Invalid(f"Invalid pin number: {value} (must be 0-23)") + if value < 0 or value > 30: + raise cv.Invalid(f"Invalid pin number: {value} (must be 0-30)") if value in _ESP32C6_SPI_PSRAM_PINS: raise cv.Invalid( f"This pin cannot be used on ESP32-C6s and is already used by the SPI/PSRAM interface (function: {_ESP32C6_SPI_PSRAM_PINS[value]})" ) + if value in _ESP32C6_USB_JTAG_PINS: + _LOGGER.warning( + "GPIO%d is used by the USB-Serial-JTAG interface." + " Using this pin as GPIO will conflict with USB-Serial-JTAG.", + value, + ) return value @@ -39,8 +47,8 @@ def esp32_c6_validate_supports(value: dict[str, Any]) -> dict[str, Any]: mode = value[CONF_MODE] is_input = mode[CONF_INPUT] - if num < 0 or num > 23: - raise cv.Invalid(f"Invalid pin number: {num} (must be 0-23)") + if num < 0 or num > 30: + raise cv.Invalid(f"Invalid pin number: {num} (must be 0-30)") if is_input: # All ESP32 pins support input mode pass diff --git a/esphome/components/esp32/gpio_esp32_h2.py b/esphome/components/esp32/gpio_esp32_h2.py index 5e7a6158f9..9dd6537694 100644 --- a/esphome/components/esp32/gpio_esp32_h2.py +++ b/esphome/components/esp32/gpio_esp32_h2.py @@ -9,7 +9,7 @@ _ESP32H2_SPI_FLASH_PINS = {6, 7, 15, 16, 17, 18, 19, 20, 21} _ESP32H2_USB_JTAG_PINS = {26, 27} -_ESP32H2_STRAPPING_PINS = {2, 3, 8, 9, 25} +_ESP32H2_STRAPPING_PINS = {8, 9, 25} _LOGGER = logging.getLogger(__name__) @@ -26,8 +26,8 @@ def esp32_h2_validate_gpio_pin(value: int) -> int: ) if value in _ESP32H2_USB_JTAG_PINS: _LOGGER.warning( - "GPIO%d is reserved for the USB-Serial-JTAG interface.\n" - "To use this pin as GPIO, USB-Serial-JTAG will be disabled.", + "GPIO%d is used by the USB-Serial-JTAG interface." + " Using this pin as GPIO will conflict with USB-Serial-JTAG.", value, ) diff --git a/esphome/components/esp32/gpio_esp32_p4.py b/esphome/components/esp32/gpio_esp32_p4.py index 865db92652..6e9227c501 100644 --- a/esphome/components/esp32/gpio_esp32_p4.py +++ b/esphome/components/esp32/gpio_esp32_p4.py @@ -20,8 +20,8 @@ def esp32_p4_validate_gpio_pin(value: int) -> int: raise cv.Invalid(f"Invalid pin number: {value} (must be 0-54)") if value in _ESP32P4_USB_JTAG_PINS: _LOGGER.warning( - "GPIO%d is reserved for the USB-Serial-JTAG interface.\n" - "To use this pin as GPIO, USB-Serial-JTAG will be disabled.", + "GPIO%d is used by the USB-Serial-JTAG interface." + " Using this pin as GPIO will conflict with USB-Serial-JTAG.", value, ) diff --git a/esphome/components/esp32/gpio_esp32_s3.py b/esphome/components/esp32/gpio_esp32_s3.py index cb0eb8178c..f528de4ccd 100644 --- a/esphome/components/esp32/gpio_esp32_s3.py +++ b/esphome/components/esp32/gpio_esp32_s3.py @@ -5,7 +5,7 @@ import esphome.config_validation as cv from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER from esphome.pins import check_strapping_pin -_ESP_32S3_SPI_PSRAM_PINS = { +_ESP32S3_SPI_PSRAM_PINS = { 26: "SPICS1", 27: "SPIHD", 28: "SPIWP", @@ -15,7 +15,7 @@ _ESP_32S3_SPI_PSRAM_PINS = { 32: "SPID", } -_ESP_32_ESP32_S3R8_PSRAM_PINS = { +_ESP32S3R8_PSRAM_PINS = { 33: "SPIIO4", 34: "SPIIO5", 35: "SPIIO6", @@ -23,7 +23,9 @@ _ESP_32_ESP32_S3R8_PSRAM_PINS = { 37: "SPIDQS", } -_ESP_32S3_STRAPPING_PINS = {0, 3, 45, 46} +_ESP32S3_USB_JTAG_PINS = {19, 20} + +_ESP32S3_STRAPPING_PINS = {0, 3, 45, 46} _LOGGER = logging.getLogger(__name__) @@ -32,11 +34,11 @@ def esp32_s3_validate_gpio_pin(value: int) -> int: if value < 0 or value > 48: raise cv.Invalid(f"Invalid pin number: {value} (must be 0-48)") - if value in _ESP_32S3_SPI_PSRAM_PINS: + if value in _ESP32S3_SPI_PSRAM_PINS: raise cv.Invalid( - f"This pin cannot be used on ESP32-S3s and is already used by the SPI/PSRAM interface(function: {_ESP_32S3_SPI_PSRAM_PINS[value]})" + f"This pin cannot be used on ESP32-S3s and is already used by the SPI/PSRAM interface(function: {_ESP32S3_SPI_PSRAM_PINS[value]})" ) - if value in _ESP_32_ESP32_S3R8_PSRAM_PINS: + if value in _ESP32S3R8_PSRAM_PINS: _LOGGER.warning( "GPIO%d is used by the PSRAM interface on ESP32-S3R8 / ESP32-S3R8V and should be avoided on these models", value, @@ -46,6 +48,12 @@ def esp32_s3_validate_gpio_pin(value: int) -> int: # These pins are not exposed in GPIO mux (reason unknown) # but they're missing from IO_MUX list in datasheet raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32-S3s.") + if value in _ESP32S3_USB_JTAG_PINS: + _LOGGER.warning( + "GPIO%d is used by the USB-Serial-JTAG interface." + " Using this pin as GPIO will conflict with USB-Serial-JTAG.", + value, + ) return value @@ -61,5 +69,5 @@ def esp32_s3_validate_supports(value: dict[str, Any]) -> dict[str, Any]: # All ESP32 pins support input mode pass - check_strapping_pin(value, _ESP_32S3_STRAPPING_PINS, _LOGGER) + check_strapping_pin(value, _ESP32S3_STRAPPING_PINS, _LOGGER) return value diff --git a/esphome/components/esp32/helpers.cpp b/esphome/components/esp32/helpers.cpp index 051b7ce162..afcec8bfc7 100644 --- a/esphome/components/esp32/helpers.cpp +++ b/esphome/components/esp32/helpers.cpp @@ -9,23 +9,19 @@ #include #include +#include "esphome/core/log.h" #include "esp_random.h" #include "esp_system.h" namespace esphome { -uint32_t random_uint32() { return esp_random(); } +static const char *const TAG = "esp32"; + bool random_bytes(uint8_t *data, size_t len) { esp_fill_random(data, len); return true; } -Mutex::Mutex() { handle_ = xSemaphoreCreateMutex(); } -Mutex::~Mutex() {} -void Mutex::lock() { xSemaphoreTake(this->handle_, portMAX_DELAY); } -bool Mutex::try_lock() { return xSemaphoreTake(this->handle_, 0) == pdTRUE; } -void Mutex::unlock() { xSemaphoreGive(this->handle_); } - // only affects the executing core // so should not be used as a mutex lock, only to get accurate timing IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); } @@ -70,22 +66,43 @@ LwIPLock::~LwIPLock() { #endif } +/// Read MAC and validate both the return code and content. +static bool read_valid_mac(uint8_t *mac, esp_err_t err) { return err == ESP_OK && mac_address_is_valid(mac); } + +static constexpr size_t MAC_ADDRESS_SIZE_BITS = MAC_ADDRESS_SIZE * 8; // 48 bits + void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter) #if defined(CONFIG_SOC_IEEE802154_SUPPORTED) // When CONFIG_SOC_IEEE802154_SUPPORTED is defined, esp_efuse_mac_get_default // returns the 802.15.4 EUI-64 address, so we read directly from eFuse instead. - if (has_custom_mac_address()) { - esp_efuse_read_field_blob(ESP_EFUSE_MAC_CUSTOM, mac, 48); - } else { - esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, 48); + // Both paths already read raw eFuse bytes, so there is no CRC-bypass fallback + // (unlike the non-IEEE802154 path where esp_efuse_mac_get_default does CRC checks). + if (has_custom_mac_address() && + read_valid_mac(mac, esp_efuse_read_field_blob(ESP_EFUSE_MAC_CUSTOM, mac, MAC_ADDRESS_SIZE_BITS))) { + return; + } + if (read_valid_mac(mac, esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, MAC_ADDRESS_SIZE_BITS))) { + return; } #else - if (has_custom_mac_address()) { - esp_efuse_mac_get_custom(mac); - } else { - esp_efuse_mac_get_default(mac); + if (has_custom_mac_address() && read_valid_mac(mac, esp_efuse_mac_get_custom(mac))) { + return; + } + if (read_valid_mac(mac, esp_efuse_mac_get_default(mac))) { + return; + } + // Default MAC read failed (e.g., eFuse CRC error) - try reading raw eFuse bytes + // directly, bypassing CRC validation. A MAC that passes mac_address_is_valid() + // (non-zero, non-broadcast, unicast) is almost certainly the real factory MAC + // with a corrupted CRC byte, which is far better than returning garbage or zeros. + if (read_valid_mac(mac, esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, MAC_ADDRESS_SIZE_BITS))) { + ESP_LOGW(TAG, "eFuse MAC CRC failed but raw bytes appear valid - using raw eFuse MAC"); + return; } #endif + // All methods failed - zero the MAC rather than returning garbage + ESP_LOGE(TAG, "Failed to read a valid MAC address from eFuse"); + memset(mac, 0, MAC_ADDRESS_SIZE); } void set_mac_address(uint8_t *mac) { esp_base_mac_addr_set(mac); } @@ -95,9 +112,11 @@ bool has_custom_mac_address() { uint8_t mac[6]; // do not use 'esp_efuse_mac_get_custom(mac)' because it drops an error in the logs whenever it fails #ifndef USE_ESP32_VARIANT_ESP32 - return (esp_efuse_read_field_blob(ESP_EFUSE_USER_DATA_MAC_CUSTOM, mac, 48) == ESP_OK) && mac_address_is_valid(mac); + return (esp_efuse_read_field_blob(ESP_EFUSE_USER_DATA_MAC_CUSTOM, mac, MAC_ADDRESS_SIZE_BITS) == ESP_OK) && + mac_address_is_valid(mac); #else - return (esp_efuse_read_field_blob(ESP_EFUSE_MAC_CUSTOM, mac, 48) == ESP_OK) && mac_address_is_valid(mac); + return (esp_efuse_read_field_blob(ESP_EFUSE_MAC_CUSTOM, mac, MAC_ADDRESS_SIZE_BITS) == ESP_OK) && + mac_address_is_valid(mac); #endif #else return false; diff --git a/esphome/components/esp32/iram_fix.py.script b/esphome/components/esp32/iram_fix.py.script index 0d23f9a81b..b656d7d1a6 100644 --- a/esphome/components/esp32/iram_fix.py.script +++ b/esphome/components/esp32/iram_fix.py.script @@ -4,12 +4,40 @@ import re # pylint: disable=E0602 Import("env") # noqa -# IRAM size for testing mode (2MB - large enough to accommodate grouped tests) -TESTING_IRAM_SIZE = 0x200000 +# Memory sizes for testing mode (large enough to accommodate grouped tests) +TESTING_IRAM_SIZE = 0x200000 # 2MB +TESTING_DRAM_SIZE = 0x200000 # 2MB + + +def patch_segment(content, segment_name, new_size): + """Patch a memory segment's length in linker script content. + + Handles both single-line and multi-line segment definitions, e.g.: + iram0_0_seg (RX) : org = 0x40080000, len = 0x20000 + 0x0 + or split across lines: + dram0_0_seg (RW) : org = 0x3FFB0000 + 0xdb5c, + len = 0x2c200 - 0xdb5c + + Args: + content: Full linker script content as string + segment_name: Name of the segment (e.g., 'iram0_0_seg') + new_size: New size as integer + + Returns: + Tuple of (new_content, was_patched) + """ + # Match segment name through to "len = " allowing newlines between org and len + pattern = rf'({re.escape(segment_name)}\s*\([^)]*\)\s*:\s*org\s*=\s*.+?,\s*len\s*=\s*)(\S+[^\n]*)' + if match := re.search(pattern, content, re.DOTALL): + replacement = f"{match.group(1)}{new_size:#x}" + new_content = content[:match.start()] + replacement + content[match.end():] + if new_content != content: + return new_content, True + return content, False def patch_idf_linker_script(source, target, env): - """Patch ESP-IDF linker script to increase IRAM size for testing mode.""" + """Patch ESP-IDF linker script to increase IRAM and DRAM size for testing mode.""" # Check if we're in testing mode by looking for the define build_flags = env.get("BUILD_FLAGS", []) testing_mode = any("-DESPHOME_TESTING_MODE" in flag for flag in build_flags) @@ -34,36 +62,22 @@ def patch_idf_linker_script(source, target, env): print(f"ESPHome: Error reading linker script: {e}") return - # Check if this file contains iram0_0_seg - if 'iram0_0_seg' not in content: - print(f"ESPHome: Warning - iram0_0_seg not found in {memory_ld}") - return + patches = [] - # Look for iram0_0_seg definition and increase its length - # ESP-IDF format can be: - # iram0_0_seg (RX) : org = 0x40080000, len = 0x20000 + 0x0 - # or more complex with nested parentheses: - # iram0_0_seg (RX) : org = (0x40370000 + 0x4000), len = (((0x403CB700 - (0x40378000 - 0x3FC88000)) - 0x3FC88000) + 0x8000 - 0x4000) - # We want to change len to TESTING_IRAM_SIZE for testing + content, patched = patch_segment(content, 'iram0_0_seg', TESTING_IRAM_SIZE) + if patched: + patches.append(f"IRAM={TESTING_IRAM_SIZE:#x}") - # Use a more robust approach: find the line and manually parse it - lines = content.split('\n') - for i, line in enumerate(lines): - if 'iram0_0_seg' in line and 'len' in line: - # Find the position of "len = " and replace everything after it until the end of the statement - match = re.search(r'(iram0_0_seg\s*\([^)]*\)\s*:\s*org\s*=\s*(?:\([^)]+\)|0x[0-9a-fA-F]+)\s*,\s*len\s*=\s*)(.+?)(\s*)$', line) - if match: - lines[i] = f"{match.group(1)}{TESTING_IRAM_SIZE:#x}{match.group(3)}" - break + content, patched = patch_segment(content, 'dram0_0_seg', TESTING_DRAM_SIZE) + if patched: + patches.append(f"DRAM={TESTING_DRAM_SIZE:#x}") - updated = '\n'.join(lines) - - if updated != content: + if patches: with open(memory_ld, "w") as f: - f.write(updated) - print(f"ESPHome: Patched IRAM size to {TESTING_IRAM_SIZE:#x} in {memory_ld} for testing mode") + f.write(content) + print(f"ESPHome: Patched {', '.join(patches)} in {memory_ld} for testing mode") else: - print(f"ESPHome: Warning - could not patch iram0_0_seg in {memory_ld}") + print(f"ESPHome: Warning - could not patch memory segments in {memory_ld}") # Hook into the build process before linking diff --git a/esphome/components/esp32/post_build.py.script b/esphome/components/esp32/post_build.py.script index 5ef5860687..8d13214259 100644 --- a/esphome/components/esp32/post_build.py.script +++ b/esphome/components/esp32/post_build.py.script @@ -8,6 +8,99 @@ import shutil # noqa: E402 from glob import glob # noqa: E402 +def _parse_sdkconfig(sdkconfig_path): + """Parse sdkconfig file and return a dict of CONFIG_ options.""" + options = {} + try: + for line in sdkconfig_path.read_text().splitlines(): + line = line.strip() + if line and not line.startswith("#") and "=" in line: + key, _, value = line.partition("=") + # Strip surrounding quotes from string values + if value.startswith('"') and value.endswith('"'): + value = value[1:-1] + options[key] = value + except FileNotFoundError: + pass + return options + + +def sign_firmware(source, target, env): + """ + Sign the firmware binary using espsecure.py if signed OTA verification is enabled. + Reads signing configuration from sdkconfig. + """ + build_dir = pathlib.Path(env.subst("$BUILD_DIR")) + project_dir = pathlib.Path(env.subst("$PROJECT_DIR")) + pioenv = env.subst("$PIOENV") + sdkconfig = _parse_sdkconfig(project_dir / f"sdkconfig.{pioenv}") + + if sdkconfig.get("CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT") != "y": + return + + if sdkconfig.get("CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES") != "y": + print("Signed OTA verification enabled but build-time signing disabled.") + print("You must sign the firmware externally before flashing.") + return + + signing_key = sdkconfig.get("CONFIG_SECURE_BOOT_SIGNING_KEY") + if not signing_key: + print("Error: CONFIG_SECURE_BOOT_SIGNING_KEY not set in sdkconfig") + env.Exit(1) + return + + signing_key_path = pathlib.Path(signing_key) + if not signing_key_path.exists(): + print(f"Error: Signing key not found: {signing_key_path}") + env.Exit(1) + return + + # ESPHome only exposes RSA3072 and ECDSA256 (both Secure Boot V2 schemes), + # so the espsecure signature version is always 2. + sign_version = "2" + + firmware_name = os.path.basename(env.subst("$PROGNAME")) + ".bin" + firmware_path = build_dir / firmware_name + + if not firmware_path.exists(): + print(f"Error: Firmware binary not found: {firmware_path}") + env.Exit(1) + return + + python_exe = f'"{env.subst("$PYTHONEXE")}"' + unsigned_path = firmware_path.with_suffix(".unsigned.bin") + + # Keep a copy of the unsigned binary + shutil.copyfile(str(firmware_path), str(unsigned_path)) + + cmd = [ + python_exe, + "-m", + "espsecure", + "sign-data", + "--version", + sign_version, + "--keyfile", + str(signing_key_path), + "--output", + str(firmware_path), + str(unsigned_path), + ] + + print(f"Signing firmware with key: {signing_key_path.name}") + result = env.Execute( + env.VerboseAction(" ".join(cmd), "Signing firmware with espsecure") + ) + + if result == 0: + print("Successfully signed firmware") + else: + print(f"Error: espsecure sign_data failed with code {result}") + # Restore unsigned binary on failure + shutil.copyfile(str(unsigned_path), str(firmware_path)) + env.Exit(1) + + def merge_factory_bin(source, target, env): """ Merges all flash sections into a single .factory.bin using esptool. @@ -124,7 +217,8 @@ def esp32_copy_ota_bin(source, target, env): print(f"Copied firmware to {new_file_name}") -# Run merge first, then ota copy second +# Run signing first, then merge, then ota copy +env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", sign_firmware) # noqa: F821 env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", merge_factory_bin) # noqa: F821 env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_copy_ota_bin) # noqa: F821 diff --git a/esphome/components/esp32/preference_backend.h b/esphome/components/esp32/preference_backend.h new file mode 100644 index 0000000000..893bc35f0c --- /dev/null +++ b/esphome/components/esp32/preference_backend.h @@ -0,0 +1,27 @@ +#pragma once +#ifdef USE_ESP32 + +#include +#include + +namespace esphome::esp32 { + +class ESP32PreferenceBackend final { + public: + bool save(const uint8_t *data, size_t len); + bool load(uint8_t *data, size_t len); + + uint32_t key; + uint32_t nvs_handle; +}; + +class ESP32Preferences; +ESP32Preferences *get_preferences(); + +} // namespace esphome::esp32 + +namespace esphome { +using PreferenceBackend = esp32::ESP32PreferenceBackend; +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp index a3ef10b21f..bc0a34ebe8 100644 --- a/esphome/components/esp32/preferences.cpp +++ b/esphome/components/esp32/preferences.cpp @@ -1,18 +1,16 @@ #ifdef USE_ESP32 +#include "preferences.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/core/preferences.h" #include #include #include -#include #include -namespace esphome { -namespace esp32 { +namespace esphome::esp32 { -static const char *const TAG = "esp32.preferences"; +static const char *const TAG = "preferences"; // Buffer size for converting uint32_t to string: max "4294967295" (10 chars) + null terminator + 1 padding static constexpr size_t KEY_BUFFER_SIZE = 12; @@ -24,185 +22,179 @@ struct NVSData { static std::vector s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -class ESP32PreferenceBackend : public ESPPreferenceBackend { - public: - uint32_t key; - uint32_t nvs_handle; - bool save(const uint8_t *data, size_t len) override { - // try find in pending saves and update that - for (auto &obj : s_pending_save) { - if (obj.key == this->key) { - obj.data.set(data, len); - return true; - } - } - NVSData save{}; - save.key = this->key; - save.data.set(data, len); - s_pending_save.push_back(std::move(save)); - ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len); - return true; - } - bool load(uint8_t *data, size_t len) override { - // try find in pending saves and load from that - for (auto &obj : s_pending_save) { - if (obj.key == this->key) { - if (obj.data.size() != len) { - // size mismatch - return false; - } - memcpy(data, obj.data.data(), len); - return true; - } - } - - char key_str[KEY_BUFFER_SIZE]; - snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key); - size_t actual_len; - esp_err_t err = nvs_get_blob(this->nvs_handle, key_str, nullptr, &actual_len); - if (err != 0) { - ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err)); - return false; - } - if (actual_len != len) { - ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len); - return false; - } - err = nvs_get_blob(this->nvs_handle, key_str, data, &len); - if (err != 0) { - ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err)); - return false; - } else { - ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key_str, len); - } - return true; - } -}; - -class ESP32Preferences : public ESPPreferences { - public: - uint32_t nvs_handle; - - void open() { - nvs_flash_init(); - esp_err_t err = nvs_open("esphome", NVS_READWRITE, &nvs_handle); - if (err == 0) - return; - - ESP_LOGW(TAG, "nvs_open failed: %s - erasing NVS", esp_err_to_name(err)); - nvs_flash_deinit(); - nvs_flash_erase(); - nvs_flash_init(); - - err = nvs_open("esphome", NVS_READWRITE, &nvs_handle); - if (err != 0) { - nvs_handle = 0; - } - } - ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { - return this->make_preference(length, type); - } - ESPPreferenceObject make_preference(size_t length, uint32_t type) override { - auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) - pref->nvs_handle = this->nvs_handle; - pref->key = type; - - return ESPPreferenceObject(pref); - } - - bool sync() override { - if (s_pending_save.empty()) +bool ESP32PreferenceBackend::save(const uint8_t *data, size_t len) { + // try find in pending saves and update that + for (auto &obj : s_pending_save) { + if (obj.key == this->key) { + obj.data.set(data, len); return true; - - ESP_LOGV(TAG, "Saving %zu items...", s_pending_save.size()); - int cached = 0, written = 0, failed = 0; - esp_err_t last_err = ESP_OK; - uint32_t last_key = 0; - - for (const auto &save : s_pending_save) { - char key_str[KEY_BUFFER_SIZE]; - snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key); - ESP_LOGVV(TAG, "Checking if NVS data %s has changed", key_str); - if (this->is_changed_(this->nvs_handle, save, key_str)) { - esp_err_t err = nvs_set_blob(this->nvs_handle, key_str, save.data.data(), save.data.size()); - ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.data.size()); - if (err != 0) { - ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", key_str, save.data.size(), esp_err_to_name(err)); - failed++; - last_err = err; - last_key = save.key; - continue; - } - written++; - } else { - ESP_LOGV(TAG, "NVS data not changed skipping %" PRIu32 " len=%zu", save.key, save.data.size()); - cached++; - } } - s_pending_save.clear(); + } + NVSData save{}; + save.key = this->key; + save.data.set(data, len); + s_pending_save.push_back(std::move(save)); + ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len); + return true; +} +bool ESP32PreferenceBackend::load(uint8_t *data, size_t len) { + // try find in pending saves and load from that + for (auto &obj : s_pending_save) { + if (obj.key == this->key) { + if (obj.data.size() != len) { + // size mismatch + return false; + } + memcpy(data, obj.data.data(), len); + return true; + } + } + + char key_str[KEY_BUFFER_SIZE]; + snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key); + size_t actual_len; + esp_err_t err = nvs_get_blob(this->nvs_handle, key_str, nullptr, &actual_len); + if (err != 0) { + ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err)); + return false; + } + if (actual_len != len) { + ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len); + return false; + } + err = nvs_get_blob(this->nvs_handle, key_str, data, &len); + if (err != 0) { + ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err)); + return false; + } else { + ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key_str, len); + } + return true; +} + +void ESP32Preferences::open() { + nvs_flash_init(); + esp_err_t err = nvs_open("esphome", NVS_READWRITE, &this->nvs_handle); + if (err == 0) + return; + + ESP_LOGW(TAG, "nvs_open failed: %s - erasing NVS", esp_err_to_name(err)); + nvs_flash_deinit(); + nvs_flash_erase(); + nvs_flash_init(); + + err = nvs_open("esphome", NVS_READWRITE, &this->nvs_handle); + if (err != 0) { + this->nvs_handle = 0; + } +} + +ESPPreferenceObject ESP32Preferences::make_preference(size_t length, uint32_t type) { + auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) + pref->nvs_handle = this->nvs_handle; + pref->key = type; + + return ESPPreferenceObject(pref); +} + +bool ESP32Preferences::sync() { + if (s_pending_save.empty()) + return true; + + ESP_LOGV(TAG, "Saving %zu items...", s_pending_save.size()); + int cached = 0, written = 0, failed = 0; + esp_err_t last_err = ESP_OK; + uint32_t last_key = 0; + + for (const auto &save : s_pending_save) { + char key_str[KEY_BUFFER_SIZE]; + snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key); + ESP_LOGVV(TAG, "Checking if NVS data %s has changed", key_str); + if (this->is_changed_(this->nvs_handle, save, key_str)) { + esp_err_t err = nvs_set_blob(this->nvs_handle, key_str, save.data.data(), save.data.size()); + ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.data.size()); + if (err != 0) { + ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", key_str, save.data.size(), esp_err_to_name(err)); + failed++; + last_err = err; + last_key = save.key; + continue; + } + written++; + } else { + ESP_LOGV(TAG, "NVS data not changed skipping %" PRIu32 " len=%zu", save.key, save.data.size()); + cached++; + } + } + s_pending_save.clear(); + + if (failed > 0) { + ESP_LOGE(TAG, "Writing %d items: %d cached, %d written, %d failed. Last error=%s for key=%" PRIu32, + cached + written + failed, cached, written, failed, esp_err_to_name(last_err), last_key); + } else if (written > 0) { ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written, failed); - if (failed > 0) { - ESP_LOGE(TAG, "Writing %d items failed. Last error=%s for key=%" PRIu32, failed, esp_err_to_name(last_err), - last_key); - } - - // note: commit on esp-idf currently is a no-op, nvs_set_blob always writes - esp_err_t err = nvs_commit(this->nvs_handle); - if (err != 0) { - ESP_LOGV(TAG, "nvs_commit() failed: %s", esp_err_to_name(err)); - return false; - } - - return failed == 0; + } else { + ESP_LOGV(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written, + failed); } - protected: - bool is_changed_(uint32_t nvs_handle, const NVSData &to_save, const char *key_str) { - size_t actual_len; - esp_err_t err = nvs_get_blob(nvs_handle, key_str, nullptr, &actual_len); - if (err != 0) { - ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err)); - return true; - } - // Check size first before allocating memory - if (actual_len != to_save.data.size()) { - return true; - } - // Most preferences are small, use stack buffer with heap fallback for large ones - SmallBufferWithHeapFallback<256> stored_data(actual_len); - err = nvs_get_blob(nvs_handle, key_str, stored_data.get(), &actual_len); - if (err != 0) { - ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err)); - return true; - } - return memcmp(to_save.data.data(), stored_data.get(), to_save.data.size()) != 0; + // note: commit on esp-idf currently is a no-op, nvs_set_blob always writes + esp_err_t err = nvs_commit(this->nvs_handle); + if (err != 0) { + ESP_LOGV(TAG, "nvs_commit() failed: %s", esp_err_to_name(err)); + return false; } - bool reset() override { - ESP_LOGD(TAG, "Erasing storage"); - s_pending_save.clear(); + return failed == 0; +} - nvs_flash_deinit(); - nvs_flash_erase(); - // Make the handle invalid to prevent any saves until restart - nvs_handle = 0; +bool ESP32Preferences::is_changed_(uint32_t nvs_handle, const NVSData &to_save, const char *key_str) { + size_t actual_len; + esp_err_t err = nvs_get_blob(nvs_handle, key_str, nullptr, &actual_len); + if (err != 0) { + ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err)); return true; } -}; + // Check size first before allocating memory + if (actual_len != to_save.data.size()) { + return true; + } + // Most preferences are small, use stack buffer with heap fallback for large ones + SmallBufferWithHeapFallback<256> stored_data(actual_len); + err = nvs_get_blob(nvs_handle, key_str, stored_data.get(), &actual_len); + if (err != 0) { + ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err)); + return true; + } + return memcmp(to_save.data.data(), stored_data.get(), to_save.data.size()) != 0; +} + +bool ESP32Preferences::reset() { + ESP_LOGD(TAG, "Erasing storage"); + s_pending_save.clear(); + + nvs_flash_deinit(); + nvs_flash_erase(); + // Make the handle invalid to prevent any saves until restart + this->nvs_handle = 0; + return true; +} static ESP32Preferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +ESP32Preferences *get_preferences() { return &s_preferences; } + void setup_preferences() { s_preferences.open(); global_preferences = &s_preferences; } -} // namespace esp32 +} // namespace esphome::esp32 +namespace esphome { ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - } // namespace esphome #endif // USE_ESP32 diff --git a/esphome/components/esp32/preferences.h b/esphome/components/esp32/preferences.h index e44213e4cf..0e187d87a9 100644 --- a/esphome/components/esp32/preferences.h +++ b/esphome/components/esp32/preferences.h @@ -1,12 +1,33 @@ #pragma once #ifdef USE_ESP32 -namespace esphome { -namespace esp32 { +#include "esphome/core/preference_backend.h" + +namespace esphome::esp32 { + +struct NVSData; + +class ESP32Preferences final : public PreferencesMixin { + public: + using PreferencesMixin::make_preference; + void open(); + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) { + return this->make_preference(length, type); + } + ESPPreferenceObject make_preference(size_t length, uint32_t type); + bool sync(); + bool reset(); + + uint32_t nvs_handle; + + protected: + bool is_changed_(uint32_t nvs_handle, const NVSData &to_save, const char *key_str); +}; void setup_preferences(); -} // namespace esp32 -} // namespace esphome +} // namespace esphome::esp32 + +DECLARE_PREFERENCE_ALIASES(esphome::esp32::ESP32Preferences) #endif // USE_ESP32 diff --git a/esphome/components/esp32/printf_stubs.cpp b/esphome/components/esp32/printf_stubs.cpp index c6f03bc363..386fbbd79d 100644 --- a/esphome/components/esp32/printf_stubs.cpp +++ b/esphome/components/esp32/printf_stubs.cpp @@ -2,10 +2,11 @@ * Linker wrap stubs for FILE*-based printf functions. * * ESP-IDF SDK components (gpio driver, ringbuf, log_write) reference - * fprintf(), printf(), and vprintf() which pull in newlib's _vfprintf_r - * (~11 KB). This is a separate implementation from _svfprintf_r (used by - * snprintf/vsnprintf) that handles FILE* stream I/O with buffering and - * locking. + * fprintf(), printf(), vprintf(), and vfprintf() which pull in the full + * printf implementation (~11 KB on newlib's _vfprintf_r, ~2.8 KB on + * picolibc's vfprintf). This is a separate implementation from the one + * used by snprintf/vsnprintf that handles FILE* stream I/O with buffering + * and locking. * * ESPHome replaces the ESP-IDF log handler via esp_log_set_vprintf_(), * so the SDK's vprintf() path is dead code at runtime. The fprintf() @@ -70,11 +71,15 @@ int __wrap_printf(const char *fmt, ...) { return len; } +int __wrap_vfprintf(FILE *stream, const char *fmt, va_list ap) { + char buf[PRINTF_BUFFER_SIZE]; + return write_printf_buffer(stream, buf, vsnprintf(buf, sizeof(buf), fmt, ap)); +} + int __wrap_fprintf(FILE *stream, const char *fmt, ...) { va_list ap; va_start(ap, fmt); - char buf[PRINTF_BUFFER_SIZE]; - int len = write_printf_buffer(stream, buf, vsnprintf(buf, sizeof(buf), fmt, ap)); + int len = __wrap_vfprintf(stream, fmt, ap); va_end(ap); return len; } diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index 43208eb87e..79d05049bf 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -7,7 +7,6 @@ from typing import Any from esphome import automation import esphome.codegen as cg -from esphome.components import socket from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant from esphome.components.esp32.const import VARIANT_ESP32C2 import esphome.config_validation as cv @@ -134,10 +133,38 @@ class HandlerCounts: _handler_counts = HandlerCounts() +def _add_callback( + parent_var: cg.MockObj, + method: str, + handler_var: cg.MockObj, + params: str, + call_args: str, +) -> None: + """Generate a lambda callback that forwards to a handler method. + + Uses a braced scope with a local pointer variable so the generated C++ + lambda captures only that pointer, avoiding GCC warnings about capturing + variables with static storage duration. + """ + cg.add( + cg.RawStatement( + f"{{ auto *h = {handler_var}; " + f"{parent_var}->{method}(" + f"[h]({params}) {{ h->{call_args}; }}); }}" + ) + ) + + def register_gap_event_handler(parent_var: cg.MockObj, handler_var: cg.MockObj) -> None: """Register a GAP event handler and track the count.""" _handler_counts.gap_event += 1 - cg.add(parent_var.register_gap_event_handler(handler_var)) + _add_callback( + parent_var, + "add_gap_event_callback", + handler_var, + "esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param", + "gap_event_handler(event, param)", + ) def register_gap_scan_event_handler( @@ -145,7 +172,13 @@ def register_gap_scan_event_handler( ) -> None: """Register a GAP scan event handler and track the count.""" _handler_counts.gap_scan_event += 1 - cg.add(parent_var.register_gap_scan_event_handler(handler_var)) + _add_callback( + parent_var, + "add_gap_scan_event_callback", + handler_var, + "const esphome::esp32_ble::BLEScanResult &scan_result", + "gap_scan_event_handler(scan_result)", + ) def register_gattc_event_handler( @@ -153,7 +186,13 @@ def register_gattc_event_handler( ) -> None: """Register a GATTc event handler and track the count.""" _handler_counts.gattc_event += 1 - cg.add(parent_var.register_gattc_event_handler(handler_var)) + _add_callback( + parent_var, + "add_gattc_event_callback", + handler_var, + "esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param", + "gattc_event_handler(event, gattc_if, param)", + ) def register_gatts_event_handler( @@ -161,7 +200,13 @@ def register_gatts_event_handler( ) -> None: """Register a GATTs event handler and track the count.""" _handler_counts.gatts_event += 1 - cg.add(parent_var.register_gatts_event_handler(handler_var)) + _add_callback( + parent_var, + "add_gatts_event_callback", + handler_var, + "esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param", + "gatts_event_handler(event, gatts_if, param)", + ) def register_ble_status_event_handler( @@ -169,7 +214,13 @@ def register_ble_status_event_handler( ) -> None: """Register a BLE status event handler and track the count.""" _handler_counts.ble_status_event += 1 - cg.add(parent_var.register_ble_status_event_handler(handler_var)) + _add_callback( + parent_var, + "add_ble_status_event_callback", + handler_var, + "", + "ble_before_disabled_event_handler()", + ) def register_bt_logger(*loggers: BTLoggers) -> None: @@ -225,10 +276,6 @@ NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2] esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble") ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component) -GAPEventHandler = esp32_ble_ns.class_("GAPEventHandler") -GATTcEventHandler = esp32_ble_ns.class_("GATTcEventHandler") -GATTsEventHandler = esp32_ble_ns.class_("GATTsEventHandler") - BLEEnabledCondition = esp32_ble_ns.class_("BLEEnabledCondition", automation.Condition) BLEEnableAction = esp32_ble_ns.class_("BLEEnableAction", automation.Action) BLEDisableAction = esp32_ble_ns.class_("BLEDisableAction", automation.Action) @@ -326,14 +373,14 @@ def bt_uuid(value): value = in_value.upper() if len(value) == len(bt_uuid16_format): - pattern = re.compile("^[A-F|0-9]{4,}$") + pattern = re.compile("^[A-F0-9]{4,}$") if not pattern.match(value): raise cv.Invalid( f"Invalid hexadecimal value for 16 bit UUID format: '{in_value}'" ) return value if len(value) == len(bt_uuid32_format): - pattern = re.compile("^[A-F|0-9]{8,}$") + pattern = re.compile("^[A-F0-9]{8,}$") if not pattern.match(value): raise cv.Invalid( f"Invalid hexadecimal value for 32 bit UUID format: '{in_value}'" @@ -341,7 +388,7 @@ def bt_uuid(value): return value if len(value) == len(bt_uuid128_format): pattern = re.compile( - "^[A-F|0-9]{8,}-[A-F|0-9]{4,}-[A-F|0-9]{4,}-[A-F|0-9]{4,}-[A-F|0-9]{12,}$" + "^[A-F0-9]{8,}-[A-F0-9]{4,}-[A-F0-9]{4,}-[A-F0-9]{4,}-[A-F0-9]{12,}$" ) if not pattern.match(value): raise cv.Invalid( @@ -544,11 +591,6 @@ async def to_code(config): cg.add(var.set_name(name)) await cg.register_component(var, config) - # BLE uses the socket wake_loop_threadsafe() mechanism to wake the main loop from BLE tasks - # This enables low-latency (~12μs) BLE event processing instead of waiting for - # select() timeout (0-16ms). The wake socket is shared across all components. - socket.require_wake_loop_threadsafe() - # Define max connections for use in C++ code (e.g., ble_server.h) max_connections = config.get(CONF_MAX_CONNECTIONS, DEFAULT_MAX_CONNECTIONS) cg.add_define("USE_ESP32_BLE_MAX_CONNECTIONS", max_connections) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index fee1c546be..0280439731 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -81,8 +81,6 @@ void ESP32BLE::disable() { this->state_ = BLE_COMPONENT_STATE_DISABLE; } -bool ESP32BLE::is_active() { return this->state_ == BLE_COMPONENT_STATE_ACTIVE; } - #ifdef USE_ESP32_BLE_ADVERTISING void ESP32BLE::advertising_start() { this->advertising_init_(); @@ -401,8 +399,17 @@ void ESP32BLE::loop() { return; } +#ifdef USE_ESP32_BLE_ADVERTISING + if (this->advertising_ != nullptr) { + this->advertising_->loop(); + } +#endif + BLEEvent *ble_event = this->ble_events_.pop(); - while (ble_event != nullptr) { + if (ble_event == nullptr) + return; + + do { switch (ble_event->type_) { #if defined(USE_ESP32_BLE_SERVER) && defined(ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT) case BLEEvent::GATTS: { @@ -410,9 +417,7 @@ void ESP32BLE::loop() { esp_gatt_if_t gatts_if = ble_event->event_.gatts.gatts_if; esp_ble_gatts_cb_param_t *param = &ble_event->event_.gatts.gatts_param; ESP_LOGV(TAG, "gatts_event [esp_gatt_if: %d] - %d", gatts_if, event); - for (auto *gatts_handler : this->gatts_event_handlers_) { - gatts_handler->gatts_event_handler(event, gatts_if, param); - } + this->gatts_event_callbacks_.call(event, gatts_if, param); break; } #endif @@ -422,9 +427,7 @@ void ESP32BLE::loop() { esp_gatt_if_t gattc_if = ble_event->event_.gattc.gattc_if; esp_ble_gattc_cb_param_t *param = &ble_event->event_.gattc.gattc_param; ESP_LOGV(TAG, "gattc_event [esp_gatt_if: %d] - %d", gattc_if, event); - for (auto *gattc_handler : this->gattc_event_handlers_) { - gattc_handler->gattc_event_handler(event, gattc_if, param); - } + this->gattc_event_callbacks_.call(event, gattc_if, param); break; } #endif @@ -433,10 +436,7 @@ void ESP32BLE::loop() { switch (gap_event) { case ESP_GAP_BLE_SCAN_RESULT_EVT: #ifdef ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT - // Use the new scan event handler - no memcpy! - for (auto *scan_handler : this->gap_scan_event_handlers_) { - scan_handler->gap_scan_event_handler(ble_event->scan_result()); - } + this->gap_scan_event_callbacks_.call(ble_event->scan_result()); #endif break; @@ -480,9 +480,7 @@ void ESP32BLE::loop() { } // clang-format on // Dispatch to all registered handlers - for (auto *gap_handler : this->gap_event_handlers_) { - gap_handler->gap_event_handler(gap_event, param); - } + this->gap_event_callbacks_.call(gap_event, param); } #endif break; @@ -499,15 +497,11 @@ void ESP32BLE::loop() { } // Return the event to the pool this->ble_event_pool_.release(ble_event); - ble_event = this->ble_events_.pop(); - } -#ifdef USE_ESP32_BLE_ADVERTISING - if (this->advertising_ != nullptr) { - this->advertising_->loop(); - } -#endif + } while ((ble_event = this->ble_events_.pop()) != nullptr); - // Log dropped events periodically + // Log dropped events - only reachable when events were processed. + // Drops only occur when the queue is full, and only this loop drains it, + // so if pop() returned nullptr above we can skip this check (saves a memw). uint16_t dropped = this->ble_events_.get_and_reset_dropped_count(); if (dropped > 0) { ESP_LOGW(TAG, "Dropped %u BLE events due to buffer overflow", dropped); @@ -520,9 +514,7 @@ void ESP32BLE::loop_handle_state_transition_not_active_() { ESP_LOGD(TAG, "Disabling"); #ifdef ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT - for (auto *ble_event_handler : this->ble_status_event_handlers_) { - ble_event_handler->ble_before_disabled_event_handler(); - } + this->ble_status_event_callbacks_.call(); #endif if (!ble_dismantle_()) { @@ -607,9 +599,7 @@ void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_pa GAP_SECURITY_EVENTS: enqueue_ble_event(event, param); // Wake up main loop to process security event immediately -#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) App.wake_loop_threadsafe(); -#endif return; // Ignore these GAP events as they are not relevant for our use case @@ -630,9 +620,7 @@ void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gat esp_ble_gatts_cb_param_t *param) { enqueue_ble_event(event, gatts_if, param); // Wake up main loop to process GATT event immediately -#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) App.wake_loop_threadsafe(); -#endif } #endif @@ -641,9 +629,7 @@ void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gat esp_ble_gattc_cb_param_t *param) { enqueue_ble_event(event, gattc_if, param); // Wake up main loop to process GATT event immediately -#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) App.wake_loop_threadsafe(); -#endif } #endif diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 752ddc9d1f..de8c8c2343 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -87,37 +87,6 @@ enum BLEComponentState : uint8_t { BLE_COMPONENT_STATE_ACTIVE, }; -class GAPEventHandler { - public: - virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0; -}; - -class GAPScanEventHandler { - public: - virtual void gap_scan_event_handler(const BLEScanResult &scan_result) = 0; -}; - -#ifdef USE_ESP32_BLE_CLIENT -class GATTcEventHandler { - public: - virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, - esp_ble_gattc_cb_param_t *param) = 0; -}; -#endif - -#ifdef USE_ESP32_BLE_SERVER -class GATTsEventHandler { - public: - virtual void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param) = 0; -}; -#endif - -class BLEStatusEventHandler { - public: - virtual void ble_before_disabled_event_handler() = 0; -}; - class ESP32BLE : public Component { public: void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; } @@ -135,7 +104,7 @@ class ESP32BLE : public Component { void enable(); void disable(); - bool is_active(); + ESPHOME_ALWAYS_INLINE bool is_active() { return this->state_ == BLE_COMPONENT_STATE_ACTIVE; } void setup() override; void loop() override; void dump_config() override; @@ -154,22 +123,28 @@ class ESP32BLE : public Component { #endif #ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT - void register_gap_event_handler(GAPEventHandler *handler) { this->gap_event_handlers_.push_back(handler); } + template void add_gap_event_callback(F &&callback) { + this->gap_event_callbacks_.add(std::forward(callback)); + } #endif #ifdef ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT - void register_gap_scan_event_handler(GAPScanEventHandler *handler) { - this->gap_scan_event_handlers_.push_back(handler); + template void add_gap_scan_event_callback(F &&callback) { + this->gap_scan_event_callbacks_.add(std::forward(callback)); } #endif #if defined(USE_ESP32_BLE_CLIENT) && defined(ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT) - void register_gattc_event_handler(GATTcEventHandler *handler) { this->gattc_event_handlers_.push_back(handler); } + template void add_gattc_event_callback(F &&callback) { + this->gattc_event_callbacks_.add(std::forward(callback)); + } #endif #if defined(USE_ESP32_BLE_SERVER) && defined(ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT) - void register_gatts_event_handler(GATTsEventHandler *handler) { this->gatts_event_handlers_.push_back(handler); } + template void add_gatts_event_callback(F &&callback) { + this->gatts_event_callbacks_.add(std::forward(callback)); + } #endif #ifdef ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT - void register_ble_status_event_handler(BLEStatusEventHandler *handler) { - this->ble_status_event_handlers_.push_back(handler); + template void add_ble_status_event_callback(F &&callback) { + this->ble_status_event_callbacks_.add(std::forward(callback)); } #endif void set_enable_on_boot(bool enable_on_boot) { this->enable_on_boot_ = enable_on_boot; } @@ -202,21 +177,27 @@ class ESP32BLE : public Component { private: template friend void enqueue_ble_event(Args... args); - // Handler vectors - use StaticVector when counts are known at compile time #ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT - StaticVector gap_event_handlers_; + StaticCallbackManager + gap_event_callbacks_; #endif #ifdef ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT - StaticVector gap_scan_event_handlers_; + StaticCallbackManager + gap_scan_event_callbacks_; #endif #if defined(USE_ESP32_BLE_CLIENT) && defined(ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT) - StaticVector gattc_event_handlers_; + StaticCallbackManager + gattc_event_callbacks_; #endif #if defined(USE_ESP32_BLE_SERVER) && defined(ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT) - StaticVector gatts_event_handlers_; + StaticCallbackManager + gatts_event_callbacks_; #endif #ifdef ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT - StaticVector ble_status_event_handlers_; + StaticCallbackManager ble_status_event_callbacks_; #endif // Large objects (size depends on template parameters, but typically aligned to 4 bytes) diff --git a/esphome/components/esp32_ble_beacon/__init__.py b/esphome/components/esp32_ble_beacon/__init__.py index 04c783980d..8052c13596 100644 --- a/esphome/components/esp32_ble_beacon/__init__.py +++ b/esphome/components/esp32_ble_beacon/__init__.py @@ -10,12 +10,7 @@ AUTO_LOAD = ["esp32_ble"] DEPENDENCIES = ["esp32"] esp32_ble_beacon_ns = cg.esphome_ns.namespace("esp32_ble_beacon") -ESP32BLEBeacon = esp32_ble_beacon_ns.class_( - "ESP32BLEBeacon", - cg.Component, - esp32_ble.GAPEventHandler, - cg.Parented.template(esp32_ble.ESP32BLE), -) +ESP32BLEBeacon = esp32_ble_beacon_ns.class_("ESP32BLEBeacon", cg.Component) CONF_MAJOR = "major" CONF_MINOR = "minor" CONF_MIN_INTERVAL = "min_interval" diff --git a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h index 7a0424f3aa..44a7133454 100644 --- a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h +++ b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h @@ -35,7 +35,7 @@ using esp_ble_ibeacon_t = struct { using namespace esp32_ble; -class ESP32BLEBeacon : public Component, public GAPEventHandler, public Parented { +class ESP32BLEBeacon : public Component { public: explicit ESP32BLEBeacon(const std::array &uuid) : uuid_(uuid) {} @@ -51,7 +51,7 @@ class ESP32BLEBeacon : public Component, public GAPEventHandler, public Parented #ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID void set_tx_power(esp_power_level_t val) { this->tx_power_ = val; } #endif - void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; + void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); protected: void on_advertise_(); diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index 9d6e079d92..7f0f2c624d 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -350,7 +350,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ // For V3_WITHOUT_CACHE, we already set fast params before connecting // No need to update them again here this->log_event_("Searching for services"); - esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr); + esp_ble_gattc_search_service(esp_gattc_if, param->open.conn_id, nullptr); break; } case ESP_GATTC_CONNECT_EVT: { diff --git a/esphome/components/esp32_ble_server/__init__.py b/esphome/components/esp32_ble_server/__init__.py index 827ddba955..7bf3092a4e 100644 --- a/esphome/components/esp32_ble_server/__init__.py +++ b/esphome/components/esp32_ble_server/__init__.py @@ -72,7 +72,6 @@ BLECharacteristic_ns = esp32_ble_server_ns.namespace("BLECharacteristic") BLEServer = esp32_ble_server_ns.class_( "BLEServer", cg.Component, - esp32_ble.GATTsEventHandler, cg.Parented.template(esp32_ble.ESP32BLE), ) esp32_ble_server_automations_ns = esp32_ble_server_ns.namespace( @@ -308,24 +307,30 @@ def final_validate_config(config): # Check if all characteristics that require notifications have the notify property set for char_id in CORE.data.get(DOMAIN, {}).get(KEY_NOTIFY_REQUIRED, set()): # Look for the characteristic in the configuration - char_config = [ + matches = [ char_conf for service_conf in config[CONF_SERVICES] for char_conf in service_conf[CONF_CHARACTERISTICS] if char_conf[CONF_ID] == char_id - ][0] + ] + if not matches: + continue + char_config = matches[0] if not char_config[CONF_NOTIFY]: raise cv.Invalid( f"Characteristic {char_config[CONF_UUID]} has notify actions and the {CONF_NOTIFY} property is not set" ) for char_id in CORE.data.get(DOMAIN, {}).get(KEY_SET_VALUE, set()): # Look for the characteristic in the configuration - char_config = [ + matches = [ char_conf for service_conf in config[CONF_SERVICES] for char_conf in service_conf[CONF_CHARACTERISTICS] if char_conf[CONF_ID] == char_id - ][0] + ] + if not matches: + continue + char_config = matches[0] if isinstance(char_config.get(CONF_VALUE, {}).get(CONF_DATA), cv.Lambda): raise cv.Invalid( f"Characteristic {char_config[CONF_UUID]} has both a set_value action and a templated value" diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index ecc53e197f..be0691dc06 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -7,7 +7,6 @@ #ifdef USE_ESP32 -#include #include #include #include @@ -39,16 +38,17 @@ void BLEServer::loop() { case RUNNING: { // Start all services that are pending to start if (!this->services_to_start_.empty()) { - for (auto &service : this->services_to_start_) { + size_t write_idx = 0; + for (auto *service : this->services_to_start_) { if (service->is_created()) { service->start(); // Needs to be called once per characteristic in the service } + // Remove services that have started or are starting + if (!service->is_starting() && !service->is_running()) { + this->services_to_start_[write_idx++] = service; + } } - // Remove services that have been started - this->services_to_start_.erase( - std::remove_if(this->services_to_start_.begin(), this->services_to_start_.end(), - [](BLEService *service) { return service->is_starting() || service->is_running(); }), - this->services_to_start_.end()); + this->services_to_start_.erase(this->services_to_start_.begin() + write_idx, this->services_to_start_.end()); } break; } @@ -91,8 +91,6 @@ void BLEServer::loop() { } } -bool BLEServer::is_running() { return this->parent_->is_active() && this->state_ == RUNNING; } - bool BLEServer::can_proceed() { return this->is_running() || !this->parent_->is_active(); } void BLEServer::restart_advertising_() { diff --git a/esphome/components/esp32_ble_server/ble_server.h b/esphome/components/esp32_ble_server/ble_server.h index ff7e0044e4..9708ed40c8 100644 --- a/esphome/components/esp32_ble_server/ble_server.h +++ b/esphome/components/esp32_ble_server/ble_server.h @@ -24,7 +24,7 @@ namespace esp32_ble_server { using namespace esp32_ble; using namespace bytebuffer; -class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEventHandler, public Parented { +class BLEServer : public Component, public Parented { public: void setup() override; void loop() override; @@ -32,7 +32,7 @@ class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEv float get_setup_priority() const override; bool can_proceed() override; - bool is_running(); + ESPHOME_ALWAYS_INLINE bool is_running() { return this->parent_->is_active() && this->state_ == RUNNING; } void set_manufacturer_data(const std::vector &data) { this->manufacturer_data_ = data; @@ -53,10 +53,9 @@ class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEv const uint16_t *get_clients() const { return this->clients_; } uint8_t get_client_count() const { return this->client_count_; } - void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param) override; + void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); - void ble_before_disabled_event_handler() override; + void ble_before_disabled_event_handler(); // Direct callback registration - supports multiple callbacks void on_connect(std::function &&callback) { diff --git a/esphome/components/esp32_ble_server/ble_service.cpp b/esphome/components/esp32_ble_server/ble_service.cpp index 96fedf2346..8956c87b3e 100644 --- a/esphome/components/esp32_ble_server/ble_service.cpp +++ b/esphome/components/esp32_ble_server/ble_service.cpp @@ -159,7 +159,7 @@ void BLEService::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t g break; } case ESP_GATTS_STOP_EVT: { - if (param->start.service_handle == this->handle_) { + if (param->stop.service_handle == this->handle_) { this->state_ = STOPPED; } break; diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index c5e8f3178d..d758b400c4 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -90,8 +90,6 @@ esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker") ESP32BLETracker = esp32_ble_tracker_ns.class_( "ESP32BLETracker", cg.Component, - esp32_ble.GAPEventHandler, - esp32_ble.GATTcEventHandler, cg.Parented.template(esp32_ble.ESP32BLE), ) ESPBTClient = esp32_ble_tracker_ns.class_("ESPBTClient") @@ -380,7 +378,8 @@ async def esp32_ble_tracker_start_scan_action_to_code( ): paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) - cg.add(var.set_continuous(config[CONF_CONTINUOUS])) + template_ = await cg.templatable(config[CONF_CONTINUOUS], args, cg.bool_) + cg.add(var.set_continuous(template_)) return var diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 6a2834a869..c7f2319d69 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -27,8 +27,14 @@ #include #endif +#ifdef USE_ESP32_BLE_DEVICE +#ifdef USE_BLE_TRACKER_PSA_AES +#include +#else #define MBEDTLS_AES_ALT #include +#endif +#endif // USE_ESP32_BLE_DEVICE // bt_trace.h #undef TAG @@ -243,7 +249,7 @@ void ESP32BLETracker::start_scan_(bool first) { return; } this->set_scanner_state_(ScannerState::STARTING); - ESP_LOGD(TAG, "Starting scan, set scanner state to STARTING."); + ESP_LOGV(TAG, "Starting scan, set scanner state to STARTING."); if (!first) { #ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT for (auto *listener : this->listeners_) @@ -744,23 +750,48 @@ void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) { } bool ESPBTDevice::resolve_irk(const uint8_t *irk) const { - uint8_t ecb_key[16]; - uint8_t ecb_plaintext[16]; - uint8_t ecb_ciphertext[16]; + static constexpr size_t AES_BLOCK_SIZE = 16; + static constexpr size_t AES_KEY_BITS = 128; + + uint8_t ecb_key[AES_BLOCK_SIZE]; + uint8_t ecb_plaintext[AES_BLOCK_SIZE]; + uint8_t ecb_ciphertext[AES_BLOCK_SIZE]; uint64_t addr64 = esp32_ble::ble_addr_to_uint64(this->address_); - memcpy(&ecb_key, irk, 16); - memset(&ecb_plaintext, 0, 16); + memcpy(&ecb_key, irk, AES_BLOCK_SIZE); + memset(&ecb_plaintext, 0, AES_BLOCK_SIZE); ecb_plaintext[13] = (addr64 >> 40) & 0xff; ecb_plaintext[14] = (addr64 >> 32) & 0xff; ecb_plaintext[15] = (addr64 >> 24) & 0xff; +#ifdef USE_BLE_TRACKER_PSA_AES + // Use PSA Crypto API (mbedtls 4.0 / IDF 6.0+) + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + psa_set_key_type(&attributes, PSA_KEY_TYPE_AES); + psa_set_key_bits(&attributes, AES_KEY_BITS); + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_ENCRYPT); + psa_set_key_algorithm(&attributes, PSA_ALG_ECB_NO_PADDING); + + mbedtls_svc_key_id_t key_id; + if (psa_import_key(&attributes, ecb_key, AES_BLOCK_SIZE, &key_id) != PSA_SUCCESS) { + return false; + } + + size_t output_length; + psa_status_t status = psa_cipher_encrypt(key_id, PSA_ALG_ECB_NO_PADDING, ecb_plaintext, AES_BLOCK_SIZE, + ecb_ciphertext, AES_BLOCK_SIZE, &output_length); + psa_destroy_key(key_id); + if (status != PSA_SUCCESS || output_length != AES_BLOCK_SIZE) { + return false; + } +#else + // Use legacy mbedtls AES API (IDF < 6.0) mbedtls_aes_context ctx = {0, 0, {0}}; mbedtls_aes_init(&ctx); - if (mbedtls_aes_setkey_enc(&ctx, ecb_key, 128) != 0) { + if (mbedtls_aes_setkey_enc(&ctx, ecb_key, AES_KEY_BITS) != 0) { mbedtls_aes_free(&ctx); return false; } @@ -771,6 +802,7 @@ bool ESPBTDevice::resolve_irk(const uint8_t *irk) const { } mbedtls_aes_free(&ctx); +#endif return ecb_ciphertext[15] == (addr64 & 0xff) && ecb_ciphertext[14] == ((addr64 >> 8) & 0xff) && ecb_ciphertext[13] == ((addr64 >> 16) & 0xff); @@ -823,7 +855,7 @@ void ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) { } void ESP32BLETracker::cleanup_scan_state_(bool is_stop_complete) { - ESP_LOGD(TAG, "Scan %scomplete, set scanner state to IDLE.", is_stop_complete ? "stop " : ""); + ESP_LOGV(TAG, "Scan %scomplete, set scanner state to IDLE.", is_stop_complete ? "stop " : ""); #ifdef USE_ESP32_BLE_DEVICE this->already_discovered_.clear(); #endif diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index e0e25aca20..43405b02b7 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -12,6 +12,13 @@ #ifdef USE_ESP32 +#include +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) +// mbedtls 4.0 (IDF 6.0) removed the legacy mbedtls AES API. +// Use the PSA Crypto API instead. +#define USE_BLE_TRACKER_PSA_AES +#endif + #include #include #include @@ -284,10 +291,6 @@ class ESPBTClient : public ESPBTDeviceListener { }; class ESP32BLETracker : public Component, - public GAPEventHandler, - public GAPScanEventHandler, - public GATTcEventHandler, - public BLEStatusEventHandler, #ifdef USE_OTA_STATE_LISTENER public ota::OTAGlobalStateListener, #endif @@ -318,11 +321,10 @@ class ESP32BLETracker : public Component, void start_scan(); void stop_scan(); - void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, - esp_ble_gattc_cb_param_t *param) override; - void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; - void gap_scan_event_handler(const BLEScanResult &scan_result) override; - void ble_before_disabled_event_handler() override; + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); + void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); + void gap_scan_event_handler(const BLEScanResult &scan_result); + void ble_before_disabled_event_handler(); #ifdef USE_OTA_STATE_LISTENER void on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) override; diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 3a5d87792b..5165956806 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -2,7 +2,7 @@ import logging from esphome import automation, pins import esphome.codegen as cg -from esphome.components import i2c, socket +from esphome.components import i2c from esphome.components.esp32 import add_idf_component, add_idf_sdkconfig_option from esphome.components.psram import DOMAIN as psram_domain import esphome.config_validation as cv @@ -29,7 +29,7 @@ from esphome.types import ConfigType _LOGGER = logging.getLogger(__name__) -AUTO_LOAD = ["camera", "socket"] +AUTO_LOAD = ["camera"] DEPENDENCIES = ["esp32"] esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera") @@ -370,7 +370,6 @@ SETTERS = { async def to_code(config): cg.add_define("USE_CAMERA") - socket.require_wake_loop_threadsafe() var = cg.new_Pvariable(config[CONF_ID]) await setup_entity(var, config, "camera") await cg.register_component(var, config) @@ -400,7 +399,7 @@ async def to_code(config): if config[CONF_JPEG_QUALITY] != 0 and config[CONF_PIXEL_FORMAT] != "JPEG": cg.add_define("USE_ESP32_CAMERA_JPEG_CONVERSION") - add_idf_component(name="espressif/esp32-camera", ref="2.1.1") + add_idf_component(name="espressif/esp32-camera", ref="2.1.6") add_idf_sdkconfig_option("CONFIG_SCCB_HARDWARE_I2C_DRIVER_NEW", True) add_idf_sdkconfig_option("CONFIG_SCCB_HARDWARE_I2C_DRIVER_LEGACY", False) diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 085feb8c8a..a7546476d8 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -521,11 +521,9 @@ void ESP32Camera::framebuffer_task(void *pv) { camera_fb_t *framebuffer = esp_camera_fb_get(); xQueueSend(that->framebuffer_get_queue_, &framebuffer, portMAX_DELAY); // Only wake the main loop if there's a pending request to consume the frame -#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) if (that->has_requested_image_()) { App.wake_loop_threadsafe(); } -#endif // return is no-op for config with 1 fb xQueueReceive(that->framebuffer_return_queue_, &framebuffer, portMAX_DELAY); esp_camera_fb_return(framebuffer); diff --git a/esphome/components/esp32_hosted/__init__.py b/esphome/components/esp32_hosted/__init__.py index a51ae2cd66..1619a845d8 100644 --- a/esphome/components/esp32_hosted/__init__.py +++ b/esphome/components/esp32_hosted/__init__.py @@ -6,103 +6,246 @@ from esphome.components import esp32 import esphome.config_validation as cv from esphome.const import ( CONF_CLK_PIN, + CONF_CS_PIN, + CONF_FREQUENCY, + CONF_MISO_PIN, + CONF_MOSI_PIN, CONF_RESET_PIN, + CONF_TYPE, CONF_VARIANT, - KEY_CORE, - KEY_FRAMEWORK_VERSION, ) -from esphome.core import CORE from esphome.cpp_generator import add_define CODEOWNERS = ["@swoboda1337"] CONF_ACTIVE_HIGH = "active_high" +CONF_BUS_WIDTH = "bus_width" CONF_CMD_PIN = "cmd_pin" CONF_D0_PIN = "d0_pin" CONF_D1_PIN = "d1_pin" CONF_D2_PIN = "d2_pin" CONF_D3_PIN = "d3_pin" -CONF_SLOT = "slot" +CONF_DATA_READY_ACTIVE_HIGH = "data_ready_active_high" +CONF_DATA_READY_PIN = "data_ready_pin" +CONF_HANDSHAKE_ACTIVE_HIGH = "handshake_active_high" +CONF_HANDSHAKE_PIN = "handshake_pin" CONF_SDIO_FREQUENCY = "sdio_frequency" +CONF_SLOT = "slot" +CONF_SPI_MODE = "spi_mode" -CONFIG_SCHEMA = cv.All( - cv.Schema( - { - cv.Required(CONF_VARIANT): cv.one_of(*esp32.VARIANTS, upper=True), - cv.Required(CONF_ACTIVE_HIGH): cv.boolean, - cv.Required(CONF_CLK_PIN): pins.internal_gpio_output_pin_number, - cv.Required(CONF_CMD_PIN): pins.internal_gpio_output_pin_number, - cv.Required(CONF_D0_PIN): pins.internal_gpio_output_pin_number, - cv.Required(CONF_D1_PIN): pins.internal_gpio_output_pin_number, - cv.Required(CONF_D2_PIN): pins.internal_gpio_output_pin_number, - cv.Required(CONF_D3_PIN): pins.internal_gpio_output_pin_number, - cv.Required(CONF_RESET_PIN): pins.internal_gpio_output_pin_number, - cv.Optional(CONF_SLOT, default=1): cv.int_range(min=0, max=1), - cv.Optional(CONF_SDIO_FREQUENCY, default="40MHz"): cv.All( - cv.frequency, cv.Range(min=400e3, max=50e6) - ), - } - ), +# Shared fields for both transport modes +BASE_SCHEMA = cv.Schema( + { + cv.Required(CONF_VARIANT): cv.one_of(*esp32.VARIANTS, upper=True), + cv.Required(CONF_ACTIVE_HIGH): cv.boolean, + cv.Required(CONF_RESET_PIN): pins.internal_gpio_output_pin_number, + } +) + +SDIO_SCHEMA = BASE_SCHEMA.extend( + { + cv.Required(CONF_CLK_PIN): pins.internal_gpio_output_pin_number, + cv.Required(CONF_CMD_PIN): pins.internal_gpio_output_pin_number, + cv.Required(CONF_D0_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_D1_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_D2_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_D3_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_BUS_WIDTH, default=4): cv.one_of(1, 4, int=True), + cv.Optional(CONF_SLOT, default=1): cv.int_range(min=0, max=1), + cv.Optional(CONF_SDIO_FREQUENCY, default="40MHz"): cv.All( + cv.frequency, cv.Range(min=400e3, max=50e6) + ), + } ) -async def to_code(config): - add_define("USE_ESP32_HOSTED") - if config[CONF_ACTIVE_HIGH]: - esp32.add_idf_sdkconfig_option( - "CONFIG_ESP_HOSTED_SDIO_RESET_ACTIVE_HIGH", - True, +def _validate_sdio(config): + if config[CONF_BUS_WIDTH] == 4: + for pin in (CONF_D1_PIN, CONF_D2_PIN, CONF_D3_PIN): + if pin not in config: + raise cv.Invalid( + f"{pin} is required when bus_width is 4", + path=[pin], + ) + return config + + +# SPI variant-dependent defaults and limits +_SPI_VARIANT_DEFAULTS = { + "ESP32": {"spi_mode": 2, "frequency": 10, "max_frequency": 10}, + "ESP32C6": {"spi_mode": 3, "frequency": 26, "max_frequency": 40}, +} +_SPI_DEFAULT = {"spi_mode": 3, "frequency": 40, "max_frequency": 40} + +SPI_SCHEMA = BASE_SCHEMA.extend( + { + cv.Required(CONF_CLK_PIN): pins.internal_gpio_output_pin_number, + cv.Required(CONF_MOSI_PIN): pins.internal_gpio_output_pin_number, + cv.Required(CONF_MISO_PIN): pins.internal_gpio_input_pin_number, + cv.Required(CONF_CS_PIN): pins.internal_gpio_output_pin_number, + cv.Required(CONF_HANDSHAKE_PIN): pins.internal_gpio_input_pin_number, + cv.Required(CONF_DATA_READY_PIN): pins.internal_gpio_input_pin_number, + cv.Optional(CONF_SPI_MODE): cv.int_range(min=0, max=3), + cv.Optional(CONF_FREQUENCY): cv.All(cv.frequency, cv.Range(min=1e6, max=40e6)), + cv.Optional(CONF_HANDSHAKE_ACTIVE_HIGH, default=True): cv.boolean, + cv.Optional(CONF_DATA_READY_ACTIVE_HIGH, default=True): cv.boolean, + } +) + + +def _validate_spi(config): + variant = config[CONF_VARIANT] + defaults = _SPI_VARIANT_DEFAULTS.get(variant, _SPI_DEFAULT) + + if CONF_SPI_MODE not in config: + config[CONF_SPI_MODE] = defaults["spi_mode"] + + if CONF_FREQUENCY not in config: + config[CONF_FREQUENCY] = float(defaults["frequency"] * 1e6) + + freq_mhz = int(config[CONF_FREQUENCY] // 1e6) + if freq_mhz > defaults["max_frequency"]: + raise cv.Invalid( + f"SPI frequency {freq_mhz}MHz exceeds maximum {defaults['max_frequency']}MHz for {variant}", + path=[CONF_FREQUENCY], ) + return config + + +CONFIG_SCHEMA = cv.typed_schema( + { + "sdio": cv.All(SDIO_SCHEMA, _validate_sdio), + "spi": cv.All(SPI_SCHEMA, _validate_spi), + }, + default_type="sdio", +) + + +def _configure_sdio(config): + slot = config[CONF_SLOT] + esp32.add_idf_sdkconfig_option( + f"CONFIG_ESP_HOSTED_SDIO_SLOT_{slot}", + True, + ) + if config[CONF_BUS_WIDTH] == 1: + esp32.add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_SDIO_1_BIT_BUS", True) else: - esp32.add_idf_sdkconfig_option( - "CONFIG_ESP_HOSTED_SDIO_RESET_ACTIVE_LOW", - True, - ) + esp32.add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_SDIO_4_BIT_BUS", True) esp32.add_idf_sdkconfig_option( - "CONFIG_ESP_HOSTED_SDIO_GPIO_RESET_SLAVE", # NOLINT - config[CONF_RESET_PIN], - ) - esp32.add_idf_sdkconfig_option( - f"CONFIG_SLAVE_IDF_TARGET_{config[CONF_VARIANT]}", # NOLINT - True, - ) - esp32.add_idf_sdkconfig_option( - f"CONFIG_ESP_HOSTED_SDIO_SLOT_{config[CONF_SLOT]}", - True, - ) - esp32.add_idf_sdkconfig_option( - f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CLK_SLOT_{config[CONF_SLOT]}", + f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CLK_SLOT_{slot}", config[CONF_CLK_PIN], ) esp32.add_idf_sdkconfig_option( - f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CMD_SLOT_{config[CONF_SLOT]}", + f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CMD_SLOT_{slot}", config[CONF_CMD_PIN], ) esp32.add_idf_sdkconfig_option( - f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D0_SLOT_{config[CONF_SLOT]}", + f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D0_SLOT_{slot}", config[CONF_D0_PIN], ) - esp32.add_idf_sdkconfig_option( - f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D1_4BIT_BUS_SLOT_{config[CONF_SLOT]}", - config[CONF_D1_PIN], - ) - esp32.add_idf_sdkconfig_option( - f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D2_4BIT_BUS_SLOT_{config[CONF_SLOT]}", - config[CONF_D2_PIN], - ) - esp32.add_idf_sdkconfig_option( - f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D3_4BIT_BUS_SLOT_{config[CONF_SLOT]}", - config[CONF_D3_PIN], - ) + if config[CONF_BUS_WIDTH] == 4: + esp32.add_idf_sdkconfig_option( + f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D1_4BIT_BUS_SLOT_{slot}", + config[CONF_D1_PIN], + ) + esp32.add_idf_sdkconfig_option( + f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D2_4BIT_BUS_SLOT_{slot}", + config[CONF_D2_PIN], + ) + esp32.add_idf_sdkconfig_option( + f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D3_4BIT_BUS_SLOT_{slot}", + config[CONF_D3_PIN], + ) esp32.add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_CUSTOM_SDIO_PINS", True) esp32.add_idf_sdkconfig_option( "CONFIG_ESP_HOSTED_SDIO_CLOCK_FREQ_KHZ", int(config[CONF_SDIO_FREQUENCY] // 1000), ) - framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] - os.environ["ESP_IDF_VERSION"] = f"{framework_ver.major}.{framework_ver.minor}" - if framework_ver >= cv.Version(5, 5, 0): + +def _configure_spi(config): + esp32.add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_SPI_HOST_INTERFACE", True) + # SPI mode is set via per-variant choice options + variant = config[CONF_VARIANT] + mode = config[CONF_SPI_MODE] + suffix = "ESP32" if variant == "ESP32" else "ESP32XX" + esp32.add_idf_sdkconfig_option( + f"CONFIG_ESP_HOSTED_SPI_PRIV_MODE_{mode}_{suffix}", + True, + ) + # Frequency is set via per-variant options + freq_mhz = int(config[CONF_FREQUENCY] // 1e6) + if variant == "ESP32": + esp32.add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_SPI_FREQ_ESP32", freq_mhz) + elif variant == "ESP32C6": + esp32.add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_SPI_FREQ_ESP32C6", freq_mhz) + else: + esp32.add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_SPI_FREQ_ESP32XX", freq_mhz) + # Pin configuration (use HSPI variant as P4/H2 hosts don't have VSPI) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_SPI_HSPI_GPIO_MOSI", config[CONF_MOSI_PIN] + ) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_SPI_HSPI_GPIO_MISO", config[CONF_MISO_PIN] + ) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_SPI_HSPI_GPIO_CLK", config[CONF_CLK_PIN] + ) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_SPI_HSPI_GPIO_CS", config[CONF_CS_PIN] + ) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_SPI_GPIO_HANDSHAKE", config[CONF_HANDSHAKE_PIN] + ) + esp32.add_idf_sdkconfig_option( + "CONFIG_ESP_HOSTED_SPI_GPIO_DATA_READY", config[CONF_DATA_READY_PIN] + ) + # Handshake and data_ready polarity + if config[CONF_HANDSHAKE_ACTIVE_HIGH]: + esp32.add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_HS_ACTIVE_HIGH", True) + else: + esp32.add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_HS_ACTIVE_LOW", True) + if config[CONF_DATA_READY_ACTIVE_HIGH]: + esp32.add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_DR_ACTIVE_HIGH", True) + else: + esp32.add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_DR_ACTIVE_LOW", True) + + +async def to_code(config): + add_define("USE_ESP32_HOSTED") + transport = config[CONF_TYPE] + transport_prefix = "SDIO" if transport == "sdio" else "SPI" + + # Reset polarity + if config[CONF_ACTIVE_HIGH]: + esp32.add_idf_sdkconfig_option( + f"CONFIG_ESP_HOSTED_{transport_prefix}_RESET_ACTIVE_HIGH", True + ) + else: + esp32.add_idf_sdkconfig_option( + f"CONFIG_ESP_HOSTED_{transport_prefix}_RESET_ACTIVE_LOW", True + ) + # Reset GPIO + esp32.add_idf_sdkconfig_option( + f"CONFIG_ESP_HOSTED_{transport_prefix}_GPIO_RESET_SLAVE", # NOLINT + config[CONF_RESET_PIN], + ) + # Slave variant # NOLINT + esp32.add_idf_sdkconfig_option( + f"CONFIG_SLAVE_IDF_TARGET_{config[CONF_VARIANT]}", # NOLINT + True, + ) + + # Transport-specific configuration + if transport == "sdio": + _configure_sdio(config) + else: + _configure_spi(config) + + # Library versions + idf_ver = esp32.idf_version() + os.environ["ESP_IDF_VERSION"] = f"{idf_ver.major}.{idf_ver.minor}" + if idf_ver >= cv.Version(5, 5, 0): esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="1.4.0") esp32.add_idf_component(name="espressif/eppp_link", ref="1.1.4") esp32.add_idf_component(name="espressif/esp_hosted", ref="2.12.1") diff --git a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp index dcd6e643c2..af35d32888 100644 --- a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp +++ b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp @@ -448,6 +448,13 @@ void Esp32HostedUpdate::perform(bool force) { return; } +#ifdef USE_ESP32_HOSTED_HTTP_UPDATE + if (this->firmware_url_.empty()) { + ESP_LOGW(TAG, "No firmware URL available, run check first"); + return; + } +#endif + update::UpdateState prev_state = this->state_; this->state_ = update::UPDATE_STATE_INSTALLING; this->update_info_.has_progress = false; diff --git a/esphome/components/esp32_improv/automation.h b/esphome/components/esp32_improv/automation.h index 52c5da125b..cd2bd84c30 100644 --- a/esphome/components/esp32_improv/automation.h +++ b/esphome/components/esp32_improv/automation.h @@ -12,58 +12,73 @@ namespace esp32_improv { class ESP32ImprovProvisionedTrigger : public Trigger<> { public: - explicit ESP32ImprovProvisionedTrigger(ESP32ImprovComponent *parent) { - parent->add_on_state_callback([this, parent](improv::State state, improv::Error error) { - if (state == improv::STATE_PROVISIONED && !parent->is_failed()) { - trigger(); + explicit ESP32ImprovProvisionedTrigger(ESP32ImprovComponent *parent) : parent_(parent) { + parent->add_on_state_callback([this](improv::State state, improv::Error error) { + if (state == improv::STATE_PROVISIONED && !this->parent_->is_failed()) { + this->trigger(); } }); } + + protected: + ESP32ImprovComponent *parent_; }; class ESP32ImprovProvisioningTrigger : public Trigger<> { public: - explicit ESP32ImprovProvisioningTrigger(ESP32ImprovComponent *parent) { - parent->add_on_state_callback([this, parent](improv::State state, improv::Error error) { - if (state == improv::STATE_PROVISIONING && !parent->is_failed()) { - trigger(); + explicit ESP32ImprovProvisioningTrigger(ESP32ImprovComponent *parent) : parent_(parent) { + parent->add_on_state_callback([this](improv::State state, improv::Error error) { + if (state == improv::STATE_PROVISIONING && !this->parent_->is_failed()) { + this->trigger(); } }); } + + protected: + ESP32ImprovComponent *parent_; }; class ESP32ImprovStartTrigger : public Trigger<> { public: - explicit ESP32ImprovStartTrigger(ESP32ImprovComponent *parent) { - parent->add_on_state_callback([this, parent](improv::State state, improv::Error error) { + explicit ESP32ImprovStartTrigger(ESP32ImprovComponent *parent) : parent_(parent) { + parent->add_on_state_callback([this](improv::State state, improv::Error error) { if ((state == improv::STATE_AUTHORIZED || state == improv::STATE_AWAITING_AUTHORIZATION) && - !parent->is_failed()) { - trigger(); + !this->parent_->is_failed()) { + this->trigger(); } }); } + + protected: + ESP32ImprovComponent *parent_; }; class ESP32ImprovStateTrigger : public Trigger { public: - explicit ESP32ImprovStateTrigger(ESP32ImprovComponent *parent) { - parent->add_on_state_callback([this, parent](improv::State state, improv::Error error) { - if (!parent->is_failed()) { - trigger(state, error); + explicit ESP32ImprovStateTrigger(ESP32ImprovComponent *parent) : parent_(parent) { + parent->add_on_state_callback([this](improv::State state, improv::Error error) { + if (!this->parent_->is_failed()) { + this->trigger(state, error); } }); } + + protected: + ESP32ImprovComponent *parent_; }; class ESP32ImprovStoppedTrigger : public Trigger<> { public: - explicit ESP32ImprovStoppedTrigger(ESP32ImprovComponent *parent) { - parent->add_on_state_callback([this, parent](improv::State state, improv::Error error) { - if (state == improv::STATE_STOPPED && !parent->is_failed()) { - trigger(); + explicit ESP32ImprovStoppedTrigger(ESP32ImprovComponent *parent) : parent_(parent) { + parent->add_on_state_callback([this](improv::State state, improv::Error error) { + if (state == improv::STATE_STOPPED && !this->parent_->is_failed()) { + this->trigger(); } }); } + + protected: + ESP32ImprovComponent *parent_; }; } // namespace esp32_improv diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index e4ae49f235..c24b08b06f 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -350,8 +350,7 @@ void ESP32ImprovComponent::process_incoming_data_() { ESP_LOGD(TAG, "Received Improv Wi-Fi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(), command.password.c_str()); - auto f = std::bind(&ESP32ImprovComponent::on_wifi_connect_timeout_, this); - this->set_timeout("wifi-connect-timeout", 30000, f); + this->set_timeout("wifi-connect-timeout", 30000, [this]() { this->on_wifi_connect_timeout_(); }); this->incoming_data_.clear(); break; } diff --git a/esphome/components/esp32_improv/esp32_improv_component.h b/esphome/components/esp32_improv/esp32_improv_component.h index 8f4cfd7958..41799f2325 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.h +++ b/esphome/components/esp32_improv/esp32_improv_component.h @@ -48,8 +48,8 @@ class ESP32ImprovComponent : public Component, public improv_base::ImprovBase { bool should_start() const { return this->should_start_; } #ifdef USE_ESP32_IMPROV_STATE_CALLBACK - void add_on_state_callback(std::function &&callback) { - this->state_callback_.add(std::move(callback)); + template void add_on_state_callback(F &&callback) { + this->state_callback_.add(std::forward(callback)); } #endif #ifdef USE_BINARY_SENSOR diff --git a/esphome/components/esp32_rmt_led_strip/led_strip.cpp b/esphome/components/esp32_rmt_led_strip/led_strip.cpp index 66b41931aa..ca97a181fd 100644 --- a/esphome/components/esp32_rmt_led_strip/led_strip.cpp +++ b/esphome/components/esp32_rmt_led_strip/led_strip.cpp @@ -99,8 +99,6 @@ void ESP32RMTLEDStripLightOutput::setup() { channel.gpio_num = gpio_num_t(this->pin_); channel.mem_block_symbols = this->rmt_symbols_; channel.trans_queue_depth = 1; - channel.flags.io_loop_back = 0; - channel.flags.io_od_mode = 0; channel.flags.invert_out = this->invert_out_; channel.flags.with_dma = this->use_dma_; channel.intr_priority = 0; diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 0d331b29d6..e44bc807e9 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -217,7 +217,7 @@ void ESP32TouchComponent::setup() { for (uint32_t i = 0; i < ONESHOT_SCAN_COUNT; i++) { err = touch_sensor_trigger_oneshot_scanning(this->sens_handle_, ONESHOT_SCAN_TIMEOUT_MS); if (err != ESP_OK) { - ESP_LOGW(TAG, "Oneshot scan %d failed: %s", i, esp_err_to_name(err)); + ESP_LOGW(TAG, "Oneshot scan %" PRIu32 " failed: %s", i, esp_err_to_name(err)); } } diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 2081145096..bef7e36470 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -20,6 +20,7 @@ from esphome.const import ( ThreadModel, ) from esphome.core import CORE, CoroPriority, Lambda, coroutine_with_priority +from esphome.core.config import BOARD_MAX_LENGTH from esphome.helpers import copy_file_if_changed from esphome.types import ConfigType @@ -203,7 +204,9 @@ BUILD_FLASH_MODES = ["qio", "qout", "dio", "dout"] CONFIG_SCHEMA = cv.All( cv.Schema( { - cv.Required(CONF_BOARD): cv.string_strict, + cv.Required(CONF_BOARD): cv.All( + cv.string_strict, cv.ByteLength(max=BOARD_MAX_LENGTH) + ), cv.Optional(CONF_FRAMEWORK, default={}): ARDUINO_FRAMEWORK_SCHEMA, cv.Optional(CONF_RESTORE_FROM_FLASH, default=False): cv.boolean, cv.Optional(CONF_EARLY_PIN_INIT, default=True): cv.boolean, @@ -233,6 +236,7 @@ async def to_code(config): cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) cg.add_define("ESPHOME_VARIANT", "ESP8266") cg.add_define(ThreadModel.SINGLE) + cg.add_define("USE_ESP8266_CRASH_HANDLER") enable_scanf_float = config.get(CONF_ENABLE_SCANF_FLOAT) if enable_scanf_float is None and lambdas_use_scanf_float(CORE.config): diff --git a/esphome/components/esp8266/crash_handler.cpp b/esphome/components/esp8266/crash_handler.cpp new file mode 100644 index 0000000000..91b0cf9082 --- /dev/null +++ b/esphome/components/esp8266/crash_handler.cpp @@ -0,0 +1,235 @@ +#ifdef USE_ESP8266 + +#include "esphome/core/defines.h" +#ifdef USE_ESP8266_CRASH_HANDLER + +#include "crash_handler.h" +#include "esphome/core/log.h" + +#include + +extern "C" { +#include + +// Global reset info struct populated by SDK/Arduino core at boot +extern struct rst_info resetInfo; +} + +// Xtensa windowed-ABI: bits[31:30] encode call type (CALL0=00, CALL4=01, +// CALL8=10, CALL12=11). Mask and force bit 30 to recover the real address. +static constexpr uint32_t XTENSA_ADDR_MASK = 0x3FFFFFFF; +static constexpr uint32_t XTENSA_CODE_BASE = 0x40000000; + +// ESP8266 memory map boundaries for code regions +static constexpr uint32_t IRAM_START = 0x40100000; +static constexpr uint32_t IRAM_END = 0x40108000; // 32KB + +// Linker symbols for the actual firmware IROM section. +// Using these instead of a conservative upper bound (0x40400000) prevents +// false positives from stale stack values beyond the actual flash mapping. +extern "C" { +// NOLINTBEGIN(bugprone-reserved-identifier,readability-identifier-naming,readability-redundant-declaration) +extern void _irom0_text_start(void); +extern void _irom0_text_end(void); +// NOLINTEND(bugprone-reserved-identifier,readability-identifier-naming,readability-redundant-declaration) +} + +// Check if a value looks like a code address in IRAM or flash-mapped IROM. +// IRAM_ATTR as safety net — normally inlined into custom_crash_callback, but +// ensures correctness if the compiler ever chooses not to inline. +static inline bool IRAM_ATTR is_code_addr(uint32_t val) { + uint32_t addr = (val & XTENSA_ADDR_MASK) | XTENSA_CODE_BASE; + return (addr >= IRAM_START && addr < IRAM_END) || + (addr >= (uint32_t) _irom0_text_start && addr < (uint32_t) _irom0_text_end); +} + +// Recover the actual code address from a windowed-ABI return address on the stack. +static inline uint32_t IRAM_ATTR recover_code_addr(uint32_t val) { return (val & XTENSA_ADDR_MASK) | XTENSA_CODE_BASE; } + +// RTC user memory layout for crash backtrace data. +// User-accessible RTC memory: blocks 64-191 (each block = 4 bytes). +// We use blocks 174-191 (last 18 blocks, 72 bytes) to minimize conflicts. +// Store 16 raw candidates, filter to real return addresses at log time. +static constexpr uint8_t RTC_CRASH_BASE = 174; +static constexpr size_t MAX_BACKTRACE = 16; + +// Magic word packs sentinel, version, and count into one uint32_t: +// bits[31:16] = sentinel +// bits[15:8] = version +// bits[7:0] = backtrace count +static constexpr uint8_t CRASH_SENTINEL_BITS = 16; +static constexpr uint8_t CRASH_VERSION_BITS = 8; + +static constexpr uint16_t CRASH_SENTINEL_VALUE = 0xDEAD; +static constexpr uint8_t CRASH_VERSION_VALUE = 1; + +static constexpr uint32_t CRASH_SENTINEL = static_cast(CRASH_SENTINEL_VALUE) << CRASH_SENTINEL_BITS; +static constexpr uint32_t CRASH_VERSION = static_cast(CRASH_VERSION_VALUE) << CRASH_VERSION_BITS; +static constexpr uint32_t CRASH_SENTINEL_MASK = static_cast(0xFFFF) << CRASH_SENTINEL_BITS; +static constexpr uint32_t CRASH_VERSION_MASK = static_cast(0xFF) << CRASH_VERSION_BITS; +static constexpr uint32_t CRASH_COUNT_MASK = 0xFF; + +// Struct layout: 18 RTC blocks (72 bytes): +// [0] = magic (sentinel | version | count) +// [1..16] = up to 16 code addresses from stack scanning +// [17] = epc1 at crash time (to skip duplicates at log time) +struct RtcCrashData { + uint32_t magic; + uint32_t backtrace[MAX_BACKTRACE]; + uint32_t epc1; // Fault PC, used to filter duplicates +}; +static_assert(sizeof(RtcCrashData) == 72, "RtcCrashData must fit in 18 RTC blocks"); + +namespace esphome::esp8266 { + +static const char *const TAG = "esp8266"; + +static inline bool is_crash_reason(uint32_t reason) { + return reason == REASON_WDT_RST || reason == REASON_EXCEPTION_RST || reason == REASON_SOFT_WDT_RST; +} + +bool crash_handler_has_data() { return is_crash_reason(resetInfo.reason); } + +// Xtensa exception cause names for the LX106 core (ESP8266). +// Only includes causes that can actually occur on the LX106 — it has no MMU, +// no TLB, no PIF, and no privilege levels, so causes 12-18 and 24-26 are +// impossible and omitted. The numeric cause is always logged as fallback. +// Uses if-else with LOG_STR to avoid CSWTCH jump tables (RAM on ESP8266). +static const LogString *get_exception_cause(uint32_t cause) { + if (cause == 0) + return LOG_STR("IllegalInst"); + if (cause == 2) + return LOG_STR("InstFetchErr"); + if (cause == 3) + return LOG_STR("LoadStoreErr"); + if (cause == 4) + return LOG_STR("Level1Int"); + if (cause == 6) + return LOG_STR("DivByZero"); + if (cause == 9) + return LOG_STR("Alignment"); + if (cause == 20) + return LOG_STR("InstFetchProhibit"); + if (cause == 28) + return LOG_STR("LoadProhibit"); + if (cause == 29) + return LOG_STR("StoreProhibit"); + return nullptr; +} + +static const LogString *get_reset_reason(uint32_t reason) { + if (reason == REASON_WDT_RST) + return LOG_STR("Hardware WDT"); + if (reason == REASON_EXCEPTION_RST) + return LOG_STR("Exception"); + if (reason == REASON_SOFT_WDT_RST) + return LOG_STR("Soft WDT"); + return LOG_STR("Unknown"); +} + +// Read backtrace from RTC user memory into caller-provided buffer. +// Returns the number of valid backtrace entries (0 if no data found). +static uint8_t read_rtc_backtrace(uint32_t *backtrace, size_t max_entries) { + RtcCrashData rtc_data; + if (!system_rtc_mem_read(RTC_CRASH_BASE, &rtc_data, sizeof(rtc_data))) + return 0; + uint32_t magic = rtc_data.magic; + if ((magic & CRASH_SENTINEL_MASK) != CRASH_SENTINEL || (magic & CRASH_VERSION_MASK) != CRASH_VERSION) + return 0; + uint8_t raw_count = magic & CRASH_COUNT_MASK; + if (raw_count > MAX_BACKTRACE) + raw_count = MAX_BACKTRACE; + // Skip any that match epc1 (already reported as the fault PC). + // Note: we cannot verify CALL instructions at addr-3 on ESP8266 because + // reading from IROM causes LoadStoreError due to flash cache conflicts + // (the reading code and target can share a direct-mapped cache line). + // The linker-symbol IROM bounds already eliminate most false positives. + uint8_t out = 0; + for (uint8_t i = 0; i < raw_count && out < max_entries; i++) { + uint32_t addr = rtc_data.backtrace[i]; + if (addr != rtc_data.epc1) + backtrace[out++] = addr; + } + return out; +} + +// Intentionally uses separate ESP_LOGE calls per line instead of combining into +// one multi-line log message. This ensures each address appears as its own line +// on the serial console, making it possible to see partial output if the device +// crashes again during boot, and allowing the CLI's process_stacktrace to match +// and decode each address individually. +void crash_handler_log() { + if (!is_crash_reason(resetInfo.reason)) + return; + + // Read and filter backtrace from RTC into stack-local buffer (no persistent RAM cost). + // Both resetInfo and RTC data survive until the next reset, so this can be + // called multiple times (logger init + API subscribe) with the same result. + uint32_t backtrace[MAX_BACKTRACE]; + uint8_t bt_count = read_rtc_backtrace(backtrace, MAX_BACKTRACE); + + ESP_LOGE(TAG, "*** CRASH DETECTED ON PREVIOUS BOOT ***"); + // GCC's ROM divide routine triggers IllegalInstruction (exccause=0) at specific + // ROM addresses instead of IntegerDivideByZero (exccause=6). Patch to match + // the Arduino core's postmortem handler behavior. + static constexpr uint32_t EXCCAUSE_ILLEGAL_INSTRUCTION = 0; + static constexpr uint32_t EXCCAUSE_INTEGER_DIVIDE_BY_ZERO = 6; + static constexpr uint32_t ROM_DIV_ZERO_ADDR_1 = 0x4000dce5; + static constexpr uint32_t ROM_DIV_ZERO_ADDR_2 = 0x4000dd3d; + uint32_t exccause = resetInfo.exccause; + if (exccause == EXCCAUSE_ILLEGAL_INSTRUCTION && + (resetInfo.epc1 == ROM_DIV_ZERO_ADDR_1 || resetInfo.epc1 == ROM_DIV_ZERO_ADDR_2)) { + exccause = EXCCAUSE_INTEGER_DIVIDE_BY_ZERO; + } + const LogString *cause = get_exception_cause(exccause); + if (cause != nullptr) { + ESP_LOGE(TAG, " Reason: %s - %s (exccause=%" PRIu32 ")", LOG_STR_ARG(get_reset_reason(resetInfo.reason)), + LOG_STR_ARG(cause), exccause); + } else { + ESP_LOGE(TAG, " Reason: %s (exccause=%" PRIu32 ")", LOG_STR_ARG(get_reset_reason(resetInfo.reason)), exccause); + } + ESP_LOGE(TAG, " PC: 0x%08" PRIX32, resetInfo.epc1); + if (resetInfo.reason == REASON_EXCEPTION_RST) { + ESP_LOGE(TAG, " EXCVADDR: 0x%08" PRIX32, resetInfo.excvaddr); + } + for (uint8_t i = 0; i < bt_count; i++) { + ESP_LOGE(TAG, " BT%d: 0x%08" PRIX32, i, backtrace[i]); + } +} + +} // namespace esphome::esp8266 + +// --- Custom crash callback --- +// Overrides the weak custom_crash_callback() from Arduino core's +// core_esp8266_postmortem.cpp. Called during exception handling before +// the device restarts. We scan the full stack for code addresses and store +// them in RTC user memory (which survives software reset). +extern "C" void IRAM_ATTR custom_crash_callback(struct rst_info *rst_info, uint32_t stack, uint32_t stack_end) { + // No zero-init — only magic, epc1, and backtrace[0..count-1] are read. + // Saves the IRAM cost of a 72-byte zero-init loop. + RtcCrashData data; // NOLINT(cppcoreguidelines-pro-type-member-init) + uint8_t count = 0; + + // Stack pointer from the Xtensa exception frame is always 4-byte aligned. + auto *scan = (uint32_t *) stack; // NOLINT(performance-no-int-to-ptr) + auto *end = (uint32_t *) stack_end; // NOLINT(performance-no-int-to-ptr) + uint32_t epc1 = rst_info->epc1; + + for (; scan < end && count < MAX_BACKTRACE; scan++) { + uint32_t val = *scan; + if (is_code_addr(val)) { + uint32_t addr = recover_code_addr(val); + // Skip epc1 — already reported as the fault PC + if (addr != epc1) + data.backtrace[count++] = addr; + } + } + + data.epc1 = epc1; + data.magic = CRASH_SENTINEL | CRASH_VERSION | count; + + system_rtc_mem_write(RTC_CRASH_BASE, &data, sizeof(data)); +} + +#endif // USE_ESP8266_CRASH_HANDLER +#endif // USE_ESP8266 diff --git a/esphome/components/esp8266/crash_handler.h b/esphome/components/esp8266/crash_handler.h new file mode 100644 index 0000000000..ea3683b834 --- /dev/null +++ b/esphome/components/esp8266/crash_handler.h @@ -0,0 +1,20 @@ +#pragma once + +#ifdef USE_ESP8266 + +#include "esphome/core/defines.h" + +#ifdef USE_ESP8266_CRASH_HANDLER + +namespace esphome::esp8266 { + +/// Log crash data if a crash was detected on previous boot. +void crash_handler_log(); + +/// Returns true if the previous boot was a crash (exception, WDT, or soft WDT). +bool crash_handler_has_data(); + +} // namespace esphome::esp8266 + +#endif // USE_ESP8266_CRASH_HANDLER +#endif // USE_ESP8266 diff --git a/esphome/components/esp8266/gpio.py b/esphome/components/esp8266/gpio.py index 43508afaf9..64be4a6495 100644 --- a/esphome/components/esp8266/gpio.py +++ b/esphome/components/esp8266/gpio.py @@ -155,7 +155,7 @@ ESP8266_PIN_SCHEMA = cv.All( @dataclass class PinInitialState: - mode = 255 + mode: int = 255 level: int = 255 diff --git a/esphome/components/esp8266/helpers.cpp b/esphome/components/esp8266/helpers.cpp index 4a64ae181e..aadfc31197 100644 --- a/esphome/components/esp8266/helpers.cpp +++ b/esphome/components/esp8266/helpers.cpp @@ -12,12 +12,8 @@ namespace esphome { uint32_t random_uint32() { return os_random(); } bool random_bytes(uint8_t *data, size_t len) { return os_get_random(data, len) == 0; } -// ESP8266 doesn't have mutexes, but that shouldn't be an issue as it's single-core and non-preemptive OS. -Mutex::Mutex() {} -Mutex::~Mutex() {} -void Mutex::lock() {} -bool Mutex::try_lock() { return true; } -void Mutex::unlock() {} +// ESP8266 Mutex is defined inline as a no-op in helpers.h when USE_ESP8266 (or USE_RP2040) is set, +// independent of the ESPHOME_THREAD_SINGLE thread model define. IRAM_ATTR InterruptLock::InterruptLock() { state_ = xt_rsil(15); } IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(state_); } diff --git a/esphome/components/esp8266/preference_backend.h b/esphome/components/esp8266/preference_backend.h new file mode 100644 index 0000000000..f9da8ff165 --- /dev/null +++ b/esphome/components/esp8266/preference_backend.h @@ -0,0 +1,29 @@ +#pragma once +#ifdef USE_ESP8266 + +#include +#include + +namespace esphome::esp8266 { + +class ESP8266PreferenceBackend final { + public: + bool save(const uint8_t *data, size_t len); + bool load(uint8_t *data, size_t len); + + uint32_t type = 0; + uint16_t offset = 0; + uint8_t length_words = 0; // Max 255 words (1020 bytes of data) + bool in_flash = false; +}; + +class ESP8266Preferences; +ESP8266Preferences *get_preferences(); + +} // namespace esphome::esp8266 + +namespace esphome { +using PreferenceBackend = esp8266::ESP8266PreferenceBackend; +} // namespace esphome + +#endif // USE_ESP8266 diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp index e749b1f633..f444f03555 100644 --- a/esphome/components/esp8266/preferences.cpp +++ b/esphome/components/esp8266/preferences.cpp @@ -5,28 +5,27 @@ extern "C" { #include "spi_flash.h" } -#include "esphome/core/defines.h" +#include "preferences.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/core/preferences.h" -#include "preferences.h" #include namespace esphome::esp8266 { -static const char *const TAG = "esp8266.preferences"; +static const char *const TAG = "preferences"; static constexpr uint32_t ESP_RTC_USER_MEM_START = 0x60001200; static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128; static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4; -// RTC memory layout for preferences: -// - Eboot region: RTC words 0-31 (reserved, mapped from preference offset 96-127) -// - Normal region: RTC words 32-127 (mapped from preference offset 0-95) +// RTC memory layout: +// - Eboot region: RTC words 0-31 (reserved, mapped from preference offset 78-109) +// - Normal region: RTC words 32-109 (mapped from preference offset 0-77) +// - Crash handler: RTC words 110-127 (reserved for crash_handler.cpp backtrace data) static constexpr uint32_t RTC_EBOOT_REGION_WORDS = 32; // Words 0-31 reserved for eboot -static constexpr uint32_t RTC_NORMAL_REGION_WORDS = 96; // Words 32-127 for normal prefs -static constexpr uint32_t PREF_TOTAL_WORDS = RTC_EBOOT_REGION_WORDS + RTC_NORMAL_REGION_WORDS; // 128 +static constexpr uint32_t RTC_NORMAL_REGION_WORDS = 78; // Words 32-109 for normal prefs +static constexpr uint32_t PREF_TOTAL_WORDS = RTC_EBOOT_REGION_WORDS + RTC_NORMAL_REGION_WORDS; // 110 // Maximum preference size in words (limited by uint8_t length_words field) static constexpr uint32_t MAX_PREFERENCE_WORDS = 255; @@ -137,155 +136,135 @@ static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) { static constexpr size_t PREF_MAX_BUFFER_WORDS = ESP8266_FLASH_STORAGE_SIZE > RTC_NORMAL_REGION_WORDS ? ESP8266_FLASH_STORAGE_SIZE : RTC_NORMAL_REGION_WORDS; -class ESP8266PreferenceBackend : public ESPPreferenceBackend { - public: - uint32_t type = 0; - uint16_t offset = 0; - uint8_t length_words = 0; // Max 255 words (1020 bytes of data) - bool in_flash = false; +bool ESP8266PreferenceBackend::save(const uint8_t *data, size_t len) { + if (bytes_to_words(len) != this->length_words) + return false; + const size_t buffer_size = static_cast(this->length_words) + 1; + if (buffer_size > PREF_MAX_BUFFER_WORDS) + return false; + uint32_t buffer[PREF_MAX_BUFFER_WORDS]; + memset(buffer, 0, buffer_size * sizeof(uint32_t)); + memcpy(buffer, data, len); + buffer[this->length_words] = calculate_crc(buffer, buffer + this->length_words, this->type); + return this->in_flash ? save_to_flash(this->offset, buffer, buffer_size) + : save_to_rtc(this->offset, buffer, buffer_size); +} - bool save(const uint8_t *data, size_t len) override { - if (bytes_to_words(len) != this->length_words) - return false; - const size_t buffer_size = static_cast(this->length_words) + 1; - if (buffer_size > PREF_MAX_BUFFER_WORDS) - return false; - uint32_t buffer[PREF_MAX_BUFFER_WORDS]; - memset(buffer, 0, buffer_size * sizeof(uint32_t)); - memcpy(buffer, data, len); - buffer[this->length_words] = calculate_crc(buffer, buffer + this->length_words, this->type); - return this->in_flash ? save_to_flash(this->offset, buffer, buffer_size) - : save_to_rtc(this->offset, buffer, buffer_size); +bool ESP8266PreferenceBackend::load(uint8_t *data, size_t len) { + if (bytes_to_words(len) != this->length_words) + return false; + const size_t buffer_size = static_cast(this->length_words) + 1; + if (buffer_size > PREF_MAX_BUFFER_WORDS) + return false; + uint32_t buffer[PREF_MAX_BUFFER_WORDS]; + bool ret = this->in_flash ? load_from_flash(this->offset, buffer, buffer_size) + : load_from_rtc(this->offset, buffer, buffer_size); + if (!ret) + return false; + if (buffer[this->length_words] != calculate_crc(buffer, buffer + this->length_words, this->type)) + return false; + memcpy(data, buffer, len); + return true; +} + +void ESP8266Preferences::setup() { + ESP_LOGVV(TAG, "Loading preferences from flash"); + + { + InterruptLock lock; + spi_flash_read(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4); + } +} + +ESPPreferenceObject ESP8266Preferences::make_preference(size_t length, uint32_t type, bool in_flash) { + const uint32_t length_words = bytes_to_words(length); + if (length_words > MAX_PREFERENCE_WORDS) { + ESP_LOGE(TAG, "Preference too large: %u words", static_cast(length_words)); + return {}; } - bool load(uint8_t *data, size_t len) override { - if (bytes_to_words(len) != this->length_words) - return false; - const size_t buffer_size = static_cast(this->length_words) + 1; - if (buffer_size > PREF_MAX_BUFFER_WORDS) - return false; - uint32_t buffer[PREF_MAX_BUFFER_WORDS]; - bool ret = this->in_flash ? load_from_flash(this->offset, buffer, buffer_size) - : load_from_rtc(this->offset, buffer, buffer_size); - if (!ret) - return false; - if (buffer[this->length_words] != calculate_crc(buffer, buffer + this->length_words, this->type)) - return false; - memcpy(data, buffer, len); - return true; - } -}; + const uint32_t total_words = length_words + 1; // +1 for CRC + uint16_t offset; -class ESP8266Preferences : public ESPPreferences { - public: - uint32_t current_offset = 0; - uint32_t current_flash_offset = 0; // in words - - void setup() { - ESP_LOGVV(TAG, "Loading preferences from flash"); - - { - InterruptLock lock; - spi_flash_read(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4); - } - } - - ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { - const uint32_t length_words = bytes_to_words(length); - if (length_words > MAX_PREFERENCE_WORDS) { - ESP_LOGE(TAG, "Preference too large: %u words", static_cast(length_words)); + if (in_flash) { + if (this->current_flash_offset + total_words > ESP8266_FLASH_STORAGE_SIZE) return {}; + offset = static_cast(this->current_flash_offset); + this->current_flash_offset += total_words; + } else { + uint32_t start = this->current_offset; + bool in_normal = start < RTC_NORMAL_REGION_WORDS; + // Normal: offset 0-95 maps to RTC offset 32-127 + // Eboot: offset 96-127 maps to RTC offset 0-31 + if (in_normal && start + total_words > RTC_NORMAL_REGION_WORDS) { + // start is in normal but end is not -> switch to Eboot + this->current_offset = start = RTC_NORMAL_REGION_WORDS; + in_normal = false; } - - const uint32_t total_words = length_words + 1; // +1 for CRC - uint16_t offset; - - if (in_flash) { - if (this->current_flash_offset + total_words > ESP8266_FLASH_STORAGE_SIZE) - return {}; - offset = static_cast(this->current_flash_offset); - this->current_flash_offset += total_words; - } else { - uint32_t start = this->current_offset; - bool in_normal = start < RTC_NORMAL_REGION_WORDS; - // Normal: offset 0-95 maps to RTC offset 32-127 - // Eboot: offset 96-127 maps to RTC offset 0-31 - if (in_normal && start + total_words > RTC_NORMAL_REGION_WORDS) { - // start is in normal but end is not -> switch to Eboot - this->current_offset = start = RTC_NORMAL_REGION_WORDS; - in_normal = false; - } - if (start + total_words > PREF_TOTAL_WORDS) - return {}; // Doesn't fit in RTC memory - // Convert preference offset to RTC memory offset - offset = static_cast(in_normal ? start + RTC_EBOOT_REGION_WORDS : start - RTC_NORMAL_REGION_WORDS); - this->current_offset = start + total_words; - } - - auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) - pref->offset = offset; - pref->type = type; - pref->length_words = static_cast(length_words); - pref->in_flash = in_flash; - return pref; + if (start + total_words > PREF_TOTAL_WORDS) + return {}; // Doesn't fit in RTC memory + // Convert preference offset to RTC memory offset + offset = static_cast(in_normal ? start + RTC_EBOOT_REGION_WORDS : start - RTC_NORMAL_REGION_WORDS); + this->current_offset = start + total_words; } - ESPPreferenceObject make_preference(size_t length, uint32_t type) override { -#ifdef USE_ESP8266_PREFERENCES_FLASH - return make_preference(length, type, true); -#else - return make_preference(length, type, false); -#endif - } + auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) + pref->offset = offset; + pref->type = type; + pref->length_words = static_cast(length_words); + pref->in_flash = in_flash; + return ESPPreferenceObject(pref); +} - bool sync() override { - if (!s_flash_dirty) - return true; - if (s_prevent_write) - return false; - - ESP_LOGD(TAG, "Saving"); - SpiFlashOpResult erase_res, write_res = SPI_FLASH_RESULT_OK; - { - InterruptLock lock; - erase_res = spi_flash_erase_sector(get_esp8266_flash_sector()); - if (erase_res == SPI_FLASH_RESULT_OK) { - write_res = spi_flash_write(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4); - } - } - if (erase_res != SPI_FLASH_RESULT_OK) { - ESP_LOGE(TAG, "Erasing failed"); - return false; - } - if (write_res != SPI_FLASH_RESULT_OK) { - ESP_LOGE(TAG, "Writing failed"); - return false; - } - - s_flash_dirty = false; +bool ESP8266Preferences::sync() { + if (!s_flash_dirty) return true; + if (s_prevent_write) + return false; + + ESP_LOGD(TAG, "Saving"); + SpiFlashOpResult erase_res, write_res = SPI_FLASH_RESULT_OK; + { + InterruptLock lock; + erase_res = spi_flash_erase_sector(get_esp8266_flash_sector()); + if (erase_res == SPI_FLASH_RESULT_OK) { + write_res = spi_flash_write(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4); + } + } + if (erase_res != SPI_FLASH_RESULT_OK) { + ESP_LOGE(TAG, "Erasing failed"); + return false; + } + if (write_res != SPI_FLASH_RESULT_OK) { + ESP_LOGE(TAG, "Writing failed"); + return false; } - bool reset() override { - ESP_LOGD(TAG, "Erasing storage"); - SpiFlashOpResult erase_res; - { - InterruptLock lock; - erase_res = spi_flash_erase_sector(get_esp8266_flash_sector()); - } - if (erase_res != SPI_FLASH_RESULT_OK) { - ESP_LOGE(TAG, "Erasing failed"); - return false; - } + s_flash_dirty = false; + return true; +} - // Protect flash from writing till restart - s_prevent_write = true; - return true; +bool ESP8266Preferences::reset() { + ESP_LOGD(TAG, "Erasing storage"); + SpiFlashOpResult erase_res; + { + InterruptLock lock; + erase_res = spi_flash_erase_sector(get_esp8266_flash_sector()); } -}; + if (erase_res != SPI_FLASH_RESULT_OK) { + ESP_LOGE(TAG, "Erasing failed"); + return false; + } + + // Protect flash from writing till restart + s_prevent_write = true; + return true; +} static ESP8266Preferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +ESP8266Preferences *get_preferences() { return &s_preferences; } + void setup_preferences() { s_preferences.setup(); global_preferences = &s_preferences; diff --git a/esphome/components/esp8266/preferences.h b/esphome/components/esp8266/preferences.h index 16cf80a129..43557d5ec5 100644 --- a/esphome/components/esp8266/preferences.h +++ b/esphome/components/esp8266/preferences.h @@ -1,12 +1,34 @@ #pragma once - #ifdef USE_ESP8266 +#include "esphome/core/preference_backend.h" + namespace esphome::esp8266 { +class ESP8266Preferences final : public PreferencesMixin { + public: + using PreferencesMixin::make_preference; + void setup(); + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash); + ESPPreferenceObject make_preference(size_t length, uint32_t type) { +#ifdef USE_ESP8266_PREFERENCES_FLASH + return this->make_preference(length, type, true); +#else + return this->make_preference(length, type, false); +#endif + } + bool sync(); + bool reset(); + + uint32_t current_offset = 0; + uint32_t current_flash_offset = 0; // in words +}; + void setup_preferences(); void preferences_prevent_write(bool prevent); } // namespace esphome::esp8266 +DECLARE_PREFERENCE_ALIASES(esphome::esp8266::ESP8266Preferences) + #endif // USE_ESP8266 diff --git a/esphome/components/esp8266_pwm/output.py b/esphome/components/esp8266_pwm/output.py index b9b6dcc95a..f119a6ba9f 100644 --- a/esphome/components/esp8266_pwm/output.py +++ b/esphome/components/esp8266_pwm/output.py @@ -62,6 +62,6 @@ async def to_code(config) -> None: async def esp8266_set_frequency_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) - template_ = await cg.templatable(config[CONF_FREQUENCY], args, float) + template_ = await cg.templatable(config[CONF_FREQUENCY], args, cg.float_) cg.add(var.set_frequency(template_)) return var diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index d8dbe2dee2..af9b8ee19a 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -262,7 +262,7 @@ void ESPHomeOTAComponent::handle_data_() { /// BSD sockets (ESP32): setblocking(true) makes read/write block /// lwip sockets (LT): setblocking(true) makes read/write block /// Raw TCP (8266, RP2040): setblocking is no-op; SO_RCVTIMEO uses - /// socket_delay()/socket_wake() in read(); + /// wakeable_delay() in read(); /// write() always returns immediately ota::OTAResponseTypes error_code = ota::OTA_RESPONSE_ERROR_UNKNOWN; bool update_started = false; @@ -332,12 +332,13 @@ void ESPHomeOTAComponent::handle_data_() { size_t requested = remaining < OTA_BUFFER_SIZE ? remaining : OTA_BUFFER_SIZE; ssize_t read = this->client_->read(buf, requested); if (read == -1) { - if (this->would_block_(errno)) { + const int err = errno; + if (this->would_block_(err)) { // read() already waited up to SO_RCVTIMEO for data, just feed WDT App.feed_wdt(); continue; } - ESP_LOGW(TAG, "Read err %d", errno); + ESP_LOGW(TAG, "Read err %d", err); goto error; // NOLINT(cppcoreguidelines-avoid-goto) } else if (read == 0) { ESP_LOGW(TAG, "Remote closed"); @@ -426,8 +427,9 @@ bool ESPHomeOTAComponent::readall_(uint8_t *buf, size_t len) { ssize_t read = this->client_->read(buf + at, len - at); if (read == -1) { - if (!this->would_block_(errno)) { - ESP_LOGW(TAG, "Read err %zu bytes, errno %d", len, errno); + const int err = errno; + if (!this->would_block_(err)) { + ESP_LOGW(TAG, "Read err %zu bytes, errno %d", len, err); return false; } } else if (read == 0) { @@ -455,8 +457,9 @@ bool ESPHomeOTAComponent::writeall_(const uint8_t *buf, size_t len) { ssize_t written = this->client_->write(buf + at, len - at); if (written == -1) { - if (!this->would_block_(errno)) { - ESP_LOGW(TAG, "Write err %zu bytes, errno %d", len, errno); + const int err = errno; + if (!this->would_block_(err)) { + ESP_LOGW(TAG, "Write err %zu bytes, errno %d", len, err); return false; } // EWOULDBLOCK: on raw TCP writes never block, delay(1) prevents spinning diff --git a/esphome/components/espnow/__init__.py b/esphome/components/espnow/__init__.py index d1a85ae8fd..a9624734d0 100644 --- a/esphome/components/espnow/__init__.py +++ b/esphome/components/espnow/__init__.py @@ -1,6 +1,6 @@ from esphome import automation, core import esphome.codegen as cg -from esphome.components import socket, wifi +from esphome.components import wifi from esphome.components.udp import CONF_ON_RECEIVE import esphome.config_validation as cv from esphome.const import ( @@ -17,7 +17,7 @@ from esphome.core import HexInt from esphome.types import ConfigType CODEOWNERS = ["@jesserockz"] -AUTO_LOAD = ["socket"] + byte_vector = cg.std_vector.template(cg.uint8) peer_address_t = cg.std_ns.class_("array").template(cg.uint8, 6) @@ -124,14 +124,11 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - # ESP-NOW uses wake_loop_threadsafe() to wake the main loop from ESP-NOW callbacks - # This enables low-latency event processing instead of waiting for select() timeout - socket.require_wake_loop_threadsafe() - cg.add_define("USE_ESPNOW") if wifi_channel := config.get(CONF_CHANNEL): cg.add(var.set_wifi_channel(wifi_channel)) + cg.add(var.set_enable_on_boot(config[CONF_ENABLE_ON_BOOT])) cg.add(var.set_auto_add_peer(config[CONF_AUTO_ADD_PEER])) for peer in config.get(CONF_PEERS, []): @@ -161,15 +158,15 @@ def validate_peer(value): def _validate_raw_data(value): if isinstance(value, str): - if len(value) >= MAX_ESPNOW_PACKET_SIZE: + if len(value) > MAX_ESPNOW_PACKET_SIZE: raise cv.Invalid( - f"'{CONF_DATA}' must be less than {MAX_ESPNOW_PACKET_SIZE} characters long, got {len(value)}" + f"'{CONF_DATA}' must be at most {MAX_ESPNOW_PACKET_SIZE} characters long, got {len(value)}" ) return value if isinstance(value, list): if len(value) > MAX_ESPNOW_PACKET_SIZE: raise cv.Invalid( - f"'{CONF_DATA}' must be less than {MAX_ESPNOW_PACKET_SIZE} bytes long, got {len(value)}" + f"'{CONF_DATA}' must be at most {MAX_ESPNOW_PACKET_SIZE} bytes long, got {len(value)}" ) return cv.Schema([cv.hex_uint8_t])(value) raise cv.Invalid( @@ -248,7 +245,7 @@ async def send_action( data = config.get(CONF_DATA, []) if isinstance(data, str): - data = [cg.RawExpression(f"'{c}'") for c in data] + data = list(data.encode()) templ = await cg.templatable(data, args, byte_vector, byte_vector) cg.add(var.set_data(templ)) diff --git a/esphome/components/espnow/espnow_component.cpp b/esphome/components/espnow/espnow_component.cpp index 78916891f4..282287ca83 100644 --- a/esphome/components/espnow/espnow_component.cpp +++ b/esphome/components/espnow/espnow_component.cpp @@ -4,6 +4,8 @@ #include "espnow_err.h" +#include + #include "esphome/core/application.h" #include "esphome/core/defines.h" #include "esphome/core/helpers.h" @@ -90,10 +92,8 @@ void on_send_report(const uint8_t *mac_addr, esp_now_send_status_t status) // Push always succeeds: pool is sized to queue capacity (SIZE-1), so if // allocate() returned non-null, the queue cannot be full. - // Wake main loop immediately to process ESP-NOW send event instead of waiting for select() timeout -#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) + // Wake main loop immediately to process ESP-NOW send event App.wake_loop_threadsafe(); -#endif } void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int size) { @@ -113,10 +113,8 @@ void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int // Push always succeeds: pool is sized to queue capacity (SIZE-1), so if // allocate() returned non-null, the queue cannot be full. - // Wake main loop immediately to process ESP-NOW receive event instead of waiting for select() timeout -#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) + // Wake main loop immediately to process ESP-NOW receive event App.wake_loop_threadsafe(); -#endif } ESPNowComponent::ESPNowComponent() { global_esp_now = this; } @@ -266,7 +264,7 @@ void ESPNowComponent::loop() { if (wifi::global_wifi_component != nullptr && wifi::global_wifi_component->is_connected()) { int32_t new_channel = wifi::global_wifi_component->get_wifi_channel(); if (new_channel != this->wifi_channel_) { - ESP_LOGI(TAG, "Wifi Channel is changed from %d to %d.", this->wifi_channel_, new_channel); + ESP_LOGI(TAG, "Wifi Channel is changed from %d to %" PRId32 ".", this->wifi_channel_, new_channel); this->wifi_channel_ = new_channel; } } diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index 935d2004d4..10f9a73863 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -1,23 +1,10 @@ +from dataclasses import dataclass import logging from esphome import automation, pins import esphome.codegen as cg -from esphome.components.esp32 import ( - VARIANT_ESP32, - VARIANT_ESP32C3, - VARIANT_ESP32C5, - VARIANT_ESP32C6, - VARIANT_ESP32C61, - VARIANT_ESP32P4, - VARIANT_ESP32S2, - VARIANT_ESP32S3, - add_idf_component, - add_idf_sdkconfig_option, - get_esp32_variant, - include_builtin_idf_component, -) from esphome.components.network import ip_address_literal -from esphome.components.spi import CONF_INTERFACE_INDEX, get_spi_interface +from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv from esphome.const import ( CONF_ADDRESS, @@ -49,6 +36,9 @@ from esphome.const import ( CONF_VALUE, KEY_CORE, KEY_FRAMEWORK_VERSION, + KEY_NATIVE_IDF, + Platform, + PlatformFramework, ) from esphome.core import ( CORE, @@ -60,12 +50,14 @@ import esphome.final_validate as fv from esphome.types import ConfigType CONFLICTS_WITH = ["wifi"] -DEPENDENCIES = ["esp32"] AUTO_LOAD = ["network"] LOGGER = logging.getLogger(__name__) # Key for tracking IP state listener count in CORE.data ETHERNET_IP_STATE_LISTENERS_KEY = "ethernet_ip_state_listeners" +# Key for tracking configured ethernet type +ETHERNET_TYPE_KEY = "ethernet_type" +KEY_ETHERNET = "ethernet" def request_ethernet_ip_state_listener() -> None: @@ -112,6 +104,8 @@ CONF_CLK_MODE = "clk_mode" CONF_POWER_PIN = "power_pin" CONF_PHY_REGISTERS = "phy_registers" +CONF_INTERFACE = "interface" + CONF_CLOCK_SPEED = "clock_speed" EthernetType = ethernet_ns.enum("EthernetType") @@ -123,10 +117,14 @@ ETHERNET_TYPES = { "JL1101": EthernetType.ETHERNET_TYPE_JL1101, "KSZ8081": EthernetType.ETHERNET_TYPE_KSZ8081, "KSZ8081RNA": EthernetType.ETHERNET_TYPE_KSZ8081RNA, + "W5100": EthernetType.ETHERNET_TYPE_W5100, "W5500": EthernetType.ETHERNET_TYPE_W5500, "OPENETH": EthernetType.ETHERNET_TYPE_OPENETH, "DM9051": EthernetType.ETHERNET_TYPE_DM9051, "LAN8670": EthernetType.ETHERNET_TYPE_LAN8670, + "ENC28J60": EthernetType.ETHERNET_TYPE_ENC28J60, + "W6100": EthernetType.ETHERNET_TYPE_W6100, + "W6300": EthernetType.ETHERNET_TYPE_W6300, } # PHY types that need compile-time defines for conditional compilation @@ -139,10 +137,52 @@ _PHY_TYPE_TO_DEFINE = { "JL1101": "USE_ETHERNET_JL1101", "KSZ8081": "USE_ETHERNET_KSZ8081", "KSZ8081RNA": "USE_ETHERNET_KSZ8081", + "W5100": "USE_ETHERNET_W5100", + "W5500": "USE_ETHERNET_W5500", + "DM9051": "USE_ETHERNET_DM9051", "LAN8670": "USE_ETHERNET_LAN8670", + "ENC28J60": "USE_ETHERNET_ENC28J60", + "W6100": "USE_ETHERNET_W6100", + "W6300": "USE_ETHERNET_W6300", } -SPI_ETHERNET_TYPES = ["W5500", "DM9051"] + +@dataclass(frozen=True) +class IDFRegistryComponent: + """An ESP-IDF component from the Espressif Component Registry.""" + + name: str + version: str + + +# IDF 6.0 moved per-chip PHY/MAC drivers to the Espressif Component Registry. +_IDF6_ETHERNET_COMPONENTS: dict[str, IDFRegistryComponent] = { + "LAN8720": IDFRegistryComponent("espressif/lan87xx", "1.0.0"), + "RTL8201": IDFRegistryComponent("espressif/rtl8201", "1.0.1"), + "DP83848": IDFRegistryComponent("espressif/dp83848", "1.0.0"), + "IP101": IDFRegistryComponent("espressif/ip101", "1.0.0"), + "KSZ8081": IDFRegistryComponent("espressif/ksz80xx", "1.0.0"), + "KSZ8081RNA": IDFRegistryComponent("espressif/ksz80xx", "1.0.0"), + "W5500": IDFRegistryComponent("espressif/w5500", "1.0.1"), + "DM9051": IDFRegistryComponent("espressif/dm9051", "1.0.0"), + "ENC28J60": IDFRegistryComponent("espressif/enc28j60", "1.0.1"), + "LAN8670": IDFRegistryComponent("espressif/lan867x", "2.0.0"), +} + +# These types are always external IDF components (never built-in to ESP-IDF) +_ALWAYS_EXTERNAL_IDF_COMPONENTS = {"LAN8670", "ENC28J60"} + +# ESP32-only SPI ethernet types (W5100 is RP2040-only, no ESP-IDF driver) +SPI_ETHERNET_TYPES = {"W5500", "DM9051", "ENC28J60"} +# RP2040-supported ethernet types (SPI and PIO QSPI) +RP2040_ETHERNET_TYPES = {"W5100", "W5500", "W6100", "W6300", "ENC28J60"} +_RP2040_SPI_LIBRARIES = { + "W5100": "lwIP_w5100", + "W5500": "lwIP_w5500", + "ENC28J60": "lwIP_enc28j60", + "W6100": "lwIP_w6100", + "W6300": "lwIP_w6300", +} SPI_ETHERNET_DEFAULT_POLLING_INTERVAL = TimePeriodMilliseconds(milliseconds=10) emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t") @@ -159,6 +199,13 @@ CLK_MODES_DEPRECATED = { "GPIO17_OUT": ("CLK_OUT", 17), } +spi_host_device_t = cg.global_ns.enum("spi_host_device_t") + +SPI_INTERFACE_MAP = { + "spi2": spi_host_device_t.SPI2_HOST, + "spi3": spi_host_device_t.SPI3_HOST, +} + MANUAL_IP_SCHEMA = cv.Schema( { cv.Required(CONF_STATIC_IP): cv.ipv4address, @@ -173,20 +220,44 @@ EthernetComponent = ethernet_ns.class_("EthernetComponent", cg.Component) ManualIP = ethernet_ns.struct("ManualIP") -def _is_framework_spi_polling_mode_supported(): - # SPI Ethernet without IRQ feature is added in - # esp-idf >= (5.3+ ,5.2.1+, 5.1.4) - # Note: Arduino now uses ESP-IDF as a component, so we only check IDF version - framework_version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] - if framework_version >= cv.Version(5, 3, 0): +def _is_framework_spi_polling_mode_supported() -> bool: + """Check if ESP-IDF framework supports SPI polling mode (ESP32 only). + + SPI Ethernet without IRQ feature is added in + esp-idf >= (5.3+, 5.2.1+, 5.1.4) + """ + if not CORE.is_esp32: + return False + from esphome.components.esp32 import idf_version + + ver = idf_version() + if ver >= cv.Version(5, 3, 0): return True - if cv.Version(5, 3, 0) > framework_version >= cv.Version(5, 2, 1): + if cv.Version(5, 3, 0) > ver >= cv.Version(5, 2, 1): return True - if cv.Version(5, 2, 0) > framework_version >= cv.Version(5, 1, 4): # noqa: SIM103 + if cv.Version(5, 2, 0) > ver >= cv.Version(5, 1, 4): # noqa: SIM103 return True return False +def _validate_spi_interface(config: ConfigType) -> ConfigType: + """Set default SPI interface or validate user choice against the variant.""" + if not CORE.is_esp32: + return config + from esphome.components.esp32 import VARIANT_ESP32, get_esp32_variant + from esphome.components.spi import get_hw_interface_list + + has_spi3 = "spi3" in sum(get_hw_interface_list(), []) + if CONF_INTERFACE not in config: + # Only classic ESP32 defaults to spi3; all others default to spi2 + config[CONF_INTERFACE] = ( + "spi3" if get_esp32_variant() == VARIANT_ESP32 else "spi2" + ) + elif config[CONF_INTERFACE] == "spi3" and not has_spi3: + raise cv.Invalid("Interface 'spi3' is not available on this variant.") + return config + + def _validate(config): if CONF_USE_ADDRESS not in config: if CONF_MANUAL_IP in config: @@ -195,52 +266,79 @@ def _validate(config): use_address = CORE.name + config[CONF_DOMAIN] config[CONF_USE_ADDRESS] = use_address - if config[CONF_TYPE] in SPI_ETHERNET_TYPES: - if _is_framework_spi_polling_mode_supported(): - if CONF_POLLING_INTERVAL in config and CONF_INTERRUPT_PIN in config: - raise cv.Invalid( - f"Cannot specify more than one of {CONF_INTERRUPT_PIN}, {CONF_POLLING_INTERVAL}" - ) - if CONF_POLLING_INTERVAL not in config and CONF_INTERRUPT_PIN not in config: - config[CONF_POLLING_INTERVAL] = SPI_ETHERNET_DEFAULT_POLLING_INTERVAL - else: - if CONF_POLLING_INTERVAL in config: - raise cv.Invalid( - "In this version of the framework " - f"({CORE.target_framework} {CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]}), " - f"'{CONF_POLLING_INTERVAL}' is not supported." - ) - if CONF_INTERRUPT_PIN not in config: - raise cv.Invalid( - "In this version of the framework " - f"({CORE.target_framework} {CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]}), " - f"'{CONF_INTERRUPT_PIN}' is a required option for [ethernet]." - ) - elif config[CONF_TYPE] != "OPENETH": - if CONF_CLK_MODE in config: - mode, pin = CLK_MODES_DEPRECATED[config[CONF_CLK_MODE]] - LOGGER.warning( - "[ethernet] The 'clk_mode' option is deprecated. " - "Please replace 'clk_mode: %s' with:\n" - " clk:\n" - " mode: %s\n" - " pin: %s\n" - "Removal scheduled for 2026.7.0.", - config[CONF_CLK_MODE], - mode, - pin, - ) - config[CONF_CLK] = CLK_SCHEMA({CONF_MODE: mode, CONF_PIN: pin}) - del config[CONF_CLK_MODE] - elif CONF_CLK not in config: - raise cv.Invalid("'clk' is a required option for [ethernet].") - variant = get_esp32_variant() - if variant not in (VARIANT_ESP32, VARIANT_ESP32P4): - raise cv.Invalid( - f"{config[CONF_TYPE]} PHY requires RMII interface and is only supported " - f"on ESP32 classic and ESP32-P4, not {variant}" + if CORE.is_esp32: + if config[CONF_TYPE] in SPI_ETHERNET_TYPES: + # ENC28J60 driver does not support polling mode - interrupt is required + if config[CONF_TYPE] == "ENC28J60": + if CONF_POLLING_INTERVAL in config: + raise cv.Invalid( + f"'{CONF_POLLING_INTERVAL}' is not supported for ENC28J60. " + f"'{CONF_INTERRUPT_PIN}' is required." + ) + if CONF_INTERRUPT_PIN not in config: + raise cv.Invalid( + f"'{CONF_INTERRUPT_PIN}' is a required option for ENC28J60." + ) + elif _is_framework_spi_polling_mode_supported(): + if CONF_POLLING_INTERVAL in config and CONF_INTERRUPT_PIN in config: + raise cv.Invalid( + f"Cannot specify more than one of {CONF_INTERRUPT_PIN}, {CONF_POLLING_INTERVAL}" + ) + if ( + CONF_POLLING_INTERVAL not in config + and CONF_INTERRUPT_PIN not in config + ): + config[CONF_POLLING_INTERVAL] = ( + SPI_ETHERNET_DEFAULT_POLLING_INTERVAL + ) + else: + if CONF_POLLING_INTERVAL in config: + raise cv.Invalid( + "In this version of the framework " + f"({CORE.target_framework} {CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]}), " + f"'{CONF_POLLING_INTERVAL}' is not supported." + ) + if CONF_INTERRUPT_PIN not in config: + raise cv.Invalid( + "In this version of the framework " + f"({CORE.target_framework} {CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]}), " + f"'{CONF_INTERRUPT_PIN}' is a required option for [ethernet]." + ) + elif config[CONF_TYPE] != "OPENETH": + from esphome.components.esp32 import ( + VARIANT_ESP32, + VARIANT_ESP32P4, + get_esp32_variant, ) + if CONF_CLK_MODE in config: + mode, pin = CLK_MODES_DEPRECATED[config[CONF_CLK_MODE]] + LOGGER.warning( + "[ethernet] The 'clk_mode' option is deprecated. " + "Please replace 'clk_mode: %s' with:\n" + " clk:\n" + " mode: %s\n" + " pin: %s\n" + "Removal scheduled for 2026.7.0.", + config[CONF_CLK_MODE], + mode, + pin, + ) + config[CONF_CLK] = CLK_SCHEMA({CONF_MODE: mode, CONF_PIN: pin}) + del config[CONF_CLK_MODE] + elif CONF_CLK not in config: + raise cv.Invalid("'clk' is a required option for [ethernet].") + variant = get_esp32_variant() + if variant not in (VARIANT_ESP32, VARIANT_ESP32P4): + raise cv.Invalid( + f"{config[CONF_TYPE]} PHY requires RMII interface and is only supported " + f"on ESP32 classic and ESP32-P4, not {variant}" + ) + elif CORE.is_rp2040 and config[CONF_TYPE] not in RP2040_ETHERNET_TYPES: + raise cv.Invalid( + f"Only {', '.join(sorted(RP2040_ETHERNET_TYPES))} are supported on RP2040, " + f"not {config[CONF_TYPE]}" + ) return config @@ -269,41 +367,55 @@ CLK_SCHEMA = cv.Schema( cv.Required(CONF_PIN): pins.internal_gpio_pin_number, } ) -RMII_SCHEMA = BASE_SCHEMA.extend( - cv.Schema( - { - cv.Required(CONF_MDC_PIN): pins.internal_gpio_output_pin_number, - cv.Required(CONF_MDIO_PIN): pins.internal_gpio_output_pin_number, - cv.Optional(CONF_CLK_MODE): cv.enum( - CLK_MODES_DEPRECATED, upper=True, space="_" - ), - cv.Optional(CONF_CLK): CLK_SCHEMA, - cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31), - cv.Optional(CONF_POWER_PIN): pins.internal_gpio_output_pin_number, - cv.Optional(CONF_PHY_REGISTERS): cv.ensure_list(PHY_REGISTER_SCHEMA), - } - ) +RMII_SCHEMA = cv.All( + BASE_SCHEMA.extend( + cv.Schema( + { + cv.Required(CONF_MDC_PIN): pins.internal_gpio_output_pin_number, + cv.Required(CONF_MDIO_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_CLK_MODE): cv.enum( + CLK_MODES_DEPRECATED, upper=True, space="_" + ), + cv.Optional(CONF_CLK): CLK_SCHEMA, + cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31), + cv.Optional(CONF_POWER_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_PHY_REGISTERS): cv.ensure_list(PHY_REGISTER_SCHEMA), + } + ) + ), + cv.only_on([Platform.ESP32]), ) -SPI_SCHEMA = BASE_SCHEMA.extend( - cv.Schema( - { - cv.Required(CONF_CLK_PIN): pins.internal_gpio_output_pin_number, - cv.Required(CONF_MISO_PIN): pins.internal_gpio_input_pin_number, - cv.Required(CONF_MOSI_PIN): pins.internal_gpio_output_pin_number, - cv.Required(CONF_CS_PIN): pins.internal_gpio_output_pin_number, - cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_number, - cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_number, - cv.Optional(CONF_CLOCK_SPEED, default="26.67MHz"): cv.All( - cv.frequency, cv.int_range(int(8e6), int(80e6)) - ), - # Set default value (SPI_ETHERNET_DEFAULT_POLLING_INTERVAL) at _validate() - cv.Optional(CONF_POLLING_INTERVAL): cv.All( - cv.positive_time_period_milliseconds, - cv.Range(min=TimePeriodMilliseconds(milliseconds=1)), - ), - } +SPI_SCHEMA = cv.All( + BASE_SCHEMA.extend( + cv.Schema( + { + cv.Required(CONF_CLK_PIN): pins.internal_gpio_output_pin_number, + cv.Required(CONF_MISO_PIN): pins.internal_gpio_input_pin_number, + cv.Required(CONF_MOSI_PIN): pins.internal_gpio_output_pin_number, + cv.Required(CONF_CS_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_number, + cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_number, + cv.SplitDefault(CONF_CLOCK_SPEED, esp32="26.67MHz"): cv.All( + cv.only_on_esp32, + cv.frequency, + cv.int_range(int(8e6), int(80e6)), + ), + cv.Optional(CONF_INTERFACE): cv.All( + cv.only_on_esp32, + cv.one_of(*SPI_INTERFACE_MAP.keys(), lower=True), + ), + # Set default value (SPI_ETHERNET_DEFAULT_POLLING_INTERVAL) at _validate() + cv.Optional(CONF_POLLING_INTERVAL): cv.All( + cv.only_on_esp32, + cv.positive_time_period_milliseconds, + cv.Range(min=TimePeriodMilliseconds(milliseconds=1)), + ), + } + ), ), + cv.only_on([Platform.ESP32, Platform.RP2040]), + _validate_spi_interface, ) CONFIG_SCHEMA = cv.All( @@ -316,9 +428,13 @@ CONFIG_SCHEMA = cv.All( "JL1101": RMII_SCHEMA, "KSZ8081": RMII_SCHEMA, "KSZ8081RNA": RMII_SCHEMA, + "W5100": cv.All(SPI_SCHEMA, cv.only_on([Platform.RP2040])), "W5500": SPI_SCHEMA, - "OPENETH": BASE_SCHEMA, + "OPENETH": cv.All(BASE_SCHEMA, cv.only_on([Platform.ESP32])), "DM9051": SPI_SCHEMA, + "ENC28J60": SPI_SCHEMA, + "W6100": cv.All(SPI_SCHEMA, cv.only_on([Platform.RP2040])), + "W6300": cv.All(SPI_SCHEMA, cv.only_on([Platform.RP2040])), "LAN8670": RMII_SCHEMA, }, upper=True, @@ -328,28 +444,22 @@ CONFIG_SCHEMA = cv.All( def _final_validate_spi(config): + if not CORE.is_esp32: + return # SPI interface validation is ESP32-only if config[CONF_TYPE] not in SPI_ETHERNET_TYPES: return + from esphome.components.spi import CONF_INTERFACE_INDEX, get_spi_interface + if spi_configs := fv.full_config.get().get(CONF_SPI): - variant = get_esp32_variant() - if variant in ( - VARIANT_ESP32C3, - VARIANT_ESP32C5, - VARIANT_ESP32C6, - VARIANT_ESP32C61, - VARIANT_ESP32S2, - VARIANT_ESP32S3, - ): - spi_host = "SPI2_HOST" - else: - spi_host = "SPI3_HOST" + # get_spi_interface() returns strings like "SPI2_HOST" + spi_host = f"{config[CONF_INTERFACE].upper()}_HOST" for spi_conf in spi_configs: if (index := spi_conf.get(CONF_INTERFACE_INDEX)) is not None: interface = get_spi_interface(index) if interface == spi_host: raise cv.Invalid( - f"`spi` component is using interface '{interface}'. " - f"To use {config[CONF_TYPE]}, you must change the `interface` on the `spi` component.", + f"The `ethernet` and `spi` components are both using interface '{interface}'. " + f"To use {config[CONF_TYPE]}, change the `interface` on either `ethernet:` or `spi:`." ) @@ -378,6 +488,51 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) + if CORE.is_esp32: + await _to_code_esp32(var, config) + elif CORE.is_rp2040: + await _to_code_rp2040(var, config) + + cg.add(var.set_type(ETHERNET_TYPES[config[CONF_TYPE]])) + cg.add(var.set_use_address(config[CONF_USE_ADDRESS])) + CORE.data.setdefault(KEY_ETHERNET, {})[ETHERNET_TYPE_KEY] = config[CONF_TYPE] + + if CONF_MANUAL_IP in config: + cg.add_define("USE_ETHERNET_MANUAL_IP") + cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP]))) + + # Add compile-time define for PHY types with specific code + if phy_define := _PHY_TYPE_TO_DEFINE.get(config[CONF_TYPE]): + cg.add_define(phy_define) + + if mac_address := config.get(CONF_MAC_ADDRESS): + cg.add(var.set_fixed_mac(mac_address.parts)) + + cg.add_define("USE_ETHERNET") + + if on_connect_config := config.get(CONF_ON_CONNECT): + cg.add_define("USE_ETHERNET_CONNECT_TRIGGER") + await automation.build_automation( + var.get_connect_trigger(), [], on_connect_config + ) + + if on_disconnect_config := config.get(CONF_ON_DISCONNECT): + cg.add_define("USE_ETHERNET_DISCONNECT_TRIGGER") + await automation.build_automation( + var.get_disconnect_trigger(), [], on_disconnect_config + ) + + CORE.add_job(final_step) + + +async def _to_code_esp32(var: cg.Pvariable, config: ConfigType) -> None: + from esphome.components.esp32 import ( + add_idf_component, + add_idf_sdkconfig_option, + idf_version, + include_builtin_idf_component, + ) + if config[CONF_TYPE] in SPI_ETHERNET_TYPES: cg.add(var.set_clk_pin(config[CONF_CLK_PIN])) cg.add(var.set_miso_pin(config[CONF_MISO_PIN])) @@ -394,8 +549,15 @@ async def to_code(config): cg.add(var.set_clock_speed(config[CONF_CLOCK_SPEED])) cg.add_define("USE_ETHERNET_SPI") + + cg.add(var.set_interface(SPI_INTERFACE_MAP[config[CONF_INTERFACE]])) add_idf_sdkconfig_option("CONFIG_ETH_USE_SPI_ETHERNET", True) - add_idf_sdkconfig_option(f"CONFIG_ETH_SPI_ETHERNET_{config[CONF_TYPE]}", True) + # CONFIG_ETH_SPI_ETHERNET_{TYPE} Kconfig options were removed in IDF 6.0 + # ENC28J60 was never built-in to IDF, so it has no Kconfig option + if idf_version() < cv.Version(6, 0, 0) and config[CONF_TYPE] != "ENC28J60": + add_idf_sdkconfig_option( + f"CONFIG_ETH_SPI_ETHERNET_{config[CONF_TYPE]}", True + ) elif config[CONF_TYPE] == "OPENETH": cg.add_define("USE_ETHERNET_OPENETH") add_idf_sdkconfig_option("CONFIG_ETH_USE_OPENETH", True) @@ -415,22 +577,6 @@ async def to_code(config): ) cg.add(var.add_phy_register(reg)) - cg.add(var.set_type(ETHERNET_TYPES[config[CONF_TYPE]])) - cg.add(var.set_use_address(config[CONF_USE_ADDRESS])) - - if CONF_MANUAL_IP in config: - cg.add_define("USE_ETHERNET_MANUAL_IP") - cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP]))) - - # Add compile-time define for PHY types with specific code - if phy_define := _PHY_TYPE_TO_DEFINE.get(config[CONF_TYPE]): - cg.add_define(phy_define) - - if mac_address := config.get(CONF_MAC_ADDRESS): - cg.add(var.set_fixed_mac(mac_address.parts)) - - cg.add_define("USE_ETHERNET") - # Disable WiFi when using Ethernet to save memory add_idf_sdkconfig_option("CONFIG_ESP_WIFI_ENABLED", False) # Also disable WiFi/BT coexistence since WiFi is disabled @@ -439,31 +585,44 @@ async def to_code(config): # Re-enable ESP-IDF's Ethernet driver (excluded by default to save compile time) include_builtin_idf_component("esp_eth") - if config[CONF_TYPE] == "LAN8670": - # Add LAN867x 10BASE-T1S PHY support component - add_idf_component(name="espressif/lan867x", ref="2.0.0") + if config[CONF_TYPE] in _ALWAYS_EXTERNAL_IDF_COMPONENTS: + component = _IDF6_ETHERNET_COMPONENTS[config[CONF_TYPE]] + add_idf_component(name=component.name, ref=component.version) + elif idf_version() >= cv.Version(6, 0, 0) and ( + # IDF 6.0 moved per-chip PHY/MAC drivers to the Espressif Component Registry + component := _IDF6_ETHERNET_COMPONENTS.get(config[CONF_TYPE]) + ): + add_idf_component(name=component.name, ref=component.version) - if on_connect_config := config.get(CONF_ON_CONNECT): - cg.add_define("USE_ETHERNET_CONNECT_TRIGGER") - await automation.build_automation( - var.get_connect_trigger(), [], on_connect_config - ) - if on_disconnect_config := config.get(CONF_ON_DISCONNECT): - cg.add_define("USE_ETHERNET_DISCONNECT_TRIGGER") - await automation.build_automation( - var.get_disconnect_trigger(), [], on_disconnect_config - ) +async def _to_code_rp2040(var: cg.Pvariable, config: ConfigType) -> None: + cg.add(var.set_clk_pin(config[CONF_CLK_PIN])) + cg.add(var.set_miso_pin(config[CONF_MISO_PIN])) + cg.add(var.set_mosi_pin(config[CONF_MOSI_PIN])) + cg.add(var.set_cs_pin(config[CONF_CS_PIN])) + if CONF_INTERRUPT_PIN in config: + cg.add(var.set_interrupt_pin(config[CONF_INTERRUPT_PIN])) + if CONF_RESET_PIN in config: + cg.add(var.set_reset_pin(config[CONF_RESET_PIN])) - CORE.add_job(final_step) + cg.add_define("USE_ETHERNET_SPI") + cg.add_library(_RP2040_SPI_LIBRARIES[config[CONF_TYPE]], None) def _final_validate_rmii_pins(config: ConfigType) -> None: """Validate that RMII pins are not used by other components.""" + if not CORE.is_esp32: + return # RMII validation is ESP32-only # Only validate for RMII-based PHYs on ESP32/ESP32P4 if config[CONF_TYPE] in SPI_ETHERNET_TYPES or config[CONF_TYPE] == "OPENETH": return # SPI and OPENETH don't use RMII + from esphome.components.esp32 import ( + VARIANT_ESP32, + VARIANT_ESP32P4, + get_esp32_variant, + ) + variant = get_esp32_variant() if variant == VARIANT_ESP32: rmii_pins = ESP32_RMII_FIXED_PINS @@ -521,3 +680,39 @@ async def final_step(): if ip_state_count := CORE.data.get(ETHERNET_IP_STATE_LISTENERS_KEY, 0): cg.add_define("USE_ETHERNET_IP_STATE_LISTENERS") cg.add_define("ESPHOME_ETHERNET_IP_STATE_LISTENERS", ip_state_count) + + +_platform_filter = filter_source_files_from_platform( + { + "ethernet_component_esp32.cpp": { + PlatformFramework.ESP32_IDF, + PlatformFramework.ESP32_ARDUINO, + }, + "ethernet_component_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO}, + "esp_eth_phy_jl1101.c": { + PlatformFramework.ESP32_IDF, + PlatformFramework.ESP32_ARDUINO, + }, + } +) + + +def _filter_source_files() -> list[str]: + excluded = _platform_filter() + eth_data = CORE.data.get(KEY_ETHERNET, {}) + eth_type = eth_data.get(ETHERNET_TYPE_KEY) + # Only compile the custom JL1101 driver when JL1101 is configured + # and pioarduino doesn't have it builtin (IDF 5.4.2 to 5.x) + if eth_type != "JL1101": + excluded.append("esp_eth_phy_jl1101.c") + elif CORE.is_esp32 and not CORE.data.get(KEY_NATIVE_IDF, False): + from esphome.components.esp32 import idf_version + + # pioarduino has JL1101 builtin on IDF 5.4.2-5.x; exclude custom driver + # to avoid shadowing. Native IDF builds always need the custom driver. + if cv.Version(5, 4, 2) <= idf_version() < cv.Version(6, 0, 0): + excluded.append("esp_eth_phy_jl1101.c") + return excluded + + +FILTER_SOURCE_FILES = _filter_source_files diff --git a/esphome/components/ethernet/esp_eth_phy_jl1101.c b/esphome/components/ethernet/esp_eth_phy_jl1101.c index b81d8227d4..46671a2dd0 100644 --- a/esphome/components/ethernet/esp_eth_phy_jl1101.c +++ b/esphome/components/ethernet/esp_eth_phy_jl1101.c @@ -29,7 +29,8 @@ #include "esp_rom_sys.h" #include "esp_idf_version.h" -#if defined(USE_ETHERNET_JL1101) && (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) || !defined(PLATFORMIO)) +#if defined(USE_ETHERNET_JL1101) && (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) || \ + ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) || !defined(PLATFORMIO)) static const char *TAG = "jl1101"; #define PHY_CHECK(a, str, goto_tag, ...) \ diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index e0788e1149..42cb0b3cfc 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -1,547 +1,22 @@ #include "ethernet_component.h" -#include "esphome/core/application.h" -#include "esphome/core/helpers.h" + +#ifdef USE_ETHERNET + #include "esphome/core/log.h" -#include "esphome/core/util.h" - -#ifdef USE_ESP32 - -#include -#include -#include "esp_event.h" - -#ifdef USE_ETHERNET_LAN8670 -#include "esp_eth_phy_lan867x.h" -#endif - -#ifdef USE_ETHERNET_SPI -#include -#include -#endif namespace esphome::ethernet { -static const char *const TAG = "ethernet"; - -// PHY register size for hex logging -static constexpr size_t PHY_REG_SIZE = 2; - EthernetComponent *global_eth_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -void EthernetComponent::log_error_and_mark_failed_(esp_err_t err, const char *message) { - ESP_LOGE(TAG, "%s: (%d) %s", message, err, esp_err_to_name(err)); - this->mark_failed(); -} - -#define ESPHL_ERROR_CHECK(err, message) \ - if ((err) != ESP_OK) { \ - this->log_error_and_mark_failed_(err, message); \ - return; \ - } - -#define ESPHL_ERROR_CHECK_RET(err, message, ret) \ - if ((err) != ESP_OK) { \ - this->log_error_and_mark_failed_(err, message); \ - return ret; \ - } - EthernetComponent::EthernetComponent() { global_eth_component = this; } -void EthernetComponent::setup() { - if (esp_reset_reason() != ESP_RST_DEEPSLEEP) { - // Delay here to allow power to stabilise before Ethernet is initialized. - delay(300); // NOLINT - } - - esp_err_t err; - -#ifdef USE_ETHERNET_SPI - // Install GPIO ISR handler to be able to service SPI Eth modules interrupts - gpio_install_isr_service(0); - - spi_bus_config_t buscfg = { - .mosi_io_num = this->mosi_pin_, - .miso_io_num = this->miso_pin_, - .sclk_io_num = this->clk_pin_, - .quadwp_io_num = -1, - .quadhd_io_num = -1, - .data4_io_num = -1, - .data5_io_num = -1, - .data6_io_num = -1, - .data7_io_num = -1, - .max_transfer_sz = 0, - .flags = 0, - .intr_flags = 0, - }; - -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C5) || defined(USE_ESP32_VARIANT_ESP32C6) || \ - defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - auto host = SPI2_HOST; -#else - auto host = SPI3_HOST; -#endif - - err = spi_bus_initialize(host, &buscfg, SPI_DMA_CH_AUTO); - ESPHL_ERROR_CHECK(err, "SPI bus initialize error"); -#endif - - err = esp_netif_init(); - ESPHL_ERROR_CHECK(err, "ETH netif init error"); - err = esp_event_loop_create_default(); - ESPHL_ERROR_CHECK(err, "ETH event loop error"); - - esp_netif_config_t cfg = ESP_NETIF_DEFAULT_ETH(); - this->eth_netif_ = esp_netif_new(&cfg); - - // Init MAC and PHY configs to default - eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); - eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); - -#ifdef USE_ETHERNET_SPI // Configure SPI interface and Ethernet driver for specific SPI module - spi_device_interface_config_t devcfg = { - .command_bits = 0, - .address_bits = 0, - .dummy_bits = 0, - .mode = 0, - .duty_cycle_pos = 0, - .cs_ena_pretrans = 0, - .cs_ena_posttrans = 0, - .clock_speed_hz = this->clock_speed_, - .input_delay_ns = 0, - .spics_io_num = this->cs_pin_, - .flags = 0, - .queue_size = 20, - .pre_cb = nullptr, - .post_cb = nullptr, - }; - -#if CONFIG_ETH_SPI_ETHERNET_W5500 - eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(host, &devcfg); -#endif -#if CONFIG_ETH_SPI_ETHERNET_DM9051 - eth_dm9051_config_t dm9051_config = ETH_DM9051_DEFAULT_CONFIG(host, &devcfg); -#endif - -#if CONFIG_ETH_SPI_ETHERNET_W5500 - w5500_config.int_gpio_num = this->interrupt_pin_; -#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT - w5500_config.poll_period_ms = this->polling_interval_; -#endif -#endif - -#if CONFIG_ETH_SPI_ETHERNET_DM9051 - dm9051_config.int_gpio_num = this->interrupt_pin_; -#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT - dm9051_config.poll_period_ms = this->polling_interval_; -#endif -#endif - - phy_config.phy_addr = this->phy_addr_spi_; - phy_config.reset_gpio_num = this->reset_pin_; - - esp_eth_mac_t *mac = nullptr; -#elif defined(USE_ETHERNET_OPENETH) - esp_eth_mac_t *mac = esp_eth_mac_new_openeth(&mac_config); -#else - phy_config.phy_addr = this->phy_addr_; - phy_config.reset_gpio_num = this->power_pin_; - - eth_esp32_emac_config_t esp32_emac_config = eth_esp32_emac_default_config(); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) - esp32_emac_config.smi_gpio.mdc_num = this->mdc_pin_; - esp32_emac_config.smi_gpio.mdio_num = this->mdio_pin_; -#else - esp32_emac_config.smi_mdc_gpio_num = this->mdc_pin_; - esp32_emac_config.smi_mdio_gpio_num = this->mdio_pin_; -#endif - esp32_emac_config.clock_config.rmii.clock_mode = this->clk_mode_; - esp32_emac_config.clock_config.rmii.clock_gpio = (emac_rmii_clock_gpio_t) this->clk_pin_; - - esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config); -#endif - - switch (this->type_) { -#ifdef USE_ETHERNET_OPENETH - case ETHERNET_TYPE_OPENETH: { - phy_config.autonego_timeout_ms = 1000; - this->phy_ = esp_eth_phy_new_dp83848(&phy_config); - break; - } -#endif -#if CONFIG_ETH_USE_ESP32_EMAC -#ifdef USE_ETHERNET_LAN8720 - case ETHERNET_TYPE_LAN8720: { - this->phy_ = esp_eth_phy_new_lan87xx(&phy_config); - break; - } -#endif -#ifdef USE_ETHERNET_RTL8201 - case ETHERNET_TYPE_RTL8201: { - this->phy_ = esp_eth_phy_new_rtl8201(&phy_config); - break; - } -#endif -#ifdef USE_ETHERNET_DP83848 - case ETHERNET_TYPE_DP83848: { - this->phy_ = esp_eth_phy_new_dp83848(&phy_config); - break; - } -#endif -#ifdef USE_ETHERNET_IP101 - case ETHERNET_TYPE_IP101: { - this->phy_ = esp_eth_phy_new_ip101(&phy_config); - break; - } -#endif -#if defined(USE_ETHERNET_JL1101) && (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) || !defined(PLATFORMIO)) - case ETHERNET_TYPE_JL1101: { - this->phy_ = esp_eth_phy_new_jl1101(&phy_config); - break; - } -#endif -#ifdef USE_ETHERNET_KSZ8081 - case ETHERNET_TYPE_KSZ8081: - case ETHERNET_TYPE_KSZ8081RNA: { - this->phy_ = esp_eth_phy_new_ksz80xx(&phy_config); - break; - } -#endif -#ifdef USE_ETHERNET_LAN8670 - case ETHERNET_TYPE_LAN8670: { - this->phy_ = esp_eth_phy_new_lan867x(&phy_config); - break; - } -#endif -#endif -#ifdef USE_ETHERNET_SPI -#if CONFIG_ETH_SPI_ETHERNET_W5500 - case ETHERNET_TYPE_W5500: { - mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config); - this->phy_ = esp_eth_phy_new_w5500(&phy_config); - break; - } -#endif -#if CONFIG_ETH_SPI_ETHERNET_DM9051 - case ETHERNET_TYPE_DM9051: { - mac = esp_eth_mac_new_dm9051(&dm9051_config, &mac_config); - this->phy_ = esp_eth_phy_new_dm9051(&phy_config); - break; - } -#endif -#endif - default: { - this->mark_failed(); - return; - } - } - - esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, this->phy_); - this->eth_handle_ = nullptr; - err = esp_eth_driver_install(ð_config, &this->eth_handle_); - ESPHL_ERROR_CHECK(err, "ETH driver install error"); - -#ifndef USE_ETHERNET_SPI -#ifdef USE_ETHERNET_KSZ8081 - if (this->type_ == ETHERNET_TYPE_KSZ8081RNA && this->clk_mode_ == EMAC_CLK_OUT) { - // KSZ8081RNA default is incorrect. It expects a 25MHz clock instead of the 50MHz we provide. - this->ksz8081_set_clock_reference_(mac); - } -#endif // USE_ETHERNET_KSZ8081 - - for (const auto &phy_register : this->phy_registers_) { - this->write_phy_register_(mac, phy_register); - } -#endif - - // use ESP internal eth mac - uint8_t mac_addr[6]; - if (this->fixed_mac_.has_value()) { - memcpy(mac_addr, this->fixed_mac_->data(), 6); - } else { - esp_read_mac(mac_addr, ESP_MAC_ETH); - } - err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_S_MAC_ADDR, mac_addr); - ESPHL_ERROR_CHECK(err, "set mac address error"); - - /* attach Ethernet driver to TCP/IP stack */ - err = esp_netif_attach(this->eth_netif_, esp_eth_new_netif_glue(this->eth_handle_)); - ESPHL_ERROR_CHECK(err, "ETH netif attach error"); - - // Register user defined event handers - err = esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &EthernetComponent::eth_event_handler, nullptr); - ESPHL_ERROR_CHECK(err, "ETH event handler register error"); - err = esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &EthernetComponent::got_ip_event_handler, nullptr); - ESPHL_ERROR_CHECK(err, "GOT IP event handler register error"); -#if USE_NETWORK_IPV6 - err = esp_event_handler_register(IP_EVENT, IP_EVENT_GOT_IP6, &EthernetComponent::got_ip6_event_handler, nullptr); - ESPHL_ERROR_CHECK(err, "GOT IPv6 event handler register error"); -#endif /* USE_NETWORK_IPV6 */ - - /* start Ethernet driver state machine */ - err = esp_eth_start(this->eth_handle_); - ESPHL_ERROR_CHECK(err, "ETH start error"); -} - -void EthernetComponent::loop() { - const uint32_t now = App.get_loop_component_start_time(); - - switch (this->state_) { - case EthernetComponentState::STOPPED: - if (this->started_) { - ESP_LOGI(TAG, "Starting connection"); - this->state_ = EthernetComponentState::CONNECTING; - this->start_connect_(); - } - break; - case EthernetComponentState::CONNECTING: - if (!this->started_) { - ESP_LOGI(TAG, "Stopped connection"); - this->state_ = EthernetComponentState::STOPPED; - } else if (this->connected_) { - // connection established - ESP_LOGI(TAG, "Connected"); - this->state_ = EthernetComponentState::CONNECTED; - - this->dump_connect_params_(); - this->status_clear_warning(); -#ifdef USE_ETHERNET_CONNECT_TRIGGER - this->connect_trigger_.trigger(); -#endif - } else if (now - this->connect_begin_ > 15000) { - ESP_LOGW(TAG, "Connecting failed; reconnecting"); - this->start_connect_(); - } - break; - case EthernetComponentState::CONNECTED: - if (!this->started_) { - ESP_LOGI(TAG, "Stopped connection"); - this->state_ = EthernetComponentState::STOPPED; -#ifdef USE_ETHERNET_DISCONNECT_TRIGGER - this->disconnect_trigger_.trigger(); -#endif - } else if (!this->connected_) { - ESP_LOGW(TAG, "Connection lost; reconnecting"); - this->state_ = EthernetComponentState::CONNECTING; - this->start_connect_(); -#ifdef USE_ETHERNET_DISCONNECT_TRIGGER - this->disconnect_trigger_.trigger(); -#endif - } else { - this->finish_connect_(); - // When connected and stable, disable the loop to save CPU cycles - this->disable_loop(); - } - break; - } -} - -void EthernetComponent::dump_config() { - const char *eth_type; - switch (this->type_) { -#ifdef USE_ETHERNET_LAN8720 - case ETHERNET_TYPE_LAN8720: - eth_type = "LAN8720"; - break; -#endif -#ifdef USE_ETHERNET_RTL8201 - case ETHERNET_TYPE_RTL8201: - eth_type = "RTL8201"; - break; -#endif -#ifdef USE_ETHERNET_DP83848 - case ETHERNET_TYPE_DP83848: - eth_type = "DP83848"; - break; -#endif -#ifdef USE_ETHERNET_IP101 - case ETHERNET_TYPE_IP101: - eth_type = "IP101"; - break; -#endif -#if defined(USE_ETHERNET_JL1101) && (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) || !defined(PLATFORMIO)) - case ETHERNET_TYPE_JL1101: - eth_type = "JL1101"; - break; -#endif -#ifdef USE_ETHERNET_KSZ8081 - case ETHERNET_TYPE_KSZ8081: - eth_type = "KSZ8081"; - break; - - case ETHERNET_TYPE_KSZ8081RNA: - eth_type = "KSZ8081RNA"; - break; -#endif -#if CONFIG_ETH_SPI_ETHERNET_W5500 - case ETHERNET_TYPE_W5500: - eth_type = "W5500"; - break; -#endif -#if CONFIG_ETH_SPI_ETHERNET_DM9051 - case ETHERNET_TYPE_DM9051: - eth_type = "DM9051"; - break; -#endif -#ifdef USE_ETHERNET_OPENETH - case ETHERNET_TYPE_OPENETH: - eth_type = "OPENETH"; - break; -#endif -#ifdef USE_ETHERNET_LAN8670 - case ETHERNET_TYPE_LAN8670: - eth_type = "LAN8670"; - break; -#endif - - default: - eth_type = "Unknown"; - break; - } - - ESP_LOGCONFIG(TAG, - "Ethernet:\n" - " Connected: %s", - YESNO(this->is_connected())); - this->dump_connect_params_(); -#ifdef USE_ETHERNET_SPI - ESP_LOGCONFIG(TAG, - " CLK Pin: %u\n" - " MISO Pin: %u\n" - " MOSI Pin: %u\n" - " CS Pin: %u", - this->clk_pin_, this->miso_pin_, this->mosi_pin_, this->cs_pin_); -#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT - if (this->polling_interval_ != 0) { - ESP_LOGCONFIG(TAG, " Polling Interval: %lu ms", this->polling_interval_); - } else -#endif - { - ESP_LOGCONFIG(TAG, " IRQ Pin: %d", this->interrupt_pin_); - } - ESP_LOGCONFIG(TAG, - " Reset Pin: %d\n" - " Clock Speed: %d MHz", - this->reset_pin_, this->clock_speed_ / 1000000); -#else - if (this->power_pin_ != -1) { - ESP_LOGCONFIG(TAG, " Power Pin: %u", this->power_pin_); - } - ESP_LOGCONFIG(TAG, - " CLK Pin: %u\n" - " MDC Pin: %u\n" - " MDIO Pin: %u\n" - " PHY addr: %u", - this->clk_pin_, this->mdc_pin_, this->mdio_pin_, this->phy_addr_); -#endif - ESP_LOGCONFIG(TAG, " Type: %s", eth_type); -} - float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; } -network::IPAddresses EthernetComponent::get_ip_addresses() { - network::IPAddresses addresses; - esp_netif_ip_info_t ip; - esp_err_t err = esp_netif_get_ip_info(this->eth_netif_, &ip); - if (err != ESP_OK) { - ESP_LOGV(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err)); - // TODO: do something smarter - // return false; - } else { - addresses[0] = network::IPAddress(&ip.ip); - } -#if USE_NETWORK_IPV6 - struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES]; - uint8_t count = 0; - count = esp_netif_get_all_ip6(this->eth_netif_, if_ip6s); - assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES); - assert(count < addresses.size()); - for (int i = 0; i < count; i++) { - addresses[i + 1] = network::IPAddress(&if_ip6s[i]); - } -#endif /* USE_NETWORK_IPV6 */ +void EthernetComponent::set_type(EthernetType type) { this->type_ = type; } - return addresses; -} - -network::IPAddress EthernetComponent::get_dns_address(uint8_t num) { - LwIPLock lock; - const ip_addr_t *dns_ip = dns_getserver(num); - return dns_ip; -} - -void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event, void *event_data) { - const char *event_name; - - switch (event) { - case ETHERNET_EVENT_START: - event_name = "ETH started"; - global_eth_component->started_ = true; - global_eth_component->enable_loop_soon_any_context(); - break; - case ETHERNET_EVENT_STOP: - event_name = "ETH stopped"; - global_eth_component->started_ = false; - global_eth_component->connected_ = false; - global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes - break; - case ETHERNET_EVENT_CONNECTED: - event_name = "ETH connected"; - // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here -#if defined(USE_ETHERNET_IP_STATE_LISTENERS) && defined(USE_ETHERNET_MANUAL_IP) - if (global_eth_component->manual_ip_.has_value()) { - global_eth_component->notify_ip_state_listeners_(); - } +#ifdef USE_ETHERNET_MANUAL_IP +void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; } #endif - break; - case ETHERNET_EVENT_DISCONNECTED: - event_name = "ETH disconnected"; - global_eth_component->connected_ = false; - global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes - break; - default: - return; - } - - ESP_LOGV(TAG, "[Ethernet event] %s (num=%" PRId32 ")", event_name, event); -} - -void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, - void *event_data) { - ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data; - const esp_netif_ip_info_t *ip_info = &event->ip_info; - ESP_LOGV(TAG, "[Ethernet event] ETH Got IP " IPSTR, IP2STR(&ip_info->ip)); - global_eth_component->got_ipv4_address_ = true; -#if USE_NETWORK_IPV6 && (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0) - global_eth_component->connected_ = global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT; - global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes -#else - global_eth_component->connected_ = true; - global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes -#endif /* USE_NETWORK_IPV6 */ -#ifdef USE_ETHERNET_IP_STATE_LISTENERS - global_eth_component->notify_ip_state_listeners_(); -#endif -} - -#if USE_NETWORK_IPV6 -void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, - void *event_data) { - ip_event_got_ip6_t *event = (ip_event_got_ip6_t *) event_data; - ESP_LOGV(TAG, "[Ethernet event] ETH Got IPv6: " IPV6STR, IPV62STR(event->ip6_info.ip)); - global_eth_component->ipv6_count_ += 1; -#if (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0) - global_eth_component->connected_ = - global_eth_component->got_ipv4_address_ && (global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT); - global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes -#else - global_eth_component->connected_ = global_eth_component->got_ipv4_address_; - global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes -#endif -#ifdef USE_ETHERNET_IP_STATE_LISTENERS - global_eth_component->notify_ip_state_listeners_(); -#endif -} -#endif /* USE_NETWORK_IPV6 */ #ifdef USE_ETHERNET_IP_STATE_LISTENERS void EthernetComponent::notify_ip_state_listeners_() { @@ -554,315 +29,6 @@ void EthernetComponent::notify_ip_state_listeners_() { } #endif // USE_ETHERNET_IP_STATE_LISTENERS -void EthernetComponent::finish_connect_() { -#if USE_NETWORK_IPV6 - // Retry IPv6 link-local setup if it failed during initial connect - // This handles the case where min_ipv6_addr_count is NOT set (or is 0), - // allowing us to reach CONNECTED state with just IPv4. - // If IPv6 setup failed in start_connect_() because the interface wasn't ready: - // - Bootup timing issues (#10281) - // - Cable unplugged/network interruption (#10705) - // We can now retry since we're in CONNECTED state and the interface is definitely up. - if (!this->ipv6_setup_done_) { - esp_err_t err = esp_netif_create_ip6_linklocal(this->eth_netif_); - if (err == ESP_OK) { - ESP_LOGD(TAG, "IPv6 link-local address created (retry succeeded)"); - } - // Always set the flag to prevent continuous retries - // If IPv6 setup fails here with the interface up and stable, it's - // likely a persistent issue (IPv6 disabled at router, hardware - // limitation, etc.) that won't be resolved by further retries. - // The device continues to work with IPv4. - this->ipv6_setup_done_ = true; - } -#endif /* USE_NETWORK_IPV6 */ -} - -void EthernetComponent::start_connect_() { - global_eth_component->got_ipv4_address_ = false; -#if USE_NETWORK_IPV6 - global_eth_component->ipv6_count_ = 0; - this->ipv6_setup_done_ = false; -#endif /* USE_NETWORK_IPV6 */ - this->connect_begin_ = millis(); - this->status_set_warning(LOG_STR("waiting for IP configuration")); - - esp_err_t err; - err = esp_netif_set_hostname(this->eth_netif_, App.get_name().c_str()); - if (err != ERR_OK) { - ESP_LOGW(TAG, "esp_netif_set_hostname failed: %s", esp_err_to_name(err)); - } - - esp_netif_ip_info_t info; -#ifdef USE_ETHERNET_MANUAL_IP - if (this->manual_ip_.has_value()) { - info.ip = this->manual_ip_->static_ip; - info.gw = this->manual_ip_->gateway; - info.netmask = this->manual_ip_->subnet; - } else -#endif - { - info.ip.addr = 0; - info.gw.addr = 0; - info.netmask.addr = 0; - } - - esp_netif_dhcp_status_t status = ESP_NETIF_DHCP_INIT; - - err = esp_netif_dhcpc_get_status(this->eth_netif_, &status); - ESPHL_ERROR_CHECK(err, "DHCPC Get Status Failed!"); - - ESP_LOGV(TAG, "DHCP Client Status: %d", status); - - err = esp_netif_dhcpc_stop(this->eth_netif_); - if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) { - ESPHL_ERROR_CHECK(err, "DHCPC stop error"); - } - - err = esp_netif_set_ip_info(this->eth_netif_, &info); - ESPHL_ERROR_CHECK(err, "DHCPC set IP info error"); - -#ifdef USE_ETHERNET_MANUAL_IP - if (this->manual_ip_.has_value()) { - LwIPLock lock; - if (this->manual_ip_->dns1.is_set()) { - ip_addr_t d; - d = this->manual_ip_->dns1; - dns_setserver(0, &d); - } - if (this->manual_ip_->dns2.is_set()) { - ip_addr_t d; - d = this->manual_ip_->dns2; - dns_setserver(1, &d); - } - } else -#endif - { - err = esp_netif_dhcpc_start(this->eth_netif_); - if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) { - ESPHL_ERROR_CHECK(err, "DHCPC start error"); - } - } -#if USE_NETWORK_IPV6 - // Attempt to create IPv6 link-local address - // We MUST attempt this here, not just in finish_connect_(), because with - // min_ipv6_addr_count set, the component won't reach CONNECTED state without IPv6. - // However, this may fail with ESP_FAIL if the interface is not up yet: - // - At bootup when link isn't ready (#10281) - // - After disconnection/cable unplugged (#10705) - // We'll retry in finish_connect_() if it fails here. - err = esp_netif_create_ip6_linklocal(this->eth_netif_); - if (err != ESP_OK) { - if (err == ESP_ERR_ESP_NETIF_INVALID_PARAMS) { - // This is a programming error, not a transient failure - ESPHL_ERROR_CHECK(err, "esp_netif_create_ip6_linklocal invalid parameters"); - } else { - // ESP_FAIL means the interface isn't up yet - // This is expected and non-fatal, happens in multiple scenarios: - // - During reconnection after network interruptions (#10705) - // - At bootup when the link isn't ready yet (#10281) - // We'll retry once we reach CONNECTED state and the interface is up - ESP_LOGW(TAG, "esp_netif_create_ip6_linklocal failed: %s", esp_err_to_name(err)); - // Don't mark component as failed - this is a transient error - } - } -#endif /* USE_NETWORK_IPV6 */ - - this->connect_begin_ = millis(); - this->status_set_warning(); -} - -void EthernetComponent::dump_connect_params_() { - esp_netif_ip_info_t ip; - esp_netif_get_ip_info(this->eth_netif_, &ip); - const ip_addr_t *dns_ip1; - const ip_addr_t *dns_ip2; - { - LwIPLock lock; - dns_ip1 = dns_getserver(0); - dns_ip2 = dns_getserver(1); - } - - // Use stack buffers for IP address formatting to avoid heap allocations - char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; - char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE]; - char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE]; - char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE]; - char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE]; - char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; - ESP_LOGCONFIG(TAG, - " IP Address: %s\n" - " Hostname: '%s'\n" - " Subnet: %s\n" - " Gateway: %s\n" - " DNS1: %s\n" - " DNS2: %s\n" - " MAC Address: %s\n" - " Is Full Duplex: %s\n" - " Link Speed: %u", - network::IPAddress(&ip.ip).str_to(ip_buf), App.get_name().c_str(), - network::IPAddress(&ip.netmask).str_to(subnet_buf), network::IPAddress(&ip.gw).str_to(gateway_buf), - network::IPAddress(dns_ip1).str_to(dns1_buf), network::IPAddress(dns_ip2).str_to(dns2_buf), - this->get_eth_mac_address_pretty_into_buffer(mac_buf), - YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL), this->get_link_speed() == ETH_SPEED_100M ? 100 : 10); - -#if USE_NETWORK_IPV6 - struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES]; - uint8_t count = 0; - count = esp_netif_get_all_ip6(this->eth_netif_, if_ip6s); - assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES); - for (int i = 0; i < count; i++) { - ESP_LOGCONFIG(TAG, " IPv6: " IPV6STR, IPV62STR(if_ip6s[i])); - } -#endif /* USE_NETWORK_IPV6 */ -} - -#ifdef USE_ETHERNET_SPI -void EthernetComponent::set_clk_pin(uint8_t clk_pin) { this->clk_pin_ = clk_pin; } -void EthernetComponent::set_miso_pin(uint8_t miso_pin) { this->miso_pin_ = miso_pin; } -void EthernetComponent::set_mosi_pin(uint8_t mosi_pin) { this->mosi_pin_ = mosi_pin; } -void EthernetComponent::set_cs_pin(uint8_t cs_pin) { this->cs_pin_ = cs_pin; } -void EthernetComponent::set_interrupt_pin(uint8_t interrupt_pin) { this->interrupt_pin_ = interrupt_pin; } -void EthernetComponent::set_reset_pin(uint8_t reset_pin) { this->reset_pin_ = reset_pin; } -void EthernetComponent::set_clock_speed(int clock_speed) { this->clock_speed_ = clock_speed; } -#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT -void EthernetComponent::set_polling_interval(uint32_t polling_interval) { this->polling_interval_ = polling_interval; } -#endif -#else -void EthernetComponent::set_phy_addr(uint8_t phy_addr) { this->phy_addr_ = phy_addr; } -void EthernetComponent::set_power_pin(int power_pin) { this->power_pin_ = power_pin; } -void EthernetComponent::set_mdc_pin(uint8_t mdc_pin) { this->mdc_pin_ = mdc_pin; } -void EthernetComponent::set_mdio_pin(uint8_t mdio_pin) { this->mdio_pin_ = mdio_pin; } -void EthernetComponent::set_clk_pin(uint8_t clk_pin) { this->clk_pin_ = clk_pin; } -void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode) { this->clk_mode_ = clk_mode; } -void EthernetComponent::add_phy_register(PHYRegister register_value) { this->phy_registers_.push_back(register_value); } -#endif -void EthernetComponent::set_type(EthernetType type) { this->type_ = type; } -#ifdef USE_ETHERNET_MANUAL_IP -void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; } -#endif - -// set_use_address() is guaranteed to be called during component setup by Python code generation, -// so use_address_ will always be valid when get_use_address() is called - no fallback needed. -const char *EthernetComponent::get_use_address() const { return this->use_address_; } - -void EthernetComponent::set_use_address(const char *use_address) { this->use_address_ = use_address; } - -void EthernetComponent::get_eth_mac_address_raw(uint8_t *mac) { - esp_err_t err; - err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_MAC_ADDR, mac); - ESPHL_ERROR_CHECK(err, "ETH_CMD_G_MAC error"); -} - -std::string EthernetComponent::get_eth_mac_address_pretty() { - char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; - return std::string(this->get_eth_mac_address_pretty_into_buffer(buf)); -} - -const char *EthernetComponent::get_eth_mac_address_pretty_into_buffer( - std::span buf) { - uint8_t mac[6]; - get_eth_mac_address_raw(mac); - format_mac_addr_upper(mac, buf.data()); - return buf.data(); -} - -eth_duplex_t EthernetComponent::get_duplex_mode() { - esp_err_t err; - eth_duplex_t duplex_mode; - err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_DUPLEX_MODE, &duplex_mode); - ESPHL_ERROR_CHECK_RET(err, "ETH_CMD_G_DUPLEX_MODE error", ETH_DUPLEX_HALF); - return duplex_mode; -} - -eth_speed_t EthernetComponent::get_link_speed() { - esp_err_t err; - eth_speed_t speed; - err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_SPEED, &speed); - ESPHL_ERROR_CHECK_RET(err, "ETH_CMD_G_SPEED error", ETH_SPEED_10M); - return speed; -} - -bool EthernetComponent::powerdown() { - ESP_LOGI(TAG, "Powering down ethernet PHY"); - if (this->phy_ == nullptr) { - ESP_LOGE(TAG, "Ethernet PHY not assigned"); - return false; - } - this->connected_ = false; - this->started_ = false; - // No need to enable_loop() here as this is only called during shutdown/reboot - if (this->phy_->pwrctl(this->phy_, false) != ESP_OK) { - ESP_LOGE(TAG, "Error powering down ethernet PHY"); - return false; - } - return true; -} - -#ifndef USE_ETHERNET_SPI - -#ifdef USE_ETHERNET_KSZ8081 -constexpr uint8_t KSZ80XX_PC2R_REG_ADDR = 0x1F; - -void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) { - esp_err_t err; - - uint32_t phy_control_2; - err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2)); - ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed"); -#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE - char hex_buf[format_hex_pretty_size(PHY_REG_SIZE)]; -#endif - ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE)); - - /* - * Bit 7 is `RMII Reference Clock Select`. Default is `0`. - * KSZ8081RNA: - * 0 - clock input to XI (Pin 8) is 25 MHz for RMII - 25 MHz clock mode. - * 1 - clock input to XI (Pin 8) is 50 MHz for RMII - 50 MHz clock mode. - * KSZ8081RND: - * 0 - clock input to XI (Pin 8) is 50 MHz for RMII - 50 MHz clock mode. - * 1 - clock input to XI (Pin 8) is 25 MHz (driven clock only, not a crystal) for RMII - 25 MHz clock mode. - */ - if ((phy_control_2 & (1 << 7)) != (1 << 7)) { - phy_control_2 |= 1 << 7; - err = mac->write_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, phy_control_2); - ESPHL_ERROR_CHECK(err, "Write PHY Control 2 failed"); - err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2)); - ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed"); - ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", - format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE)); - } -} -#endif // USE_ETHERNET_KSZ8081 - -void EthernetComponent::write_phy_register_(esp_eth_mac_t *mac, PHYRegister register_data) { - esp_err_t err; - -#ifdef USE_ETHERNET_RTL8201 - constexpr uint8_t eth_phy_psr_reg_addr = 0x1F; - if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) { - ESP_LOGD(TAG, "Select PHY Register Page: 0x%02" PRIX32, register_data.page); - err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, register_data.page); - ESPHL_ERROR_CHECK(err, "Select PHY Register page failed"); - } -#endif - - ESP_LOGD(TAG, "Writing PHY reg 0x%02" PRIX32 " = 0x%04" PRIX32, register_data.address, register_data.value); - err = mac->write_phy_reg(mac, this->phy_addr_, register_data.address, register_data.value); - ESPHL_ERROR_CHECK(err, "Writing PHY Register failed"); - -#ifdef USE_ETHERNET_RTL8201 - if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) { - ESP_LOGD(TAG, "Select PHY Register Page 0x00"); - err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, 0x0); - ESPHL_ERROR_CHECK(err, "Select PHY Register Page 0 failed"); - } -#endif -} - -#endif - } // namespace esphome::ethernet -#endif // USE_ESP32 +#endif // USE_ETHERNET diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index f7a0996fb7..3a87842315 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -7,9 +7,13 @@ #include "esphome/core/automation.h" #include "esphome/components/network/ip_address.h" -#ifdef USE_ESP32 +#ifdef USE_ETHERNET +#ifdef USE_ESP32 #include "esp_eth.h" +#ifdef USE_ETHERNET_SPI +#include "hal/spi_types.h" +#endif #include "esp_eth_mac.h" #include "esp_eth_mac_esp.h" #include "esp_netif.h" @@ -19,6 +23,33 @@ #if CONFIG_ETH_USE_ESP32_EMAC extern "C" eth_esp32_emac_config_t eth_esp32_emac_default_config(void); #endif +#endif // USE_ESP32 + +#ifdef USE_RP2040 +#if defined(USE_ETHERNET_W5500) +#include +#elif defined(USE_ETHERNET_W5100) +#include +#elif defined(USE_ETHERNET_W6100) +#include +#elif defined(USE_ETHERNET_W6300) +#include +// W6300 uses PIO QSPI, not Arduino SPI. The upstream Wiznet6300 class +// incorrectly returns needsSPI()=true, causing LwipIntfDev::begin() to +// call SPI.begin() which claims GPIOs that PIO QSPI needs. +// This wrapper hides needsSPI() with a version returning false. +class Wiznet6300NoSPI : public Wiznet6300 { + public: + using Wiznet6300::Wiznet6300; + constexpr bool needsSPI() const { return false; } +}; +using Wiznet6300lwIPFixed = LwipIntfDev; +#elif defined(USE_ETHERNET_ENC28J60) +#include +#else +#error "Unsupported RP2040 SPI Ethernet type" +#endif +#endif namespace esphome::ethernet { @@ -47,10 +78,14 @@ enum EthernetType : uint8_t { ETHERNET_TYPE_JL1101, ETHERNET_TYPE_KSZ8081, ETHERNET_TYPE_KSZ8081RNA, + ETHERNET_TYPE_W5100, ETHERNET_TYPE_W5500, ETHERNET_TYPE_OPENETH, ETHERNET_TYPE_DM9051, ETHERNET_TYPE_LAN8670, + ETHERNET_TYPE_ENC28J60, + ETHERNET_TYPE_W6100, + ETHERNET_TYPE_W6300, }; struct ManualIP { @@ -73,7 +108,13 @@ enum class EthernetComponentState : uint8_t { CONNECTED, }; -class EthernetComponent : public Component { +// Platform-neutral duplex/speed types +#ifndef USE_ESP32 +enum eth_duplex_t { ETH_DUPLEX_HALF, ETH_DUPLEX_FULL }; +enum eth_speed_t { ETH_SPEED_10M, ETH_SPEED_100M }; +#endif + +class EthernetComponent final : public Component { public: EthernetComponent(); void setup() override; @@ -83,6 +124,28 @@ class EthernetComponent : public Component { void on_powerdown() override { powerdown(); } bool is_connected() { return this->state_ == EthernetComponentState::CONNECTED; } + void set_type(EthernetType type); +#ifdef USE_ETHERNET_MANUAL_IP + void set_manual_ip(const ManualIP &manual_ip); +#endif + void set_fixed_mac(const std::array &mac) { this->fixed_mac_ = mac; } + + network::IPAddresses get_ip_addresses(); + network::IPAddress get_dns_address(uint8_t num); + const char *get_use_address() const { return this->use_address_; } + void set_use_address(const char *use_address) { this->use_address_ = use_address; } + void get_eth_mac_address_raw(uint8_t *mac); + // Remove before 2026.9.0 + ESPDEPRECATED("Use get_eth_mac_address_pretty_into_buffer() instead. Removed in 2026.9.0", "2026.3.0") + std::string get_eth_mac_address_pretty(); + const char *get_eth_mac_address_pretty_into_buffer(std::span buf); + eth_duplex_t get_duplex_mode(); + eth_speed_t get_link_speed(); + bool powerdown(); + +#ifdef USE_ESP32 + esp_eth_handle_t get_eth_handle() const { return this->eth_handle_; } + #ifdef USE_ETHERNET_SPI void set_clk_pin(uint8_t clk_pin); void set_miso_pin(uint8_t miso_pin); @@ -91,6 +154,7 @@ class EthernetComponent : public Component { void set_interrupt_pin(uint8_t interrupt_pin); void set_reset_pin(uint8_t reset_pin); void set_clock_speed(int clock_speed); + void set_interface(spi_host_device_t interface); #ifdef USE_ETHERNET_SPI_POLLING_SUPPORT void set_polling_interval(uint32_t polling_interval); #endif @@ -102,26 +166,17 @@ class EthernetComponent : public Component { void set_clk_pin(uint8_t clk_pin); void set_clk_mode(emac_rmii_clock_mode_t clk_mode); void add_phy_register(PHYRegister register_value); -#endif - void set_type(EthernetType type); -#ifdef USE_ETHERNET_MANUAL_IP - void set_manual_ip(const ManualIP &manual_ip); -#endif - void set_fixed_mac(const std::array &mac) { this->fixed_mac_ = mac; } +#endif // USE_ETHERNET_SPI +#endif // USE_ESP32 - network::IPAddresses get_ip_addresses(); - network::IPAddress get_dns_address(uint8_t num); - const char *get_use_address() const; - void set_use_address(const char *use_address); - void get_eth_mac_address_raw(uint8_t *mac); - // Remove before 2026.9.0 - ESPDEPRECATED("Use get_eth_mac_address_pretty_into_buffer() instead. Removed in 2026.9.0", "2026.3.0") - std::string get_eth_mac_address_pretty(); - const char *get_eth_mac_address_pretty_into_buffer(std::span buf); - eth_duplex_t get_duplex_mode(); - eth_speed_t get_link_speed(); - esp_eth_handle_t get_eth_handle() const { return this->eth_handle_; } - bool powerdown(); +#ifdef USE_RP2040 + void set_clk_pin(uint8_t clk_pin); + void set_miso_pin(uint8_t miso_pin); + void set_mosi_pin(uint8_t mosi_pin); + void set_cs_pin(uint8_t cs_pin); + void set_interrupt_pin(int8_t interrupt_pin); + void set_reset_pin(int8_t reset_pin); +#endif // USE_RP2040 #ifdef USE_ETHERNET_IP_STATE_LISTENERS void add_ip_state_listener(EthernetIPStateListener *listener) { this->ip_state_listeners_.push_back(listener); } @@ -133,19 +188,22 @@ class EthernetComponent : public Component { #ifdef USE_ETHERNET_DISCONNECT_TRIGGER Trigger<> *get_disconnect_trigger() { return &this->disconnect_trigger_; } #endif + protected: + void start_connect_(); + void finish_connect_(); + void dump_connect_params_(); + +#ifdef USE_ETHERNET_IP_STATE_LISTENERS + void notify_ip_state_listeners_(); +#endif + +#ifdef USE_ESP32 static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); static void got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); #if LWIP_IPV6 static void got_ip6_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); #endif /* LWIP_IPV6 */ -#ifdef USE_ETHERNET_IP_STATE_LISTENERS - void notify_ip_state_listeners_(); -#endif - - void start_connect_(); - void finish_connect_(); - void dump_connect_params_(); void log_error_and_mark_failed_(esp_err_t err, const char *message); #ifdef USE_ETHERNET_KSZ8081 /// @brief Set `RMII Reference Clock Select` bit for KSZ8081. @@ -163,6 +221,7 @@ class EthernetComponent : public Component { int reset_pin_{-1}; int phy_addr_spi_{-1}; int clock_speed_; + spi_host_device_t interface_{SPI3_HOST}; #ifdef USE_ETHERNET_SPI_POLLING_SUPPORT uint32_t polling_interval_{0}; #endif @@ -177,7 +236,46 @@ class EthernetComponent : public Component { uint8_t phy_addr_{0}; uint8_t mdc_pin_{23}; uint8_t mdio_pin_{18}; +#endif // USE_ETHERNET_SPI + + // ESP32 pointers + esp_netif_t *eth_netif_{nullptr}; + esp_eth_handle_t eth_handle_; + esp_eth_phy_t *phy_{nullptr}; +#endif // USE_ESP32 + +#ifdef USE_RP2040 + static constexpr uint32_t LINK_CHECK_INTERVAL = 500; // ms between link/IP polls +#if defined(USE_ETHERNET_W5100) + static constexpr uint32_t RESET_DELAY_MS = 150; // W5100S PLL lock time +#elif defined(USE_ETHERNET_W6300) + static constexpr uint32_t RESET_DELAY_MS = 100; // W6300 needs 100ms after hardware reset +#else + static constexpr uint32_t RESET_DELAY_MS = 10; #endif +#if defined(USE_ETHERNET_W5500) + Wiznet5500lwIP *eth_{nullptr}; +#elif defined(USE_ETHERNET_W5100) + Wiznet5100lwIP *eth_{nullptr}; +#elif defined(USE_ETHERNET_W6100) + Wiznet6100lwIP *eth_{nullptr}; +#elif defined(USE_ETHERNET_W6300) + Wiznet6300lwIPFixed *eth_{nullptr}; +#elif defined(USE_ETHERNET_ENC28J60) + ENC28J60lwIP *eth_{nullptr}; +#else +#error "Unsupported RP2040 SPI Ethernet type" +#endif + uint32_t last_link_check_{0}; + uint8_t clk_pin_; + uint8_t miso_pin_; + uint8_t mosi_pin_; + uint8_t cs_pin_; + int8_t interrupt_pin_{-1}; + int8_t reset_pin_{-1}; +#endif // USE_RP2040 + + // Common members #ifdef USE_ETHERNET_MANUAL_IP optional manual_ip_{}; #endif @@ -194,10 +292,6 @@ class EthernetComponent : public Component { bool ipv6_setup_done_{false}; #endif /* LWIP_IPV6 */ - // Pointers at the end (naturally aligned) - esp_netif_t *eth_netif_{nullptr}; - esp_eth_handle_t eth_handle_; - esp_eth_phy_t *phy_{nullptr}; optional> fixed_mac_; #ifdef USE_ETHERNET_IP_STATE_LISTENERS @@ -219,10 +313,13 @@ class EthernetComponent : public Component { // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) extern EthernetComponent *global_eth_component; -#if defined(USE_ETHERNET_JL1101) && (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) || !defined(PLATFORMIO)) +#ifdef USE_ESP32 +#if defined(USE_ETHERNET_JL1101) && (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) || \ + ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) || !defined(PLATFORMIO)) extern "C" esp_eth_phy_t *esp_eth_phy_new_jl1101(const eth_phy_config_t *config); #endif +#endif // USE_ESP32 } // namespace esphome::ethernet -#endif // USE_ESP32 +#endif // USE_ETHERNET diff --git a/esphome/components/ethernet/ethernet_component_esp32.cpp b/esphome/components/ethernet/ethernet_component_esp32.cpp new file mode 100644 index 0000000000..d4585bf100 --- /dev/null +++ b/esphome/components/ethernet/ethernet_component_esp32.cpp @@ -0,0 +1,894 @@ +#include "ethernet_component.h" + +#if defined(USE_ETHERNET) && defined(USE_ESP32) + +#include "esphome/core/application.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#include +#include +#include "esp_event.h" + +// IDF 6.0 moved per-chip PHY/MAC drivers to the Espressif Component Registry; +// they are no longer included via esp_eth.h and need explicit includes. +// On IDF 5.x these headers don't exist as standalone files. +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) +#ifdef USE_ETHERNET_LAN8720 +#include "esp_eth_phy_lan87xx.h" +#endif +#ifdef USE_ETHERNET_RTL8201 +#include "esp_eth_phy_rtl8201.h" +#endif +#ifdef USE_ETHERNET_DP83848 +#include "esp_eth_phy_dp83848.h" +#endif +#ifdef USE_ETHERNET_IP101 +#include "esp_eth_phy_ip101.h" +#endif +#ifdef USE_ETHERNET_KSZ8081 +#include "esp_eth_phy_ksz80xx.h" +#endif +#ifdef USE_ETHERNET_W5500 +#include "esp_eth_mac_w5500.h" +#include "esp_eth_phy_w5500.h" +#endif +#ifdef USE_ETHERNET_DM9051 +#include "esp_eth_mac_dm9051.h" +#include "esp_eth_phy_dm9051.h" +#endif +#endif // ESP_IDF_VERSION >= 6.0.0 + +// LAN867x header exists on all IDF versions (external component since IDF 5.3) +#ifdef USE_ETHERNET_LAN8670 +#include "esp_eth_phy_lan867x.h" +#endif + +// ENC28J60 header exists on all IDF versions (always an external component) +#ifdef USE_ETHERNET_ENC28J60 +#include "esp_eth_enc28j60.h" +#endif + +#ifdef USE_ETHERNET_SPI +#include +#include +#endif + +namespace esphome::ethernet { + +static const char *const TAG = "ethernet"; + +// PHY register size for hex logging +static constexpr size_t PHY_REG_SIZE = 2; + +void EthernetComponent::log_error_and_mark_failed_(esp_err_t err, const char *message) { + ESP_LOGE(TAG, "%s: (%d) %s", message, err, esp_err_to_name(err)); + this->mark_failed(); +} + +#define ESPHL_ERROR_CHECK(err, message) \ + if ((err) != ESP_OK) { \ + this->log_error_and_mark_failed_(err, message); \ + return; \ + } + +#define ESPHL_ERROR_CHECK_RET(err, message, ret) \ + if ((err) != ESP_OK) { \ + this->log_error_and_mark_failed_(err, message); \ + return ret; \ + } + +void EthernetComponent::loop() { + const uint32_t now = App.get_loop_component_start_time(); + + switch (this->state_) { + case EthernetComponentState::STOPPED: + if (this->started_) { + ESP_LOGI(TAG, "Starting connection"); + this->state_ = EthernetComponentState::CONNECTING; + this->start_connect_(); + } + break; + case EthernetComponentState::CONNECTING: + if (!this->started_) { + ESP_LOGI(TAG, "Stopped connection"); + this->state_ = EthernetComponentState::STOPPED; + } else if (this->connected_) { + // connection established + ESP_LOGI(TAG, "Connected"); + this->state_ = EthernetComponentState::CONNECTED; + + this->dump_connect_params_(); + this->status_clear_warning(); +#ifdef USE_ETHERNET_CONNECT_TRIGGER + this->connect_trigger_.trigger(); +#endif + } else if (now - this->connect_begin_ > 15000) { + ESP_LOGW(TAG, "Connecting failed; reconnecting"); + this->start_connect_(); + } + break; + case EthernetComponentState::CONNECTED: + if (!this->started_) { + ESP_LOGI(TAG, "Stopped connection"); + this->state_ = EthernetComponentState::STOPPED; +#ifdef USE_ETHERNET_DISCONNECT_TRIGGER + this->disconnect_trigger_.trigger(); +#endif + } else if (!this->connected_) { + ESP_LOGW(TAG, "Connection lost; reconnecting"); + this->state_ = EthernetComponentState::CONNECTING; + this->start_connect_(); +#ifdef USE_ETHERNET_DISCONNECT_TRIGGER + this->disconnect_trigger_.trigger(); +#endif + } else { + this->finish_connect_(); + // When connected and stable, disable the loop to save CPU cycles + this->disable_loop(); + } + break; + } +} + +void EthernetComponent::setup() { + if (esp_reset_reason() != ESP_RST_DEEPSLEEP) { + // Delay here to allow power to stabilise before Ethernet is initialized. + delay(300); // NOLINT + } + + esp_err_t err; + +#ifdef USE_ETHERNET_SPI + // Install GPIO ISR handler to be able to service SPI Eth modules interrupts + gpio_install_isr_service(0); + + spi_bus_config_t buscfg = { + .mosi_io_num = this->mosi_pin_, + .miso_io_num = this->miso_pin_, + .sclk_io_num = this->clk_pin_, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .data4_io_num = -1, + .data5_io_num = -1, + .data6_io_num = -1, + .data7_io_num = -1, + .max_transfer_sz = 0, + .flags = 0, + .intr_flags = 0, + }; + + auto host = this->interface_; + + err = spi_bus_initialize(host, &buscfg, SPI_DMA_CH_AUTO); + ESPHL_ERROR_CHECK(err, "SPI bus initialize error"); +#endif + + err = esp_netif_init(); + ESPHL_ERROR_CHECK(err, "ETH netif init error"); + err = esp_event_loop_create_default(); + ESPHL_ERROR_CHECK(err, "ETH event loop error"); + + esp_netif_config_t cfg = ESP_NETIF_DEFAULT_ETH(); + this->eth_netif_ = esp_netif_new(&cfg); + + // Init MAC and PHY configs to default + eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); + eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); + +#ifdef USE_ETHERNET_SPI // Configure SPI interface and Ethernet driver for specific SPI module + spi_device_interface_config_t devcfg = { + .command_bits = 0, + .address_bits = 0, + .dummy_bits = 0, + .mode = 0, + .duty_cycle_pos = 0, + .cs_ena_pretrans = 0, + .cs_ena_posttrans = 0, + .clock_speed_hz = this->clock_speed_, + .input_delay_ns = 0, + .spics_io_num = this->cs_pin_, + .flags = 0, + .queue_size = 20, + .pre_cb = nullptr, + .post_cb = nullptr, + }; + +#if defined(USE_ETHERNET_W5500) + eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(host, &devcfg); +#elif defined(USE_ETHERNET_DM9051) + eth_dm9051_config_t dm9051_config = ETH_DM9051_DEFAULT_CONFIG(host, &devcfg); +#elif defined(USE_ETHERNET_ENC28J60) + eth_enc28j60_config_t enc28j60_config = ETH_ENC28J60_DEFAULT_CONFIG(host, &devcfg); +#endif + +#if defined(USE_ETHERNET_W5500) + w5500_config.int_gpio_num = this->interrupt_pin_; +#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT + w5500_config.poll_period_ms = this->polling_interval_; +#endif +#elif defined(USE_ETHERNET_DM9051) + dm9051_config.int_gpio_num = this->interrupt_pin_; +#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT + dm9051_config.poll_period_ms = this->polling_interval_; +#endif +#elif defined(USE_ETHERNET_ENC28J60) + enc28j60_config.int_gpio_num = this->interrupt_pin_; + // ENC28J60 does not support poll_period_ms +#endif + + phy_config.phy_addr = this->phy_addr_spi_; + phy_config.reset_gpio_num = this->reset_pin_; + + esp_eth_mac_t *mac = nullptr; +#elif defined(USE_ETHERNET_OPENETH) + esp_eth_mac_t *mac = esp_eth_mac_new_openeth(&mac_config); +#else + phy_config.phy_addr = this->phy_addr_; + phy_config.reset_gpio_num = this->power_pin_; + + eth_esp32_emac_config_t esp32_emac_config = eth_esp32_emac_default_config(); +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + esp32_emac_config.smi_gpio.mdc_num = this->mdc_pin_; + esp32_emac_config.smi_gpio.mdio_num = this->mdio_pin_; +#else + esp32_emac_config.smi_mdc_gpio_num = this->mdc_pin_; + esp32_emac_config.smi_mdio_gpio_num = this->mdio_pin_; +#endif + esp32_emac_config.clock_config.rmii.clock_mode = this->clk_mode_; + esp32_emac_config.clock_config.rmii.clock_gpio = + static_cast(this->clk_pin_); + + esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config); +#endif + + switch (this->type_) { +#ifdef USE_ETHERNET_OPENETH + case ETHERNET_TYPE_OPENETH: { + phy_config.autonego_timeout_ms = 1000; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + this->phy_ = esp_eth_phy_new_generic(&phy_config); +#else + this->phy_ = esp_eth_phy_new_dp83848(&phy_config); +#endif + break; + } +#endif +#if CONFIG_ETH_USE_ESP32_EMAC +#ifdef USE_ETHERNET_LAN8720 + case ETHERNET_TYPE_LAN8720: { + this->phy_ = esp_eth_phy_new_lan87xx(&phy_config); + break; + } +#endif +#ifdef USE_ETHERNET_RTL8201 + case ETHERNET_TYPE_RTL8201: { + this->phy_ = esp_eth_phy_new_rtl8201(&phy_config); + break; + } +#endif +#ifdef USE_ETHERNET_DP83848 + case ETHERNET_TYPE_DP83848: { + this->phy_ = esp_eth_phy_new_dp83848(&phy_config); + break; + } +#endif +#ifdef USE_ETHERNET_IP101 + case ETHERNET_TYPE_IP101: { + this->phy_ = esp_eth_phy_new_ip101(&phy_config); + break; + } +#endif +#ifdef USE_ETHERNET_JL1101 + case ETHERNET_TYPE_JL1101: { + // PlatformIO (pioarduino): builtin esp_eth_phy_new_jl1101() on all IDF versions + // Non-PlatformIO: custom ESPHome driver (esp_eth_phy_jl1101.c) + this->phy_ = esp_eth_phy_new_jl1101(&phy_config); + break; + } +#endif +#ifdef USE_ETHERNET_KSZ8081 + case ETHERNET_TYPE_KSZ8081: + case ETHERNET_TYPE_KSZ8081RNA: { + this->phy_ = esp_eth_phy_new_ksz80xx(&phy_config); + break; + } +#endif +#ifdef USE_ETHERNET_LAN8670 + case ETHERNET_TYPE_LAN8670: { + this->phy_ = esp_eth_phy_new_lan867x(&phy_config); + break; + } +#endif +#endif +#ifdef USE_ETHERNET_SPI +#if defined(USE_ETHERNET_W5500) + case ETHERNET_TYPE_W5500: { + mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config); + this->phy_ = esp_eth_phy_new_w5500(&phy_config); + break; + } +#elif defined(USE_ETHERNET_DM9051) + case ETHERNET_TYPE_DM9051: { + mac = esp_eth_mac_new_dm9051(&dm9051_config, &mac_config); + this->phy_ = esp_eth_phy_new_dm9051(&phy_config); + break; + } +#elif defined(USE_ETHERNET_ENC28J60) + case ETHERNET_TYPE_ENC28J60: { + mac = esp_eth_mac_new_enc28j60(&enc28j60_config, &mac_config); + this->phy_ = esp_eth_phy_new_enc28j60(&phy_config); + break; + } +#endif +#endif + default: { + this->mark_failed(); + return; + } + } + + esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, this->phy_); + this->eth_handle_ = nullptr; + err = esp_eth_driver_install(ð_config, &this->eth_handle_); + ESPHL_ERROR_CHECK(err, "ETH driver install error"); + +#ifndef USE_ETHERNET_SPI +#ifdef USE_ETHERNET_KSZ8081 + if (this->type_ == ETHERNET_TYPE_KSZ8081RNA && this->clk_mode_ == EMAC_CLK_OUT) { + // KSZ8081RNA default is incorrect. It expects a 25MHz clock instead of the 50MHz we provide. + this->ksz8081_set_clock_reference_(mac); + } +#endif // USE_ETHERNET_KSZ8081 + + for (const auto &phy_register : this->phy_registers_) { + this->write_phy_register_(mac, phy_register); + } +#endif + + // use ESP internal eth mac + uint8_t mac_addr[6]; + if (this->fixed_mac_.has_value()) { + memcpy(mac_addr, this->fixed_mac_->data(), 6); + } else { + esp_read_mac(mac_addr, ESP_MAC_ETH); + } + err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_S_MAC_ADDR, mac_addr); + ESPHL_ERROR_CHECK(err, "set mac address error"); + + /* attach Ethernet driver to TCP/IP stack */ + err = esp_netif_attach(this->eth_netif_, esp_eth_new_netif_glue(this->eth_handle_)); + ESPHL_ERROR_CHECK(err, "ETH netif attach error"); + + // Register user defined event handers + err = esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &EthernetComponent::eth_event_handler, nullptr); + ESPHL_ERROR_CHECK(err, "ETH event handler register error"); + err = esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &EthernetComponent::got_ip_event_handler, nullptr); + ESPHL_ERROR_CHECK(err, "GOT IP event handler register error"); +#if USE_NETWORK_IPV6 + err = esp_event_handler_register(IP_EVENT, IP_EVENT_GOT_IP6, &EthernetComponent::got_ip6_event_handler, nullptr); + ESPHL_ERROR_CHECK(err, "GOT IPv6 event handler register error"); +#endif /* USE_NETWORK_IPV6 */ + + /* start Ethernet driver state machine */ + err = esp_eth_start(this->eth_handle_); + ESPHL_ERROR_CHECK(err, "ETH start error"); +} + +void EthernetComponent::dump_config() { + const char *eth_type; + switch (this->type_) { +#ifdef USE_ETHERNET_LAN8720 + case ETHERNET_TYPE_LAN8720: + eth_type = "LAN8720"; + break; +#endif +#ifdef USE_ETHERNET_RTL8201 + case ETHERNET_TYPE_RTL8201: + eth_type = "RTL8201"; + break; +#endif +#ifdef USE_ETHERNET_DP83848 + case ETHERNET_TYPE_DP83848: + eth_type = "DP83848"; + break; +#endif +#ifdef USE_ETHERNET_IP101 + case ETHERNET_TYPE_IP101: + eth_type = "IP101"; + break; +#endif +#ifdef USE_ETHERNET_JL1101 + case ETHERNET_TYPE_JL1101: + eth_type = "JL1101"; + break; +#endif +#ifdef USE_ETHERNET_KSZ8081 + case ETHERNET_TYPE_KSZ8081: + eth_type = "KSZ8081"; + break; + + case ETHERNET_TYPE_KSZ8081RNA: + eth_type = "KSZ8081RNA"; + break; +#endif +#if defined(USE_ETHERNET_W5500) + case ETHERNET_TYPE_W5500: + eth_type = "W5500"; + break; +#elif defined(USE_ETHERNET_DM9051) + case ETHERNET_TYPE_DM9051: + eth_type = "DM9051"; + break; +#elif defined(USE_ETHERNET_ENC28J60) + case ETHERNET_TYPE_ENC28J60: + eth_type = "ENC28J60"; + break; +#endif +#ifdef USE_ETHERNET_OPENETH + case ETHERNET_TYPE_OPENETH: + eth_type = "OPENETH"; + break; +#endif +#ifdef USE_ETHERNET_LAN8670 + case ETHERNET_TYPE_LAN8670: + eth_type = "LAN8670"; + break; +#endif + + default: + eth_type = "Unknown"; + break; + } + + ESP_LOGCONFIG(TAG, + "Ethernet:\n" + " Connected: %s", + YESNO(this->is_connected())); + this->dump_connect_params_(); +#ifdef USE_ETHERNET_SPI + ESP_LOGCONFIG(TAG, + " CLK Pin: %u\n" + " MISO Pin: %u\n" + " MOSI Pin: %u\n" + " CS Pin: %u", + this->clk_pin_, this->miso_pin_, this->mosi_pin_, this->cs_pin_); + const char *spi_interface = "spi3"; + if (this->interface_ == SPI2_HOST) { + spi_interface = "spi2"; + } + ESP_LOGCONFIG(TAG, " Interface: %s", spi_interface); +#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT + if (this->polling_interval_ != 0) { + ESP_LOGCONFIG(TAG, " Polling Interval: %" PRIu32 " ms", this->polling_interval_); + } else +#endif + { + ESP_LOGCONFIG(TAG, " IRQ Pin: %d", this->interrupt_pin_); + } + ESP_LOGCONFIG(TAG, + " Reset Pin: %d\n" + " Clock Speed: %d MHz", + this->reset_pin_, this->clock_speed_ / 1000000); +#else + if (this->power_pin_ != -1) { + ESP_LOGCONFIG(TAG, " Power Pin: %u", this->power_pin_); + } + ESP_LOGCONFIG(TAG, + " CLK Pin: %u\n" + " MDC Pin: %u\n" + " MDIO Pin: %u\n" + " PHY addr: %u", + this->clk_pin_, this->mdc_pin_, this->mdio_pin_, this->phy_addr_); +#endif + ESP_LOGCONFIG(TAG, " Type: %s", eth_type); +} + +network::IPAddresses EthernetComponent::get_ip_addresses() { + network::IPAddresses addresses; + esp_netif_ip_info_t ip; + esp_err_t err = esp_netif_get_ip_info(this->eth_netif_, &ip); + if (err != ESP_OK) { + ESP_LOGV(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err)); + // TODO: do something smarter + // return false; + } else { + addresses[0] = network::IPAddress(&ip.ip); + } +#if USE_NETWORK_IPV6 + struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES]; + uint8_t count = 0; + count = esp_netif_get_all_ip6(this->eth_netif_, if_ip6s); + assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES); + assert(count < addresses.size()); + for (int i = 0; i < count; i++) { + addresses[i + 1] = network::IPAddress(&if_ip6s[i]); + } +#endif /* USE_NETWORK_IPV6 */ + + return addresses; +} + +network::IPAddress EthernetComponent::get_dns_address(uint8_t num) { + LwIPLock lock; + const ip_addr_t *dns_ip = dns_getserver(num); + return dns_ip; +} + +void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event, void *event_data) { + const char *event_name; + + switch (event) { + case ETHERNET_EVENT_START: + event_name = "ETH started"; + global_eth_component->started_ = true; + global_eth_component->enable_loop_soon_any_context(); + break; + case ETHERNET_EVENT_STOP: + event_name = "ETH stopped"; + global_eth_component->started_ = false; + global_eth_component->connected_ = false; + global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes + break; + case ETHERNET_EVENT_CONNECTED: + event_name = "ETH connected"; + // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here +#if defined(USE_ETHERNET_IP_STATE_LISTENERS) && defined(USE_ETHERNET_MANUAL_IP) + if (global_eth_component->manual_ip_.has_value()) { + global_eth_component->notify_ip_state_listeners_(); + } +#endif + break; + case ETHERNET_EVENT_DISCONNECTED: + event_name = "ETH disconnected"; + global_eth_component->connected_ = false; + global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes + break; + default: + return; + } + + ESP_LOGV(TAG, "[Ethernet event] %s (num=%" PRId32 ")", event_name, event); +} + +void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, + void *event_data) { + ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data; + const esp_netif_ip_info_t *ip_info = &event->ip_info; + ESP_LOGV(TAG, "[Ethernet event] ETH Got IP " IPSTR, IP2STR(&ip_info->ip)); + global_eth_component->got_ipv4_address_ = true; +#if USE_NETWORK_IPV6 && (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0) + global_eth_component->connected_ = global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT; + global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes +#else + global_eth_component->connected_ = true; + global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes +#endif /* USE_NETWORK_IPV6 */ +#ifdef USE_ETHERNET_IP_STATE_LISTENERS + global_eth_component->notify_ip_state_listeners_(); +#endif +} + +#if USE_NETWORK_IPV6 +void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, + void *event_data) { + ip_event_got_ip6_t *event = (ip_event_got_ip6_t *) event_data; + ESP_LOGV(TAG, "[Ethernet event] ETH Got IPv6: " IPV6STR, IPV62STR(event->ip6_info.ip)); + global_eth_component->ipv6_count_ += 1; +#if (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0) + global_eth_component->connected_ = + global_eth_component->got_ipv4_address_ && (global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT); + global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes +#else + global_eth_component->connected_ = global_eth_component->got_ipv4_address_; + global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes +#endif +#ifdef USE_ETHERNET_IP_STATE_LISTENERS + global_eth_component->notify_ip_state_listeners_(); +#endif +} +#endif /* USE_NETWORK_IPV6 */ + +void EthernetComponent::finish_connect_() { +#if USE_NETWORK_IPV6 + // Retry IPv6 link-local setup if it failed during initial connect + // This handles the case where min_ipv6_addr_count is NOT set (or is 0), + // allowing us to reach CONNECTED state with just IPv4. + // If IPv6 setup failed in start_connect_() because the interface wasn't ready: + // - Bootup timing issues (#10281) + // - Cable unplugged/network interruption (#10705) + // We can now retry since we're in CONNECTED state and the interface is definitely up. + if (!this->ipv6_setup_done_) { + esp_err_t err = esp_netif_create_ip6_linklocal(this->eth_netif_); + if (err == ESP_OK) { + ESP_LOGD(TAG, "IPv6 link-local address created (retry succeeded)"); + } + // Always set the flag to prevent continuous retries + // If IPv6 setup fails here with the interface up and stable, it's + // likely a persistent issue (IPv6 disabled at router, hardware + // limitation, etc.) that won't be resolved by further retries. + // The device continues to work with IPv4. + this->ipv6_setup_done_ = true; + } +#endif /* USE_NETWORK_IPV6 */ +} + +void EthernetComponent::start_connect_() { + global_eth_component->got_ipv4_address_ = false; +#if USE_NETWORK_IPV6 + global_eth_component->ipv6_count_ = 0; + this->ipv6_setup_done_ = false; +#endif /* USE_NETWORK_IPV6 */ + this->connect_begin_ = millis(); + this->status_set_warning(LOG_STR("waiting for IP configuration")); + + esp_err_t err; + err = esp_netif_set_hostname(this->eth_netif_, App.get_name().c_str()); + if (err != ERR_OK) { + ESP_LOGW(TAG, "esp_netif_set_hostname failed: %s", esp_err_to_name(err)); + } + + esp_netif_ip_info_t info; +#ifdef USE_ETHERNET_MANUAL_IP + if (this->manual_ip_.has_value()) { + info.ip = this->manual_ip_->static_ip; + info.gw = this->manual_ip_->gateway; + info.netmask = this->manual_ip_->subnet; + } else +#endif + { + info.ip.addr = 0; + info.gw.addr = 0; + info.netmask.addr = 0; + } + + esp_netif_dhcp_status_t status = ESP_NETIF_DHCP_INIT; + + err = esp_netif_dhcpc_get_status(this->eth_netif_, &status); + ESPHL_ERROR_CHECK(err, "DHCPC Get Status Failed!"); + + ESP_LOGV(TAG, "DHCP Client Status: %d", status); + + err = esp_netif_dhcpc_stop(this->eth_netif_); + if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) { + ESPHL_ERROR_CHECK(err, "DHCPC stop error"); + } + + err = esp_netif_set_ip_info(this->eth_netif_, &info); + ESPHL_ERROR_CHECK(err, "DHCPC set IP info error"); + +#ifdef USE_ETHERNET_MANUAL_IP + if (this->manual_ip_.has_value()) { + LwIPLock lock; + if (this->manual_ip_->dns1.is_set()) { + ip_addr_t d; + d = this->manual_ip_->dns1; + dns_setserver(0, &d); + } + if (this->manual_ip_->dns2.is_set()) { + ip_addr_t d; + d = this->manual_ip_->dns2; + dns_setserver(1, &d); + } + } else +#endif + { + err = esp_netif_dhcpc_start(this->eth_netif_); + if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) { + ESPHL_ERROR_CHECK(err, "DHCPC start error"); + } + } +#if USE_NETWORK_IPV6 + // Attempt to create IPv6 link-local address + // We MUST attempt this here, not just in finish_connect_(), because with + // min_ipv6_addr_count set, the component won't reach CONNECTED state without IPv6. + // However, this may fail with ESP_FAIL if the interface is not up yet: + // - At bootup when link isn't ready (#10281) + // - After disconnection/cable unplugged (#10705) + // We'll retry in finish_connect_() if it fails here. + err = esp_netif_create_ip6_linklocal(this->eth_netif_); + if (err != ESP_OK) { + if (err == ESP_ERR_ESP_NETIF_INVALID_PARAMS) { + // This is a programming error, not a transient failure + ESPHL_ERROR_CHECK(err, "esp_netif_create_ip6_linklocal invalid parameters"); + } else { + // ESP_FAIL means the interface isn't up yet + // This is expected and non-fatal, happens in multiple scenarios: + // - During reconnection after network interruptions (#10705) + // - At bootup when the link isn't ready yet (#10281) + // We'll retry once we reach CONNECTED state and the interface is up + ESP_LOGW(TAG, "esp_netif_create_ip6_linklocal failed: %s", esp_err_to_name(err)); + // Don't mark component as failed - this is a transient error + } + } +#endif /* USE_NETWORK_IPV6 */ + + this->connect_begin_ = millis(); + this->status_set_warning(); +} + +void EthernetComponent::dump_connect_params_() { + esp_netif_ip_info_t ip; + esp_netif_get_ip_info(this->eth_netif_, &ip); + const ip_addr_t *dns_ip1; + const ip_addr_t *dns_ip2; + { + LwIPLock lock; + dns_ip1 = dns_getserver(0); + dns_ip2 = dns_getserver(1); + } + + // Use stack buffers for IP address formatting to avoid heap allocations + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGCONFIG(TAG, + " IP Address: %s\n" + " Hostname: '%s'\n" + " Subnet: %s\n" + " Gateway: %s\n" + " DNS1: %s\n" + " DNS2: %s\n" + " MAC Address: %s\n" + " Is Full Duplex: %s\n" + " Link Speed: %u", + network::IPAddress(&ip.ip).str_to(ip_buf), App.get_name().c_str(), + network::IPAddress(&ip.netmask).str_to(subnet_buf), network::IPAddress(&ip.gw).str_to(gateway_buf), + network::IPAddress(dns_ip1).str_to(dns1_buf), network::IPAddress(dns_ip2).str_to(dns2_buf), + this->get_eth_mac_address_pretty_into_buffer(mac_buf), + YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL), this->get_link_speed() == ETH_SPEED_100M ? 100 : 10); + +#if USE_NETWORK_IPV6 + struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES]; + uint8_t count = 0; + count = esp_netif_get_all_ip6(this->eth_netif_, if_ip6s); + assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES); + for (int i = 0; i < count; i++) { + ESP_LOGCONFIG(TAG, " IPv6: " IPV6STR, IPV62STR(if_ip6s[i])); + } +#endif /* USE_NETWORK_IPV6 */ +} + +#ifdef USE_ETHERNET_SPI +void EthernetComponent::set_clk_pin(uint8_t clk_pin) { this->clk_pin_ = clk_pin; } +void EthernetComponent::set_miso_pin(uint8_t miso_pin) { this->miso_pin_ = miso_pin; } +void EthernetComponent::set_mosi_pin(uint8_t mosi_pin) { this->mosi_pin_ = mosi_pin; } +void EthernetComponent::set_cs_pin(uint8_t cs_pin) { this->cs_pin_ = cs_pin; } +void EthernetComponent::set_interrupt_pin(uint8_t interrupt_pin) { this->interrupt_pin_ = interrupt_pin; } +void EthernetComponent::set_reset_pin(uint8_t reset_pin) { this->reset_pin_ = reset_pin; } +void EthernetComponent::set_clock_speed(int clock_speed) { this->clock_speed_ = clock_speed; } +void EthernetComponent::set_interface(spi_host_device_t interface) { this->interface_ = interface; } +#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT +void EthernetComponent::set_polling_interval(uint32_t polling_interval) { this->polling_interval_ = polling_interval; } +#endif +#else +void EthernetComponent::set_phy_addr(uint8_t phy_addr) { this->phy_addr_ = phy_addr; } +void EthernetComponent::set_power_pin(int power_pin) { this->power_pin_ = power_pin; } +void EthernetComponent::set_mdc_pin(uint8_t mdc_pin) { this->mdc_pin_ = mdc_pin; } +void EthernetComponent::set_mdio_pin(uint8_t mdio_pin) { this->mdio_pin_ = mdio_pin; } +void EthernetComponent::set_clk_pin(uint8_t clk_pin) { this->clk_pin_ = clk_pin; } +void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode) { this->clk_mode_ = clk_mode; } +void EthernetComponent::add_phy_register(PHYRegister register_value) { this->phy_registers_.push_back(register_value); } +#endif + +void EthernetComponent::get_eth_mac_address_raw(uint8_t *mac) { + esp_err_t err; + err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_MAC_ADDR, mac); + ESPHL_ERROR_CHECK(err, "ETH_CMD_G_MAC error"); +} + +std::string EthernetComponent::get_eth_mac_address_pretty() { + char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + return std::string(this->get_eth_mac_address_pretty_into_buffer(buf)); +} + +const char *EthernetComponent::get_eth_mac_address_pretty_into_buffer( + std::span buf) { + uint8_t mac[6]; + get_eth_mac_address_raw(mac); + format_mac_addr_upper(mac, buf.data()); + return buf.data(); +} + +eth_duplex_t EthernetComponent::get_duplex_mode() { + esp_err_t err; + eth_duplex_t duplex_mode; + err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_DUPLEX_MODE, &duplex_mode); + ESPHL_ERROR_CHECK_RET(err, "ETH_CMD_G_DUPLEX_MODE error", ETH_DUPLEX_HALF); + return duplex_mode; +} + +eth_speed_t EthernetComponent::get_link_speed() { + esp_err_t err; + eth_speed_t speed; + err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_SPEED, &speed); + ESPHL_ERROR_CHECK_RET(err, "ETH_CMD_G_SPEED error", ETH_SPEED_10M); + return speed; +} + +bool EthernetComponent::powerdown() { + ESP_LOGI(TAG, "Powering down ethernet PHY"); + if (this->phy_ == nullptr) { + ESP_LOGE(TAG, "Ethernet PHY not assigned"); + return false; + } + this->connected_ = false; + this->started_ = false; + // No need to enable_loop() here as this is only called during shutdown/reboot + if (this->phy_->pwrctl(this->phy_, false) != ESP_OK) { + ESP_LOGE(TAG, "Error powering down ethernet PHY"); + return false; + } + return true; +} + +#ifndef USE_ETHERNET_SPI + +#ifdef USE_ETHERNET_KSZ8081 +constexpr uint8_t KSZ80XX_PC2R_REG_ADDR = 0x1F; + +void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) { + esp_err_t err; + + uint32_t phy_control_2; + err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2)); + ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed"); +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE + char hex_buf[format_hex_pretty_size(PHY_REG_SIZE)]; +#endif + ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE)); + + /* + * Bit 7 is `RMII Reference Clock Select`. Default is `0`. + * KSZ8081RNA: + * 0 - clock input to XI (Pin 8) is 25 MHz for RMII - 25 MHz clock mode. + * 1 - clock input to XI (Pin 8) is 50 MHz for RMII - 50 MHz clock mode. + * KSZ8081RND: + * 0 - clock input to XI (Pin 8) is 50 MHz for RMII - 50 MHz clock mode. + * 1 - clock input to XI (Pin 8) is 25 MHz (driven clock only, not a crystal) for RMII - 25 MHz clock mode. + */ + if ((phy_control_2 & (1 << 7)) != (1 << 7)) { + phy_control_2 |= 1 << 7; + err = mac->write_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, phy_control_2); + ESPHL_ERROR_CHECK(err, "Write PHY Control 2 failed"); + err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2)); + ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed"); + ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", + format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE)); + } +} +#endif // USE_ETHERNET_KSZ8081 + +void EthernetComponent::write_phy_register_(esp_eth_mac_t *mac, PHYRegister register_data) { + esp_err_t err; + +#ifdef USE_ETHERNET_RTL8201 + constexpr uint8_t eth_phy_psr_reg_addr = 0x1F; + if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) { + ESP_LOGD(TAG, "Select PHY Register Page: 0x%02" PRIX32, register_data.page); + err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, register_data.page); + ESPHL_ERROR_CHECK(err, "Select PHY Register page failed"); + } +#endif + + ESP_LOGD(TAG, "Writing PHY reg 0x%02" PRIX32 " = 0x%04" PRIX32, register_data.address, register_data.value); + err = mac->write_phy_reg(mac, this->phy_addr_, register_data.address, register_data.value); + ESPHL_ERROR_CHECK(err, "Writing PHY Register failed"); + +#ifdef USE_ETHERNET_RTL8201 + if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) { + ESP_LOGD(TAG, "Select PHY Register Page 0x00"); + err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, 0x0); + ESPHL_ERROR_CHECK(err, "Select PHY Register Page 0 failed"); + } +#endif +} + +#endif + +} // namespace esphome::ethernet + +#endif // USE_ETHERNET && USE_ESP32 diff --git a/esphome/components/ethernet/ethernet_component_rp2040.cpp b/esphome/components/ethernet/ethernet_component_rp2040.cpp new file mode 100644 index 0000000000..ef7bd46332 --- /dev/null +++ b/esphome/components/ethernet/ethernet_component_rp2040.cpp @@ -0,0 +1,366 @@ +#include "ethernet_component.h" + +#if defined(USE_ETHERNET) && defined(USE_RP2040) + +#include "esphome/core/application.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#include "esphome/components/rp2040/gpio.h" + +#include +#include +#include + +namespace esphome::ethernet { + +static const char *const TAG = "ethernet"; + +void EthernetComponent::setup() { + // Configure SPI pins +#if !defined(USE_ETHERNET_W6300) + SPI.setRX(this->miso_pin_); + SPI.setTX(this->mosi_pin_); + SPI.setSCK(this->clk_pin_); +#endif + // W6300 uses PIO QSPI with hardcoded pins, not Arduino SPI. + // SPI pin config is skipped; Wiznet6300lwIPFixed (needsSPI()=false) + // prevents LwipIntfDev::begin() from calling SPI.begin(). + + // Toggle reset pin if configured + if (this->reset_pin_ >= 0) { + rp2040::RP2040GPIOPin reset_pin; + reset_pin.set_pin(this->reset_pin_); + reset_pin.set_flags(gpio::FLAG_OUTPUT); + reset_pin.setup(); + reset_pin.digital_write(false); + delay(1); // NOLINT + reset_pin.digital_write(true); + // W5100S needs 150ms for PLL lock; W5500/ENC28J60 need ~10ms + delay(RESET_DELAY_MS); // NOLINT + } + + // Create the SPI Ethernet device instance +#if defined(USE_ETHERNET_W5500) + this->eth_ = new Wiznet5500lwIP(this->cs_pin_, SPI, this->interrupt_pin_); // NOLINT +#elif defined(USE_ETHERNET_W5100) + this->eth_ = new Wiznet5100lwIP(this->cs_pin_, SPI, this->interrupt_pin_); // NOLINT +#elif defined(USE_ETHERNET_W6100) + this->eth_ = new Wiznet6100lwIP(this->cs_pin_, SPI, this->interrupt_pin_); // NOLINT +#elif defined(USE_ETHERNET_W6300) + this->eth_ = new Wiznet6300lwIPFixed(this->cs_pin_, SPI, this->interrupt_pin_); // NOLINT +#elif defined(USE_ETHERNET_ENC28J60) + this->eth_ = new ENC28J60lwIP(this->cs_pin_, SPI, this->interrupt_pin_); // NOLINT +#endif + + // Set hostname before begin() so the LWIP netif gets it + this->eth_->hostname(App.get_name().c_str()); + + // Configure static IP if set (must be done before begin()) +#ifdef USE_ETHERNET_MANUAL_IP + if (this->manual_ip_.has_value()) { + IPAddress ip(this->manual_ip_->static_ip); + IPAddress gateway(this->manual_ip_->gateway); + IPAddress subnet(this->manual_ip_->subnet); + IPAddress dns1(this->manual_ip_->dns1); + IPAddress dns2(this->manual_ip_->dns2); + this->eth_->config(ip, gateway, subnet, dns1, dns2); + } +#endif + + // Begin with fixed MAC or auto-generated + bool success; + if (this->fixed_mac_.has_value()) { + success = this->eth_->begin(this->fixed_mac_->data()); + } else { + success = this->eth_->begin(); + } + + if (!success) { + ESP_LOGE(TAG, "Failed to initialize Ethernet"); + delete this->eth_; // NOLINT(cppcoreguidelines-owning-memory) + this->eth_ = nullptr; + this->mark_failed(); + return; + } + + // Make this the default interface for routing + this->eth_->setDefault(true); + + // The arduino-pico LwipIntfDev automatically handles packet processing + // via __addEthernetPacketHandler when no interrupt pin is used, + // or via GPIO interrupt when one is provided. + + // Don't set started_ here — let the link polling in loop() set it + // when the link is actually up. Setting it prematurely causes + // a "Starting → Stopped → Starting" log sequence because the chip + // needs time after begin() before the PHY link is ready. +} + +void EthernetComponent::loop() { + // On RP2040, we need to poll connection state since there are no events. + const uint32_t now = App.get_loop_component_start_time(); + + // Throttle link/IP polling to avoid excessive SPI transactions. + // W5500/ENC28J60 read PHY register via SPI on every linkStatus() call. + // W5100 can't detect link state, so we skip the SPI read and assume link-up. + // connected() reads netif->ip_addr without LwIPLock, but this is a single + // 32-bit aligned read (atomic on ARM) — worst case is a one-iteration-stale + // value, which is benign for polling. + if (this->eth_ != nullptr && now - this->last_link_check_ >= LINK_CHECK_INTERVAL) { + this->last_link_check_ = now; +#if defined(USE_ETHERNET_W5100) + // W5100 can't detect link (isLinkDetectable() returns false), so linkStatus() + // returns Unknown — assume link is up after successful begin() + bool link_up = true; +#else + bool link_up = this->eth_->linkStatus() == LinkON; +#endif + bool has_ip = this->eth_->connected(); + + if (!link_up) { + if (this->started_) { + this->started_ = false; + this->connected_ = false; + } + } else { + if (!this->started_) { + this->started_ = true; + } + bool was_connected = this->connected_; + this->connected_ = has_ip; + if (this->connected_ && !was_connected) { +#ifdef USE_ETHERNET_IP_STATE_LISTENERS + this->notify_ip_state_listeners_(); +#endif + } + } + } + + // State machine + switch (this->state_) { + case EthernetComponentState::STOPPED: + if (this->started_) { + ESP_LOGI(TAG, "Starting connection"); + this->state_ = EthernetComponentState::CONNECTING; + this->start_connect_(); + } + break; + case EthernetComponentState::CONNECTING: + if (!this->started_) { + ESP_LOGI(TAG, "Stopped connection"); + this->state_ = EthernetComponentState::STOPPED; + } else if (this->connected_) { + // connection established + ESP_LOGI(TAG, "Connected"); + this->state_ = EthernetComponentState::CONNECTED; + + this->dump_connect_params_(); + this->status_clear_warning(); +#ifdef USE_ETHERNET_CONNECT_TRIGGER + this->connect_trigger_.trigger(); +#endif + } else if (now - this->connect_begin_ > 15000) { + ESP_LOGW(TAG, "Connecting failed; reconnecting"); + this->start_connect_(); + } + break; + case EthernetComponentState::CONNECTED: + if (!this->started_) { + ESP_LOGI(TAG, "Stopped connection"); + this->state_ = EthernetComponentState::STOPPED; +#ifdef USE_ETHERNET_DISCONNECT_TRIGGER + this->disconnect_trigger_.trigger(); +#endif + } else if (!this->connected_) { + ESP_LOGW(TAG, "Connection lost; reconnecting"); + this->state_ = EthernetComponentState::CONNECTING; + this->start_connect_(); +#ifdef USE_ETHERNET_DISCONNECT_TRIGGER + this->disconnect_trigger_.trigger(); +#endif + } else { + this->finish_connect_(); + } + break; + } +} + +void EthernetComponent::dump_config() { + const char *type_str = "Unknown"; +#if defined(USE_ETHERNET_W5500) + type_str = "W5500"; +#elif defined(USE_ETHERNET_W5100) + type_str = "W5100"; +#elif defined(USE_ETHERNET_W6100) + type_str = "W6100"; +#elif defined(USE_ETHERNET_W6300) + type_str = "W6300"; +#elif defined(USE_ETHERNET_ENC28J60) + type_str = "ENC28J60"; +#endif +#if defined(USE_ETHERNET_W6300) + // W6300 uses PIO QSPI with hardcoded pins — SPI pin fields are not used + ESP_LOGCONFIG(TAG, + "Ethernet:\n" + " Type: %s (PIO QSPI)\n" + " Connected: %s\n" + " IRQ Pin: %d\n" + " Reset Pin: %d", + type_str, YESNO(this->is_connected()), this->interrupt_pin_, this->reset_pin_); +#else + ESP_LOGCONFIG(TAG, + "Ethernet:\n" + " Type: %s\n" + " Connected: %s\n" + " CLK Pin: %u\n" + " MISO Pin: %u\n" + " MOSI Pin: %u\n" + " CS Pin: %u\n" + " IRQ Pin: %d\n" + " Reset Pin: %d", + type_str, YESNO(this->is_connected()), this->clk_pin_, this->miso_pin_, this->mosi_pin_, this->cs_pin_, + this->interrupt_pin_, this->reset_pin_); +#endif + this->dump_connect_params_(); +} + +network::IPAddresses EthernetComponent::get_ip_addresses() { + network::IPAddresses addresses; + if (this->eth_ != nullptr) { + LwIPLock lock; + addresses[0] = network::IPAddress(this->eth_->localIP()); + } + return addresses; +} + +network::IPAddress EthernetComponent::get_dns_address(uint8_t num) { + LwIPLock lock; + const ip_addr_t *dns_ip = dns_getserver(num); + return dns_ip; +} + +void EthernetComponent::get_eth_mac_address_raw(uint8_t *mac) { + if (this->eth_ != nullptr) { + this->eth_->macAddress(mac); + } else { + memset(mac, 0, 6); + } +} + +std::string EthernetComponent::get_eth_mac_address_pretty() { + char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + return std::string(this->get_eth_mac_address_pretty_into_buffer(buf)); +} + +const char *EthernetComponent::get_eth_mac_address_pretty_into_buffer( + std::span buf) { + uint8_t mac[6]; + get_eth_mac_address_raw(mac); + format_mac_addr_upper(mac, buf.data()); + return buf.data(); +} + +eth_duplex_t EthernetComponent::get_duplex_mode() { + // W5100, W5500, and ENC28J60 are full-duplex on RP2040 + return ETH_DUPLEX_FULL; +} + +eth_speed_t EthernetComponent::get_link_speed() { +#ifdef USE_ETHERNET_ENC28J60 + // ENC28J60 is 10Mbps only + return ETH_SPEED_10M; +#else + // W5100 and W5500 are 100Mbps + return ETH_SPEED_100M; +#endif +} + +bool EthernetComponent::powerdown() { + ESP_LOGI(TAG, "Powering down ethernet"); + if (this->eth_ != nullptr) { + this->eth_->end(); + } + this->connected_ = false; + this->started_ = false; + return true; +} + +void EthernetComponent::start_connect_() { + this->got_ipv4_address_ = false; + this->connect_begin_ = millis(); + this->status_set_warning(LOG_STR("waiting for IP configuration")); + + // Hostname is already set in setup() via LwipIntf::setHostname() + +#ifdef USE_ETHERNET_MANUAL_IP + if (this->manual_ip_.has_value()) { + // Static IP was already configured before begin() in setup() + // Set DNS servers + LwIPLock lock; + if (this->manual_ip_->dns1.is_set()) { + ip_addr_t d; + d = this->manual_ip_->dns1; + dns_setserver(0, &d); + } + if (this->manual_ip_->dns2.is_set()) { + ip_addr_t d; + d = this->manual_ip_->dns2; + dns_setserver(1, &d); + } + } +#endif +} + +void EthernetComponent::finish_connect_() { + // No additional work needed on RP2040 for now + // IPv6 link-local could be added here in the future +} + +void EthernetComponent::dump_connect_params_() { + if (this->eth_ == nullptr) { + return; + } + + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE]; + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + + // Copy all lwIP state under the lock to avoid races with IRQ callbacks + ip_addr_t ip_addr, netmask, gw, dns1_addr, dns2_addr; + { + LwIPLock lock; + auto *netif = this->eth_->getNetIf(); + ip_addr = netif->ip_addr; + netmask = netif->netmask; + gw = netif->gw; + dns1_addr = *dns_getserver(0); + dns2_addr = *dns_getserver(1); + } + ESP_LOGCONFIG(TAG, + " IP Address: %s\n" + " Hostname: '%s'\n" + " Subnet: %s\n" + " Gateway: %s\n" + " DNS1: %s\n" + " DNS2: %s\n" + " MAC Address: %s", + network::IPAddress(&ip_addr).str_to(ip_buf), App.get_name().c_str(), + network::IPAddress(&netmask).str_to(subnet_buf), network::IPAddress(&gw).str_to(gateway_buf), + network::IPAddress(&dns1_addr).str_to(dns1_buf), network::IPAddress(&dns2_addr).str_to(dns2_buf), + this->get_eth_mac_address_pretty_into_buffer(mac_buf)); +} + +void EthernetComponent::set_clk_pin(uint8_t clk_pin) { this->clk_pin_ = clk_pin; } +void EthernetComponent::set_miso_pin(uint8_t miso_pin) { this->miso_pin_ = miso_pin; } +void EthernetComponent::set_mosi_pin(uint8_t mosi_pin) { this->mosi_pin_ = mosi_pin; } +void EthernetComponent::set_cs_pin(uint8_t cs_pin) { this->cs_pin_ = cs_pin; } +void EthernetComponent::set_interrupt_pin(int8_t interrupt_pin) { this->interrupt_pin_ = interrupt_pin; } +void EthernetComponent::set_reset_pin(int8_t reset_pin) { this->reset_pin_ = reset_pin; } + +} // namespace esphome::ethernet + +#endif // USE_ETHERNET && USE_RP2040 diff --git a/esphome/components/ethernet/ethernet_helpers.c b/esphome/components/ethernet/ethernet_helpers.c index 963db3ff1c..49fbe825c8 100644 --- a/esphome/components/ethernet/ethernet_helpers.c +++ b/esphome/components/ethernet/ethernet_helpers.c @@ -1,3 +1,5 @@ +#include "esphome/core/defines.h" +#ifdef USE_ESP32 #include "esp_eth_mac_esp.h" // ETH_ESP32_EMAC_DEFAULT_CONFIG() uses out-of-order designated initializers @@ -8,3 +10,4 @@ eth_esp32_emac_config_t eth_esp32_emac_default_config(void) { return (eth_esp32_emac_config_t) ETH_ESP32_EMAC_DEFAULT_CONFIG(); } #endif +#endif // USE_ESP32 diff --git a/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp b/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp index 72ce9c86e2..15ef6a1f20 100644 --- a/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp +++ b/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp @@ -1,7 +1,7 @@ #include "ethernet_info_text_sensor.h" #include "esphome/core/log.h" -#ifdef USE_ESP32 +#ifdef USE_ETHERNET namespace esphome::ethernet_info { @@ -49,4 +49,4 @@ void MACAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo M } // namespace esphome::ethernet_info -#endif // USE_ESP32 +#endif // USE_ETHERNET diff --git a/esphome/components/ethernet_info/ethernet_info_text_sensor.h b/esphome/components/ethernet_info/ethernet_info_text_sensor.h index 912a39a83f..11002d51ba 100644 --- a/esphome/components/ethernet_info/ethernet_info_text_sensor.h +++ b/esphome/components/ethernet_info/ethernet_info_text_sensor.h @@ -4,7 +4,7 @@ #include "esphome/components/text_sensor/text_sensor.h" #include "esphome/components/ethernet/ethernet_component.h" -#ifdef USE_ESP32 +#ifdef USE_ETHERNET namespace esphome::ethernet_info { @@ -50,4 +50,4 @@ class MACAddressEthernetInfo final : public Component, public text_sensor::TextS } // namespace esphome::ethernet_info -#endif // USE_ESP32 +#endif // USE_ETHERNET diff --git a/esphome/components/event/__init__.py b/esphome/components/event/__init__.py index 300902b8ca..9c9dd025b1 100644 --- a/esphome/components/event/__init__.py +++ b/esphome/components/event/__init__.py @@ -10,7 +10,6 @@ from esphome.const import ( CONF_ID, CONF_MQTT_ID, CONF_ON_EVENT, - CONF_TRIGGER_ID, CONF_WEB_SERVER, DEVICE_CLASS_BUTTON, DEVICE_CLASS_DOORBELL, @@ -41,8 +40,6 @@ EventPtr = Event.operator("ptr") TriggerEventAction = event_ns.class_("TriggerEventAction", automation.Action) -EventTrigger = event_ns.class_("EventTrigger", automation.Trigger.template()) - validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") _EVENT_SCHEMA = ( @@ -53,11 +50,7 @@ _EVENT_SCHEMA = ( cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTEventComponent), cv.GenerateID(): cv.declare_id(Event), cv.Optional(CONF_DEVICE_CLASS): validate_device_class, - cv.Optional(CONF_ON_EVENT): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(EventTrigger), - } - ), + cv.Optional(CONF_ON_EVENT): automation.validate_automation({}), } ) ) @@ -89,11 +82,16 @@ def event_schema( return _EVENT_SCHEMA.extend(schema) +_CALLBACK_AUTOMATIONS = ( + automation.CallbackAutomation( + CONF_ON_EVENT, "add_on_event_callback", [(cg.StringRef, "event_type")] + ), +) + + @setup_entity("event") async def setup_event_core_(var, config, *, event_types: list[str]): - for conf in config.get(CONF_ON_EVENT, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.StringRef, "event_type")], conf) + await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS) cg.add(var.set_event_types(event_types)) diff --git a/esphome/components/event/event.cpp b/esphome/components/event/event.cpp index 667d4218f3..a5d64a2748 100644 --- a/esphome/components/event/event.cpp +++ b/esphome/components/event/event.cpp @@ -22,7 +22,7 @@ void Event::trigger(const std::string &event_type) { return; } this->last_event_type_ = found; - ESP_LOGD(TAG, "'%s' >> '%s'", this->get_name().c_str(), this->last_event_type_); + ESP_LOGV(TAG, "'%s' >> '%s'", this->get_name().c_str(), this->last_event_type_); this->event_callback_.call(StringRef(found)); #if defined(USE_EVENT) && defined(USE_CONTROLLER_REGISTRY) ControllerRegistry::notify_event(this); @@ -45,9 +45,5 @@ void Event::set_event_types(const std::vector &event_types) { this->last_event_type_ = nullptr; // Reset when types change } -void Event::add_on_event_callback(std::function &&callback) { - this->event_callback_.add(std::move(callback)); -} - } // namespace event } // namespace esphome diff --git a/esphome/components/event/event.h b/esphome/components/event/event.h index 5b6a94b47c..ebbee0bfe2 100644 --- a/esphome/components/event/event.h +++ b/esphome/components/event/event.h @@ -66,7 +66,9 @@ class Event : public EntityBase { /// Check if an event has been triggered. bool has_event() const { return this->last_event_type_ != nullptr; } - void add_on_event_callback(std::function &&callback); + template void add_on_event_callback(F &&callback) { + this->event_callback_.add(std::forward(callback)); + } protected: LazyCallbackManager event_callback_; diff --git a/esphome/components/ezo/automation.h b/esphome/components/ezo/automation.h deleted file mode 100644 index a4a6fa3014..0000000000 --- a/esphome/components/ezo/automation.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once -#include - -#include "esphome/core/automation.h" -#include "ezo.h" - -namespace esphome { -namespace ezo { - -class LedTrigger : public Trigger { - public: - explicit LedTrigger(EZOSensor *ezo) { - ezo->add_led_state_callback([this](bool value) { this->trigger(value); }); - } -}; - -class CustomTrigger : public Trigger { - public: - explicit CustomTrigger(EZOSensor *ezo) { - ezo->add_custom_callback([this](const std::string &value) { this->trigger(value); }); - } -}; - -class TTrigger : public Trigger { - public: - explicit TTrigger(EZOSensor *ezo) { - ezo->add_t_callback([this](const std::string &value) { this->trigger(value); }); - } -}; - -class CalibrationTrigger : public Trigger { - public: - explicit CalibrationTrigger(EZOSensor *ezo) { - ezo->add_calibration_callback([this](const std::string &value) { this->trigger(value); }); - } -}; - -class SlopeTrigger : public Trigger { - public: - explicit SlopeTrigger(EZOSensor *ezo) { - ezo->add_slope_callback([this](const std::string &value) { this->trigger(value); }); - } -}; - -class DeviceInformationTrigger : public Trigger { - public: - explicit DeviceInformationTrigger(EZOSensor *ezo) { - ezo->add_device_infomation_callback([this](const std::string &value) { this->trigger(value); }); - } -}; - -} // namespace ezo -} // namespace esphome diff --git a/esphome/components/ezo/ezo.h b/esphome/components/ezo/ezo.h index f1a2802cbd..d80869fbd9 100644 --- a/esphome/components/ezo/ezo.h +++ b/esphome/components/ezo/ezo.h @@ -44,8 +44,8 @@ class EZOSensor : public sensor::Sensor, public PollingComponent, public i2c::I2 // Device Information void get_device_information(); - void add_device_infomation_callback(std::function &&callback) { - this->device_infomation_callback_.add(std::move(callback)); + template void add_device_infomation_callback(F &&callback) { + this->device_infomation_callback_.add(std::forward(callback)); } // Sleep @@ -56,15 +56,13 @@ class EZOSensor : public sensor::Sensor, public PollingComponent, public i2c::I2 // Slope void get_slope(); - void add_slope_callback(std::function &&callback) { - this->slope_callback_.add(std::move(callback)); - } + template void add_slope_callback(F &&callback) { this->slope_callback_.add(std::forward(callback)); } // T void get_t(); void set_t(float value); void set_tempcomp_value(float temp); // For backwards compatibility - void add_t_callback(std::function &&callback) { this->t_callback_.add(std::move(callback)); } + template void add_t_callback(F &&callback) { this->t_callback_.add(std::forward(callback)); } // Calibration void get_calibration(); @@ -73,20 +71,18 @@ class EZOSensor : public sensor::Sensor, public PollingComponent, public i2c::I2 void set_calibration_point_high(float value); void set_calibration_generic(float value); void clear_calibration(); - void add_calibration_callback(std::function &&callback) { - this->calibration_callback_.add(std::move(callback)); + template void add_calibration_callback(F &&callback) { + this->calibration_callback_.add(std::forward(callback)); } // LED void get_led_state(); void set_led_state(bool on); - void add_led_state_callback(std::function &&callback) { this->led_callback_.add(std::move(callback)); } + template void add_led_state_callback(F &&callback) { this->led_callback_.add(std::forward(callback)); } // Custom void send_custom(const std::string &to_send); - void add_custom_callback(std::function &&callback) { - this->custom_callback_.add(std::move(callback)); - } + template void add_custom_callback(F &&callback) { this->custom_callback_.add(std::forward(callback)); } protected: std::deque> commands_; diff --git a/esphome/components/ezo/sensor.py b/esphome/components/ezo/sensor.py index cf240faec3..b931885149 100644 --- a/esphome/components/ezo/sensor.py +++ b/esphome/components/ezo/sensor.py @@ -2,7 +2,7 @@ from esphome import automation import esphome.codegen as cg from esphome.components import i2c, sensor import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_TRIGGER_ID +from esphome.const import CONF_ID CODEOWNERS = ["@ssieb"] @@ -21,61 +21,16 @@ EZOSensor = ezo_ns.class_( "EZOSensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice ) -CustomTrigger = ezo_ns.class_( - "CustomTrigger", automation.Trigger.template(cg.std_string) -) - - -TTrigger = ezo_ns.class_("TTrigger", automation.Trigger.template(cg.std_string)) - -SlopeTrigger = ezo_ns.class_("SlopeTrigger", automation.Trigger.template(cg.std_string)) - -CalibrationTrigger = ezo_ns.class_( - "CalibrationTrigger", automation.Trigger.template(cg.std_string) -) - -DeviceInformationTrigger = ezo_ns.class_( - "DeviceInformationTrigger", automation.Trigger.template(cg.std_string) -) - -LedTrigger = ezo_ns.class_("LedTrigger", automation.Trigger.template(cg.bool_)) - CONFIG_SCHEMA = ( sensor.sensor_schema(EZOSensor) .extend( { - cv.Optional(CONF_ON_CUSTOM): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CustomTrigger), - } - ), - cv.Optional(CONF_ON_CALIBRATION): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CalibrationTrigger), - } - ), - cv.Optional(CONF_ON_SLOPE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SlopeTrigger), - } - ), - cv.Optional(CONF_ON_T): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TTrigger), - } - ), - cv.Optional(CONF_ON_DEVICE_INFORMATION): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - DeviceInformationTrigger - ), - } - ), - cv.Optional(CONF_ON_LED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LedTrigger), - } - ), + cv.Optional(CONF_ON_CUSTOM): automation.validate_automation({}), + cv.Optional(CONF_ON_CALIBRATION): automation.validate_automation({}), + cv.Optional(CONF_ON_SLOPE): automation.validate_automation({}), + cv.Optional(CONF_ON_T): automation.validate_automation({}), + cv.Optional(CONF_ON_DEVICE_INFORMATION): automation.validate_automation({}), + cv.Optional(CONF_ON_LED): automation.validate_automation({}), } ) .extend(cv.polling_component_schema("60s")) @@ -83,32 +38,30 @@ CONFIG_SCHEMA = ( ) +_CALLBACK_AUTOMATIONS = ( + automation.CallbackAutomation( + CONF_ON_CUSTOM, "add_custom_callback", [(cg.std_string, "x")] + ), + automation.CallbackAutomation(CONF_ON_LED, "add_led_state_callback", [(bool, "x")]), + automation.CallbackAutomation( + CONF_ON_DEVICE_INFORMATION, + "add_device_infomation_callback", + [(cg.std_string, "x")], + ), + automation.CallbackAutomation( + CONF_ON_SLOPE, "add_slope_callback", [(cg.std_string, "x")] + ), + automation.CallbackAutomation( + CONF_ON_CALIBRATION, "add_calibration_callback", [(cg.std_string, "x")] + ), + automation.CallbackAutomation(CONF_ON_T, "add_t_callback", [(cg.std_string, "x")]), +) + + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await sensor.register_sensor(var, config) await i2c.register_i2c_device(var, config) - for conf in config.get(CONF_ON_CUSTOM, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.std_string, "x")], conf) - - for conf in config.get(CONF_ON_LED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(bool, "x")], conf) - - for conf in config.get(CONF_ON_DEVICE_INFORMATION, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.std_string, "x")], conf) - - for conf in config.get(CONF_ON_SLOPE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.std_string, "x")], conf) - - for conf in config.get(CONF_ON_CALIBRATION, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.std_string, "x")], conf) - - for conf in config.get(CONF_ON_T, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.std_string, "x")], conf) + await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS) diff --git a/esphome/components/ezo_pmp/__init__.py b/esphome/components/ezo_pmp/__init__.py index 1538e303f1..0793495e1a 100644 --- a/esphome/components/ezo_pmp/__init__.py +++ b/esphome/components/ezo_pmp/__init__.py @@ -202,7 +202,7 @@ async def ezo_pmp_dose_volume_over_time_to_code(config, action_id, template_arg, template_ = await cg.templatable(config[CONF_VOLUME], args, cg.double) cg.add(var.set_volume(template_)) - template_ = await cg.templatable(config[CONF_DURATION], args, int) + template_ = await cg.templatable(config[CONF_DURATION], args, cg.int_) cg.add(var.set_duration(template_)) return var @@ -236,7 +236,7 @@ async def ezo_pmp_dose_with_constant_flow_rate_to_code( template_ = await cg.templatable(config[CONF_VOLUME_PER_MINUTE], args, cg.double) cg.add(var.set_volume(template_)) - template_ = await cg.templatable(config[CONF_DURATION], args, int) + template_ = await cg.templatable(config[CONF_DURATION], args, cg.int_) cg.add(var.set_duration(template_)) return var @@ -286,7 +286,7 @@ async def ezo_pmp_change_i2c_address_to_code(config, action_id, template_arg, ar paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) - template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.double) + template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.int_) cg.add(var.set_address(template_)) return var diff --git a/esphome/components/factory_reset/__init__.py b/esphome/components/factory_reset/__init__.py index 5784d09ce6..818a53c0ed 100644 --- a/esphome/components/factory_reset/__init__.py +++ b/esphome/components/factory_reset/__init__.py @@ -1,10 +1,9 @@ -from esphome.automation import Trigger, build_automation, validate_automation +from esphome import automation import esphome.codegen as cg from esphome.components.esp8266 import CONF_RESTORE_FROM_FLASH, KEY_ESP8266 import esphome.config_validation as cv from esphome.const import ( CONF_ID, - CONF_TRIGGER_ID, PLATFORM_BK72XX, PLATFORM_ESP32, PLATFORM_ESP8266, @@ -18,7 +17,6 @@ CODEOWNERS = ["@anatoly-savchenkov"] factory_reset_ns = cg.esphome_ns.namespace("factory_reset") FactoryResetComponent = factory_reset_ns.class_("FactoryResetComponent", cg.Component) -FastBootTrigger = factory_reset_ns.class_("FastBootTrigger", Trigger, cg.Component) CONF_MAX_DELAY = "max_delay" CONF_RESETS_REQUIRED = "resets_required" @@ -55,11 +53,7 @@ CONFIG_SCHEMA = cv.All( ), ), cv.Optional(CONF_RESETS_REQUIRED): cv.positive_not_null_int, - cv.Optional(CONF_ON_INCREMENT): validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FastBootTrigger), - } - ), + cv.Optional(CONF_ON_INCREMENT): automation.validate_automation({}), } ).extend(cv.COMPONENT_SCHEMA), _validate, @@ -79,6 +73,15 @@ def _final_validate(config): FINAL_VALIDATE_SCHEMA = _final_validate +_CALLBACK_AUTOMATIONS = ( + automation.CallbackAutomation( + CONF_ON_INCREMENT, + "add_increment_callback", + [(cg.uint8, "x"), (cg.uint8, "target")], + ), +) + + async def to_code(config): if reset_count := config.get(CONF_RESETS_REQUIRED): var = cg.new_Pvariable( @@ -87,13 +90,4 @@ async def to_code(config): config[CONF_MAX_DELAY].total_seconds, ) await cg.register_component(var, config) - for conf in config.get(CONF_ON_INCREMENT, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await build_automation( - trigger, - [ - (cg.uint8, "x"), - (cg.uint8, "target"), - ], - conf, - ) + await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS) diff --git a/esphome/components/factory_reset/factory_reset.h b/esphome/components/factory_reset/factory_reset.h index 990bb2edb6..41ee627c4b 100644 --- a/esphome/components/factory_reset/factory_reset.h +++ b/esphome/components/factory_reset/factory_reset.h @@ -17,8 +17,8 @@ class FactoryResetComponent : public Component { void dump_config() override; void setup() override; - void add_increment_callback(std::function &&callback) { - this->increment_callback_.add(std::move(callback)); + template void add_increment_callback(F &&callback) { + this->increment_callback_.add(std::forward(callback)); } protected: @@ -30,12 +30,6 @@ class FactoryResetComponent : public Component { uint8_t required_count_; // The number of boot attempts before fast boot is enabled }; -class FastBootTrigger : public Trigger { - public: - explicit FastBootTrigger(FactoryResetComponent *parent) { - parent->add_increment_callback([this](uint8_t current, uint8_t target) { this->trigger(current, target); }); - } -}; } // namespace esphome::factory_reset #endif // !defined(USE_RP2040) && !defined(USE_HOST) diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index df71c6ab3f..ce1e55d36b 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -345,10 +345,10 @@ async def fan_turn_on_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) if (oscillating := config.get(CONF_OSCILLATING)) is not None: - template_ = await cg.templatable(oscillating, args, bool) + template_ = await cg.templatable(oscillating, args, cg.bool_) cg.add(var.set_oscillating(template_)) if (speed := config.get(CONF_SPEED)) is not None: - template_ = await cg.templatable(speed, args, int) + template_ = await cg.templatable(speed, args, cg.int_) cg.add(var.set_speed(template_)) if (direction := config.get(CONF_DIRECTION)) is not None: template_ = await cg.templatable(direction, args, FanDirection) @@ -370,7 +370,7 @@ async def fan_turn_on_to_code(config, action_id, template_arg, args): async def fan_cycle_speed_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) - template_ = await cg.templatable(config[CONF_OFF_SPEED_CYCLE], args, bool) + template_ = await cg.templatable(config[CONF_OFF_SPEED_CYCLE], args, cg.bool_) cg.add(var.set_no_off_cycle(template_)) return var diff --git a/esphome/components/fan/automation.h b/esphome/components/fan/automation.h index 3c3b0ce519..3ee6f89e55 100644 --- a/esphome/components/fan/automation.h +++ b/esphome/components/fan/automation.h @@ -113,16 +113,19 @@ template class FanIsOffCondition : public Condition { class FanStateTrigger : public Trigger { public: - FanStateTrigger(Fan *state) { - state->add_on_state_callback([this, state]() { this->trigger(state); }); + FanStateTrigger(Fan *state) : fan_(state) { + state->add_on_state_callback([this]() { this->trigger(this->fan_); }); } + + protected: + Fan *fan_; }; class FanTurnOnTrigger : public Trigger<> { public: - FanTurnOnTrigger(Fan *state) { - state->add_on_state_callback([this, state]() { - auto is_on = state->state; + FanTurnOnTrigger(Fan *state) : fan_(state) { + state->add_on_state_callback([this]() { + auto is_on = this->fan_->state; auto should_trigger = is_on && !this->last_on_; this->last_on_ = is_on; if (should_trigger) { @@ -133,14 +136,15 @@ class FanTurnOnTrigger : public Trigger<> { } protected: + Fan *fan_; bool last_on_; }; class FanTurnOffTrigger : public Trigger<> { public: - FanTurnOffTrigger(Fan *state) { - state->add_on_state_callback([this, state]() { - auto is_on = state->state; + FanTurnOffTrigger(Fan *state) : fan_(state) { + state->add_on_state_callback([this]() { + auto is_on = this->fan_->state; auto should_trigger = !is_on && this->last_on_; this->last_on_ = is_on; if (should_trigger) { @@ -151,14 +155,15 @@ class FanTurnOffTrigger : public Trigger<> { } protected: + Fan *fan_; bool last_on_; }; class FanDirectionSetTrigger : public Trigger { public: - FanDirectionSetTrigger(Fan *state) { - state->add_on_state_callback([this, state]() { - auto direction = state->direction; + FanDirectionSetTrigger(Fan *state) : fan_(state) { + state->add_on_state_callback([this]() { + auto direction = this->fan_->direction; auto should_trigger = direction != this->last_direction_; this->last_direction_ = direction; if (should_trigger) { @@ -169,14 +174,15 @@ class FanDirectionSetTrigger : public Trigger { } protected: + Fan *fan_; FanDirection last_direction_; }; class FanOscillatingSetTrigger : public Trigger { public: - FanOscillatingSetTrigger(Fan *state) { - state->add_on_state_callback([this, state]() { - auto oscillating = state->oscillating; + FanOscillatingSetTrigger(Fan *state) : fan_(state) { + state->add_on_state_callback([this]() { + auto oscillating = this->fan_->oscillating; auto should_trigger = oscillating != this->last_oscillating_; this->last_oscillating_ = oscillating; if (should_trigger) { @@ -187,14 +193,15 @@ class FanOscillatingSetTrigger : public Trigger { } protected: + Fan *fan_; bool last_oscillating_; }; class FanSpeedSetTrigger : public Trigger { public: - FanSpeedSetTrigger(Fan *state) { - state->add_on_state_callback([this, state]() { - auto speed = state->speed; + FanSpeedSetTrigger(Fan *state) : fan_(state) { + state->add_on_state_callback([this]() { + auto speed = this->fan_->speed; auto should_trigger = speed != this->last_speed_; this->last_speed_ = speed; if (should_trigger) { @@ -205,14 +212,15 @@ class FanSpeedSetTrigger : public Trigger { } protected: + Fan *fan_; int last_speed_; }; class FanPresetSetTrigger : public Trigger { public: - FanPresetSetTrigger(Fan *state) { - state->add_on_state_callback([this, state]() { - auto preset_mode = state->get_preset_mode(); + FanPresetSetTrigger(Fan *state) : fan_(state) { + state->add_on_state_callback([this]() { + auto preset_mode = this->fan_->get_preset_mode(); auto should_trigger = preset_mode != this->last_preset_mode_; this->last_preset_mode_ = preset_mode; if (should_trigger) { @@ -223,6 +231,7 @@ class FanPresetSetTrigger : public Trigger { } protected: + Fan *fan_; StringRef last_preset_mode_{}; }; diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index c1e0a3dc2e..9301e0cea4 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -9,6 +9,22 @@ namespace fan { static const char *const TAG = "fan"; +// Compat: shared empty vector for getter when no preset modes are set. +// Remove in 2026.11.0 when deprecated FanTraits setters are removed +// and getter can return const vector * instead of const vector &. +static const std::vector EMPTY_PRESET_MODES; // NOLINT + +const std::vector &FanTraits::supported_preset_modes() const { + if (this->preset_modes_) { + return *this->preset_modes_; + } + // Compat: fall back to owned vector from deprecated setters. Remove in 2026.11.0 (change return to const vector *). + if (!this->compat_preset_modes_.empty()) { + return this->compat_preset_modes_; + } + return EMPTY_PRESET_MODES; +} + // Fan direction strings indexed by FanDirection enum (0-1): FORWARD, REVERSE, plus UNKNOWN PROGMEM_STRING_TABLE(FanDirectionStrings, "FORWARD", "REVERSE", "UNKNOWN"); @@ -44,22 +60,22 @@ FanCall &FanCall::set_preset_mode(const char *preset_mode, size_t len) { } void FanCall::perform() { - ESP_LOGD(TAG, "'%s' - Setting:", this->parent_.get_name().c_str()); + ESP_LOGV(TAG, "'%s' - Setting:", this->parent_.get_name().c_str()); this->validate_(); if (this->binary_state_.has_value()) { - ESP_LOGD(TAG, " State: %s", ONOFF(*this->binary_state_)); + ESP_LOGV(TAG, " State: %s", ONOFF(*this->binary_state_)); } if (this->oscillating_.has_value()) { - ESP_LOGD(TAG, " Oscillating: %s", YESNO(*this->oscillating_)); + ESP_LOGV(TAG, " Oscillating: %s", YESNO(*this->oscillating_)); } if (this->speed_.has_value()) { - ESP_LOGD(TAG, " Speed: %d", *this->speed_); + ESP_LOGV(TAG, " Speed: %d", *this->speed_); } if (this->direction_.has_value()) { - ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(*this->direction_))); + ESP_LOGV(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(*this->direction_))); } if (this->preset_mode_ != nullptr) { - ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode_); + ESP_LOGV(TAG, " Preset Mode: %s", this->preset_mode_); } this->parent_.control(*this); } @@ -148,6 +164,18 @@ const char *Fan::find_preset_mode_(const char *preset_mode) { } const char *Fan::find_preset_mode_(const char *preset_mode, size_t len) { + if (preset_mode == nullptr || len == 0) { + return nullptr; + } + if (this->supported_preset_modes_) { + for (const char *mode : *this->supported_preset_modes_) { + if (strncmp(mode, preset_mode, len) == 0 && mode[len] == '\0') { + return mode; + } + } + return nullptr; + } + // Fallback for deprecated path: external components may set modes on FanTraits directly return this->get_traits().find_preset_mode(preset_mode, len); } @@ -193,25 +221,24 @@ void Fan::apply_preset_mode_(const FanCall &call) { } } -void Fan::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } void Fan::publish_state() { auto traits = this->get_traits(); - ESP_LOGD(TAG, + ESP_LOGV(TAG, "'%s' >>\n" " State: %s", this->name_.c_str(), ONOFF(this->state)); if (traits.supports_speed()) { - ESP_LOGD(TAG, " Speed: %d", this->speed); + ESP_LOGV(TAG, " Speed: %d", this->speed); } if (traits.supports_oscillation()) { - ESP_LOGD(TAG, " Oscillating: %s", YESNO(this->oscillating)); + ESP_LOGV(TAG, " Oscillating: %s", YESNO(this->oscillating)); } if (traits.supports_direction()) { - ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(this->direction))); + ESP_LOGV(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(this->direction))); } if (this->preset_mode_ != nullptr) { - ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode_); + ESP_LOGV(TAG, " Preset Mode: %s", this->preset_mode_); } this->state_callback_.call(); #if defined(USE_FAN) && defined(USE_CONTROLLER_REGISTRY) @@ -262,8 +289,6 @@ void Fan::save_state_() { return; } - auto traits = this->get_traits(); - FanRestoreState state{}; state.state = this->state; state.oscillating = this->oscillating; @@ -272,12 +297,25 @@ void Fan::save_state_() { state.preset_mode = FanRestoreState::NO_PRESET; if (this->has_preset_mode()) { - const auto &preset_modes = traits.supported_preset_modes(); - // Find index of current preset mode (pointer comparison is safe since preset is from traits) - for (size_t i = 0; i < preset_modes.size(); i++) { - if (preset_modes[i] == this->preset_mode_) { - state.preset_mode = i; - break; + if (this->supported_preset_modes_) { + // New path: search Fan-owned vector directly + for (size_t i = 0; i < this->supported_preset_modes_->size(); i++) { + if ((*this->supported_preset_modes_)[i] == this->preset_mode_) { + state.preset_mode = i; + break; + } + } + } else { + // Compat: fall back to traits for deprecated path. Remove in 2026.11.0. + // Pointer comparison works because preset_mode_ and the compat vector both + // hold pointers to string literals in .rodata (stable addresses). + auto traits = this->get_traits(); + const auto &preset_modes = traits.supported_preset_modes(); + for (size_t i = 0; i < preset_modes.size(); i++) { + if (preset_modes[i] == this->preset_mode_) { + state.preset_mode = i; + break; + } } } } diff --git a/esphome/components/fan/fan.h b/esphome/components/fan/fan.h index 2caf3a712a..d5763edf2f 100644 --- a/esphome/components/fan/fan.h +++ b/esphome/components/fan/fan.h @@ -122,12 +122,22 @@ class Fan : public EntityBase { FanCall make_call(); /// Register a callback that will be called each time the state changes. - void add_on_state_callback(std::function &&callback); + template void add_on_state_callback(F &&callback) { + this->state_callback_.add(std::forward(callback)); + } void publish_state(); virtual FanTraits get_traits() = 0; + /// Set the supported preset modes (stored on Fan, referenced by FanTraits via pointer). + void set_supported_preset_modes(std::initializer_list preset_modes) { + this->ensure_preset_modes_().assign(preset_modes.begin(), preset_modes.end()); + } + void set_supported_preset_modes(const std::vector &preset_modes) { + this->ensure_preset_modes_() = preset_modes; + } + /// Set the restore mode of this fan. void set_restore_mode(FanRestoreMode restore_mode) { this->restore_mode_ = restore_mode; } @@ -165,11 +175,27 @@ class Fan : public EntityBase { const char *find_preset_mode_(const char *preset_mode); const char *find_preset_mode_(const char *preset_mode, size_t len); + /// Wire the Fan-owned preset modes pointer into the given traits object. + void wire_preset_modes_(FanTraits &traits) { + if (this->supported_preset_modes_) { + traits.set_supported_preset_modes_(this->supported_preset_modes_); + } + } + LazyCallbackManager state_callback_{}; ESPPreferenceObject rtc_; FanRestoreMode restore_mode_; private: + /// Lazy-allocate preset modes vector (never freed — entity lives forever). + std::vector &ensure_preset_modes_() { + if (!this->supported_preset_modes_) { + this->supported_preset_modes_ = new std::vector(); // NOLINT + } + return *this->supported_preset_modes_; + } + + std::vector *supported_preset_modes_{nullptr}; const char *preset_mode_{nullptr}; }; diff --git a/esphome/components/fan/fan_traits.h b/esphome/components/fan/fan_traits.h index c0c5f34c50..a2b2633af1 100644 --- a/esphome/components/fan/fan_traits.h +++ b/esphome/components/fan/fan_traits.h @@ -3,12 +3,17 @@ #include #include #include +#include "esphome/core/helpers.h" namespace esphome { namespace fan { +class Fan; // Forward declaration + class FanTraits { + friend class Fan; // Allow Fan to access protected pointer setter + public: FanTraits() = default; FanTraits(bool oscillation, bool speed, bool direction, int speed_count) @@ -30,42 +35,64 @@ class FanTraits { bool supports_direction() const { return this->direction_; } /// Set whether this fan supports changing direction void set_direction(bool direction) { this->direction_ = direction; } - /// Return the preset modes supported by the fan. - const std::vector &supported_preset_modes() const { return this->preset_modes_; } - /// Set the preset modes supported by the fan (from initializer list). + // Compat: returns const ref with empty fallback. In 2026.11.0 change to return const vector *. + const std::vector &supported_preset_modes() const; + // Remove before 2026.11.0 + ESPDEPRECATED("Call set_supported_preset_modes() on the Fan entity instead. Removed in 2026.11.0", "2026.5.0") void set_supported_preset_modes(std::initializer_list preset_modes) { - this->preset_modes_ = preset_modes; + // Compat: store in owned vector. Copies copy the vector (deprecated path still copies this vector). + this->compat_preset_modes_ = preset_modes; + } + // Remove before 2026.11.0 + ESPDEPRECATED("Call set_supported_preset_modes() on the Fan entity instead. Removed in 2026.11.0", "2026.5.0") + void set_supported_preset_modes(const std::vector &preset_modes) { + this->compat_preset_modes_ = preset_modes; } - /// Set the preset modes supported by the fan (from vector). - void set_supported_preset_modes(const std::vector &preset_modes) { this->preset_modes_ = preset_modes; } // Deleted overloads to catch incorrect std::string usage at compile time with clear error messages void set_supported_preset_modes(const std::vector &preset_modes) = delete; void set_supported_preset_modes(std::initializer_list preset_modes) = delete; /// Return if preset modes are supported - bool supports_preset_modes() const { return !this->preset_modes_.empty(); } + bool supports_preset_modes() const { + // Same precedence as supported_preset_modes() getter + if (this->preset_modes_) { + return !this->preset_modes_->empty(); + } + return !this->compat_preset_modes_.empty(); + } /// Find and return the matching preset mode pointer from supported modes, or nullptr if not found. const char *find_preset_mode(const char *preset_mode) const { return this->find_preset_mode(preset_mode, preset_mode ? strlen(preset_mode) : 0); } const char *find_preset_mode(const char *preset_mode, size_t len) const { - if (preset_mode == nullptr || len == 0) + if (preset_mode == nullptr || len == 0) { return nullptr; - for (const char *mode : this->preset_modes_) { + } + // Check pointer-based storage (new path) then compat owned vector (deprecated path) + const auto &modes = this->preset_modes_ ? *this->preset_modes_ : this->compat_preset_modes_; + for (const char *mode : modes) { if (strncmp(mode, preset_mode, len) == 0 && mode[len] == '\0') { - return mode; // Return pointer from traits + return mode; } } return nullptr; } protected: + /// Set the preset modes pointer (only Fan::wire_preset_modes_() should call this). + void set_supported_preset_modes_(const std::vector *preset_modes) { + this->preset_modes_ = preset_modes; + } + bool oscillation_{false}; bool speed_{false}; bool direction_{false}; int speed_count_{}; - std::vector preset_modes_{}; + const std::vector *preset_modes_{nullptr}; + // Compat: owned storage for deprecated setters. Copies copy the vector (copies include this vector). + // Remove in 2026.11.0. + std::vector compat_preset_modes_; }; } // namespace fan diff --git a/esphome/components/fingerprint_grow/__init__.py b/esphome/components/fingerprint_grow/__init__.py index 2637097be8..8d935a3c9e 100644 --- a/esphome/components/fingerprint_grow/__init__.py +++ b/esphome/components/fingerprint_grow/__init__.py @@ -21,7 +21,6 @@ from esphome.const import ( CONF_SENSING_PIN, CONF_SPEED, CONF_STATE, - CONF_TRIGGER_ID, ) CODEOWNERS = ["@OnFreund", "@loongyh", "@alexborro"] @@ -38,38 +37,6 @@ FingerprintGrowComponent = fingerprint_grow_ns.class_( "FingerprintGrowComponent", cg.PollingComponent, uart.UARTDevice ) -FingerScanStartTrigger = fingerprint_grow_ns.class_( - "FingerScanStartTrigger", automation.Trigger.template() -) - -FingerScanMatchedTrigger = fingerprint_grow_ns.class_( - "FingerScanMatchedTrigger", automation.Trigger.template(cg.uint16, cg.uint16) -) - -FingerScanUnmatchedTrigger = fingerprint_grow_ns.class_( - "FingerScanUnmatchedTrigger", automation.Trigger.template() -) - -FingerScanMisplacedTrigger = fingerprint_grow_ns.class_( - "FingerScanMisplacedTrigger", automation.Trigger.template() -) - -FingerScanInvalidTrigger = fingerprint_grow_ns.class_( - "FingerScanInvalidTrigger", automation.Trigger.template() -) - -EnrollmentScanTrigger = fingerprint_grow_ns.class_( - "EnrollmentScanTrigger", automation.Trigger.template(cg.uint8, cg.uint16) -) - -EnrollmentDoneTrigger = fingerprint_grow_ns.class_( - "EnrollmentDoneTrigger", automation.Trigger.template(cg.uint16) -) - -EnrollmentFailedTrigger = fingerprint_grow_ns.class_( - "EnrollmentFailedTrigger", automation.Trigger.template(cg.uint16) -) - EnrollmentAction = fingerprint_grow_ns.class_("EnrollmentAction", automation.Action) CancelEnrollmentAction = fingerprint_grow_ns.class_( "CancelEnrollmentAction", automation.Action @@ -125,62 +92,22 @@ CONFIG_SCHEMA = cv.All( ): cv.positive_time_period_milliseconds, cv.Optional(CONF_PASSWORD): cv.uint32_t, cv.Optional(CONF_NEW_PASSWORD): cv.uint32_t, - cv.Optional(CONF_ON_FINGER_SCAN_START): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - FingerScanStartTrigger - ), - } - ), + cv.Optional(CONF_ON_FINGER_SCAN_START): automation.validate_automation({}), cv.Optional(CONF_ON_FINGER_SCAN_MATCHED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - FingerScanMatchedTrigger - ), - } + {} ), cv.Optional(CONF_ON_FINGER_SCAN_UNMATCHED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - FingerScanUnmatchedTrigger - ), - } + {} ), cv.Optional(CONF_ON_FINGER_SCAN_MISPLACED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - FingerScanMisplacedTrigger - ), - } + {} ), cv.Optional(CONF_ON_FINGER_SCAN_INVALID): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - FingerScanInvalidTrigger - ), - } - ), - cv.Optional(CONF_ON_ENROLLMENT_SCAN): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - EnrollmentScanTrigger - ), - } - ), - cv.Optional(CONF_ON_ENROLLMENT_DONE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - EnrollmentDoneTrigger - ), - } - ), - cv.Optional(CONF_ON_ENROLLMENT_FAILED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - EnrollmentFailedTrigger - ), - } + {} ), + cv.Optional(CONF_ON_ENROLLMENT_SCAN): automation.validate_automation({}), + cv.Optional(CONF_ON_ENROLLMENT_DONE): automation.validate_automation({}), + cv.Optional(CONF_ON_ENROLLMENT_FAILED): automation.validate_automation({}), } ) .extend(cv.polling_component_schema("500ms")) @@ -189,6 +116,44 @@ CONFIG_SCHEMA = cv.All( ) +_CALLBACK_AUTOMATIONS = ( + automation.CallbackAutomation( + CONF_ON_FINGER_SCAN_START, "add_on_finger_scan_start_callback" + ), + automation.CallbackAutomation( + CONF_ON_FINGER_SCAN_MATCHED, + "add_on_finger_scan_matched_callback", + [(cg.uint16, "finger_id"), (cg.uint16, "confidence")], + ), + automation.CallbackAutomation( + CONF_ON_FINGER_SCAN_UNMATCHED, + "add_on_finger_scan_unmatched_callback", + ), + automation.CallbackAutomation( + CONF_ON_FINGER_SCAN_MISPLACED, + "add_on_finger_scan_misplaced_callback", + ), + automation.CallbackAutomation( + CONF_ON_FINGER_SCAN_INVALID, "add_on_finger_scan_invalid_callback" + ), + automation.CallbackAutomation( + CONF_ON_ENROLLMENT_SCAN, + "add_on_enrollment_scan_callback", + [(cg.uint8, "scan_num"), (cg.uint16, "finger_id")], + ), + automation.CallbackAutomation( + CONF_ON_ENROLLMENT_DONE, + "add_on_enrollment_done_callback", + [(cg.uint16, "finger_id")], + ), + automation.CallbackAutomation( + CONF_ON_ENROLLMENT_FAILED, + "add_on_enrollment_failed_callback", + [(cg.uint16, "finger_id")], + ), +) + + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) @@ -213,41 +178,7 @@ async def to_code(config): idle_period_to_sleep_ms = config[CONF_IDLE_PERIOD_TO_SLEEP] cg.add(var.set_idle_period_to_sleep_ms(idle_period_to_sleep_ms)) - for conf in config.get(CONF_ON_FINGER_SCAN_START, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - - for conf in config.get(CONF_ON_FINGER_SCAN_MATCHED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(cg.uint16, "finger_id"), (cg.uint16, "confidence")], conf - ) - - for conf in config.get(CONF_ON_FINGER_SCAN_UNMATCHED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - - for conf in config.get(CONF_ON_FINGER_SCAN_MISPLACED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - - for conf in config.get(CONF_ON_FINGER_SCAN_INVALID, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - - for conf in config.get(CONF_ON_ENROLLMENT_SCAN, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(cg.uint8, "scan_num"), (cg.uint16, "finger_id")], conf - ) - - for conf in config.get(CONF_ON_ENROLLMENT_DONE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.uint16, "finger_id")], conf) - - for conf in config.get(CONF_ON_ENROLLMENT_FAILED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.uint16, "finger_id")], conf) + await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS) @automation.register_action( diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.h b/esphome/components/fingerprint_grow/fingerprint_grow.h index db9d5ce564..947c701c98 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.h +++ b/esphome/components/fingerprint_grow/fingerprint_grow.h @@ -127,30 +127,30 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic void set_enrolling_binary_sensor(binary_sensor::BinarySensor *enrolling_binary_sensor) { this->enrolling_binary_sensor_ = enrolling_binary_sensor; } - void add_on_finger_scan_start_callback(std::function callback) { - this->finger_scan_start_callback_.add(std::move(callback)); + template void add_on_finger_scan_start_callback(F &&callback) { + this->finger_scan_start_callback_.add(std::forward(callback)); } - void add_on_finger_scan_matched_callback(std::function callback) { - this->finger_scan_matched_callback_.add(std::move(callback)); + template void add_on_finger_scan_matched_callback(F &&callback) { + this->finger_scan_matched_callback_.add(std::forward(callback)); } - void add_on_finger_scan_unmatched_callback(std::function callback) { - this->finger_scan_unmatched_callback_.add(std::move(callback)); + template void add_on_finger_scan_unmatched_callback(F &&callback) { + this->finger_scan_unmatched_callback_.add(std::forward(callback)); } - void add_on_finger_scan_misplaced_callback(std::function callback) { - this->finger_scan_misplaced_callback_.add(std::move(callback)); + template void add_on_finger_scan_misplaced_callback(F &&callback) { + this->finger_scan_misplaced_callback_.add(std::forward(callback)); } - void add_on_finger_scan_invalid_callback(std::function callback) { - this->finger_scan_invalid_callback_.add(std::move(callback)); + template void add_on_finger_scan_invalid_callback(F &&callback) { + this->finger_scan_invalid_callback_.add(std::forward(callback)); } - void add_on_enrollment_scan_callback(std::function callback) { - this->enrollment_scan_callback_.add(std::move(callback)); + template void add_on_enrollment_scan_callback(F &&callback) { + this->enrollment_scan_callback_.add(std::forward(callback)); } - void add_on_enrollment_done_callback(std::function callback) { - this->enrollment_done_callback_.add(std::move(callback)); + template void add_on_enrollment_done_callback(F &&callback) { + this->enrollment_done_callback_.add(std::forward(callback)); } - void add_on_enrollment_failed_callback(std::function callback) { - this->enrollment_failed_callback_.add(std::move(callback)); + template void add_on_enrollment_failed_callback(F &&callback) { + this->enrollment_failed_callback_.add(std::forward(callback)); } void enroll_fingerprint(uint16_t finger_id, uint8_t num_buffers); @@ -210,64 +210,6 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic CallbackManager enrollment_failed_callback_; }; -class FingerScanStartTrigger : public Trigger<> { - public: - explicit FingerScanStartTrigger(FingerprintGrowComponent *parent) { - parent->add_on_finger_scan_start_callback([this]() { this->trigger(); }); - } -}; - -class FingerScanMatchedTrigger : public Trigger { - public: - explicit FingerScanMatchedTrigger(FingerprintGrowComponent *parent) { - parent->add_on_finger_scan_matched_callback( - [this](uint16_t finger_id, uint16_t confidence) { this->trigger(finger_id, confidence); }); - } -}; - -class FingerScanUnmatchedTrigger : public Trigger<> { - public: - explicit FingerScanUnmatchedTrigger(FingerprintGrowComponent *parent) { - parent->add_on_finger_scan_unmatched_callback([this]() { this->trigger(); }); - } -}; - -class FingerScanMisplacedTrigger : public Trigger<> { - public: - explicit FingerScanMisplacedTrigger(FingerprintGrowComponent *parent) { - parent->add_on_finger_scan_misplaced_callback([this]() { this->trigger(); }); - } -}; - -class FingerScanInvalidTrigger : public Trigger<> { - public: - explicit FingerScanInvalidTrigger(FingerprintGrowComponent *parent) { - parent->add_on_finger_scan_invalid_callback([this]() { this->trigger(); }); - } -}; - -class EnrollmentScanTrigger : public Trigger { - public: - explicit EnrollmentScanTrigger(FingerprintGrowComponent *parent) { - parent->add_on_enrollment_scan_callback( - [this](uint8_t scan_num, uint16_t finger_id) { this->trigger(scan_num, finger_id); }); - } -}; - -class EnrollmentDoneTrigger : public Trigger { - public: - explicit EnrollmentDoneTrigger(FingerprintGrowComponent *parent) { - parent->add_on_enrollment_done_callback([this](uint16_t finger_id) { this->trigger(finger_id); }); - } -}; - -class EnrollmentFailedTrigger : public Trigger { - public: - explicit EnrollmentFailedTrigger(FingerprintGrowComponent *parent) { - parent->add_on_enrollment_failed_callback([this](uint16_t finger_id) { this->trigger(finger_id); }); - } -}; - template class EnrollmentAction : public Action, public Parented { public: TEMPLATABLE_VALUE(uint16_t, finger_id) diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index 2667dbdbdf..a1339a4bc1 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -238,7 +238,7 @@ def validate_font_config(config): return config -FONT_EXTENSIONS = (".ttf", ".woff", ".otf", "bdf", ".pcf") +FONT_EXTENSIONS = (".ttf", ".woff", ".otf", ".bdf", ".pcf") def validate_truetype_file(value): @@ -552,6 +552,7 @@ async def to_code(config): """ # get the codepoints from glyphsets and flatten to a set of chrs. + cg.add_define("USE_FONT") point_set: set[str] = { chr(x) for x in flatten( diff --git a/esphome/components/font/font.cpp b/esphome/components/font/font.cpp index 5e3bf1dd20..ecf0ca6bdd 100644 --- a/esphome/components/font/font.cpp +++ b/esphome/components/font/font.cpp @@ -9,13 +9,87 @@ namespace font { static const char *const TAG = "font"; #ifdef USE_LVGL_FONT -const uint8_t *Font::get_glyph_bitmap(const lv_font_t *font, uint32_t unicode_letter) { - auto *fe = (Font *) font->dsc; - const auto *gd = fe->get_glyph_data_(unicode_letter); +static const uint8_t OPA4_TABLE[16] = {0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255}; + +static const uint8_t OPA2_TABLE[4] = {0, 85, 170, 255}; + +const void *Font::get_glyph_bitmap(lv_font_glyph_dsc_t *dsc, lv_draw_buf_t *draw_buf) { + const auto *font = dsc->resolved_font; + auto *const fe = (Font *) font->dsc; + + const auto *gd = fe->get_glyph_data_(dsc->gid.index); if (gd == nullptr) { return nullptr; } - return gd->data; + + const uint8_t *bitmap_in = gd->data; + uint8_t *bitmap_out_tmp = draw_buf->data; + int32_t i = 0; + int32_t x, y; + uint32_t stride = lv_draw_buf_width_to_stride(gd->width, LV_COLOR_FORMAT_A8); + + switch (fe->get_bpp()) { + case 1: { + uint8_t mask = 0; + uint8_t byte = 0; + for (y = 0; y != gd->height; y++) { + for (x = 0; x != gd->width; x++) { + if (mask == 0) { + mask = 0x80; + byte = *bitmap_in++; + } + bitmap_out_tmp[x] = byte & mask ? 255 : 0; + mask >>= 1; + } + bitmap_out_tmp += stride; + } + } break; + + case 2: + for (y = 0; y != gd->height; y++) { + for (x = 0; x != gd->width; x++, i++) { + switch (i & 0x3) { + default: + bitmap_out_tmp[x] = OPA2_TABLE[(*bitmap_in) >> 6]; + break; + case 1: + bitmap_out_tmp[x] = OPA2_TABLE[((*bitmap_in) >> 4) & 0x3]; + break; + case 2: + bitmap_out_tmp[x] = OPA2_TABLE[((*bitmap_in) >> 2) & 0x3]; + break; + case 3: + bitmap_out_tmp[x] = OPA2_TABLE[((*bitmap_in) >> 0) & 0x3]; + bitmap_in++; + } + } + bitmap_out_tmp += stride; + } + break; + + case 4: + for (y = 0; y != gd->height; y++) { + for (x = 0; x != gd->width; x++, i++) { + i = i & 0x1; + if (i == 0) { + bitmap_out_tmp[x] = OPA4_TABLE[(*bitmap_in) >> 4]; + } else if (i == 1) { + bitmap_out_tmp[x] = OPA4_TABLE[(*bitmap_in) & 0xF]; + bitmap_in++; + } + } + bitmap_out_tmp += stride; + } + break; + + case 8: + memcpy(bitmap_out_tmp, bitmap_in, gd->width * gd->height); + break; + default: + ESP_LOGD(TAG, "Unknown bpp: %d", fe->get_bpp()); + break; + } + return draw_buf; } bool Font::get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uint32_t unicode_letter, uint32_t next) { @@ -30,7 +104,8 @@ bool Font::get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uin dsc->box_w = gd->width; dsc->box_h = gd->height; dsc->is_placeholder = 0; - dsc->bpp = fe->get_bpp(); + dsc->format = (lv_font_glyph_format_t) fe->get_bpp(); + dsc->gid.index = unicode_letter; return true; } diff --git a/esphome/components/font/font.h b/esphome/components/font/font.h index 262ded3be4..4a09d7314d 100644 --- a/esphome/components/font/font.h +++ b/esphome/components/font/font.h @@ -90,7 +90,7 @@ class Font uint8_t bpp_; // bits per pixel #ifdef USE_LVGL_FONT lv_font_t lv_font_{}; - static const uint8_t *get_glyph_bitmap(const lv_font_t *font, uint32_t unicode_letter); + static const void *get_glyph_bitmap(lv_font_glyph_dsc_t *dsc, lv_draw_buf_t *draw_buf); static bool get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uint32_t unicode_letter, uint32_t next); const Glyph *get_glyph_data_(uint32_t unicode_letter); uint32_t last_letter_{}; diff --git a/esphome/components/gcja5/sensor.py b/esphome/components/gcja5/sensor.py index ec26447ccb..e4de7721c6 100644 --- a/esphome/components/gcja5/sensor.py +++ b/esphome/components/gcja5/sensor.py @@ -24,73 +24,77 @@ DEPENDENCIES = ["uart"] gcja5_ns = cg.esphome_ns.namespace("gcja5") -GCJA5Component = gcja5_ns.class_("GCJA5Component", cg.PollingComponent, uart.UARTDevice) +GCJA5Component = gcja5_ns.class_("GCJA5Component", cg.Component, uart.UARTDevice) CONF_PMC_0_3 = "pmc_0_3" CONF_PMC_5_0 = "pmc_5_0" -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(GCJA5Component), - cv.Optional(CONF_PM_1_0): sensor.sensor_schema( - unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, - icon=ICON_CHEMICAL_WEAPON, - accuracy_decimals=2, - device_class=DEVICE_CLASS_PM1, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_PM_2_5): sensor.sensor_schema( - unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, - icon=ICON_CHEMICAL_WEAPON, - accuracy_decimals=2, - device_class=DEVICE_CLASS_PM25, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_PM_10_0): sensor.sensor_schema( - unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, - icon=ICON_CHEMICAL_WEAPON, - accuracy_decimals=2, - device_class=DEVICE_CLASS_PM10, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_PMC_0_3): sensor.sensor_schema( - unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, - icon=ICON_COUNTER, - accuracy_decimals=0, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_PMC_0_5): sensor.sensor_schema( - unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, - icon=ICON_COUNTER, - accuracy_decimals=0, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_PMC_1_0): sensor.sensor_schema( - unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, - icon=ICON_COUNTER, - accuracy_decimals=0, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_PMC_2_5): sensor.sensor_schema( - unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, - icon=ICON_COUNTER, - accuracy_decimals=0, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_PMC_5_0): sensor.sensor_schema( - unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, - icon=ICON_COUNTER, - accuracy_decimals=0, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_PMC_10_0): sensor.sensor_schema( - unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, - icon=ICON_COUNTER, - accuracy_decimals=0, - state_class=STATE_CLASS_MEASUREMENT, - ), - } -).extend(uart.UART_DEVICE_SCHEMA) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(GCJA5Component), + cv.Optional(CONF_PM_1_0): sensor.sensor_schema( + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=2, + device_class=DEVICE_CLASS_PM1, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PM_2_5): sensor.sensor_schema( + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=2, + device_class=DEVICE_CLASS_PM25, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PM_10_0): sensor.sensor_schema( + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=2, + device_class=DEVICE_CLASS_PM10, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PMC_0_3): sensor.sensor_schema( + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_COUNTER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PMC_0_5): sensor.sensor_schema( + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_COUNTER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PMC_1_0): sensor.sensor_schema( + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_COUNTER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PMC_2_5): sensor.sensor_schema( + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_COUNTER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PMC_5_0): sensor.sensor_schema( + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_COUNTER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PMC_10_0): sensor.sensor_schema( + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_COUNTER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( "gcja5", baud_rate=9600, require_rx=True, parity="EVEN" ) diff --git a/esphome/components/gdk101/gdk101.cpp b/esphome/components/gdk101/gdk101.cpp index 8b381564b2..149973ba8a 100644 --- a/esphome/components/gdk101/gdk101.cpp +++ b/esphome/components/gdk101/gdk101.cpp @@ -6,9 +6,15 @@ namespace esphome { namespace gdk101 { static const char *const TAG = "gdk101"; -static const uint8_t NUMBER_OF_READ_RETRIES = 5; +static constexpr uint8_t NUMBER_OF_READ_RETRIES = 5; +static constexpr uint8_t NUMBER_OF_RESET_RETRIES = 10; +static constexpr uint32_t RESET_INTERVAL_ID = 0; +static constexpr uint32_t RESET_INTERVAL_MS = 1000; void GDK101Component::update() { + if (!this->reset_complete_) + return; + uint8_t data[2]; if (!this->read_dose_1m_(data)) { this->status_set_warning(LOG_STR("Failed to read dose 1m")); @@ -33,26 +39,45 @@ void GDK101Component::update() { } void GDK101Component::setup() { - uint8_t data[2]; - // first, reset the sensor - if (!this->reset_sensor_(data)) { - this->status_set_error(LOG_STR("Reset failed!")); - this->mark_failed(); - return; + if (!this->try_reset_()) { + // Sensor MCU boots slowly after power cycle — retry on a short interval + this->reset_retries_remaining_ = NUMBER_OF_RESET_RETRIES; + this->set_interval(RESET_INTERVAL_ID, RESET_INTERVAL_MS, [this]() { + if (this->try_reset_()) { + if (this->reset_complete_) { + this->update(); + } + return; + } + if (--this->reset_retries_remaining_ == 0) { + this->cancel_interval(RESET_INTERVAL_ID); + this->mark_failed(LOG_STR("Reset failed after retries")); + } + }); + } +} + +/// Attempt to reset the sensor and read firmware version. Returns true on success or hard failure. +bool GDK101Component::try_reset_() { + uint8_t data[2] = {0}; + if (!this->reset_sensor_(data)) { + this->status_set_warning(LOG_STR("Sensor not answering reset, will retry")); + return false; } - // sensor should acknowledge success of the reset procedure if (data[0] != 1) { - this->status_set_error(LOG_STR("Reset not acknowledged!")); - this->mark_failed(); - return; + this->status_set_warning(LOG_STR("Reset not acknowledged, will retry")); + return false; } delay(10); - // read firmware version if (!this->read_fw_version_(data)) { - this->status_set_error(LOG_STR("Failed to read firmware version")); - this->mark_failed(); - return; + this->cancel_interval(RESET_INTERVAL_ID); + this->mark_failed(LOG_STR("Failed to read firmware version")); + return true; } + this->reset_complete_ = true; + this->status_clear_warning(); + this->cancel_interval(RESET_INTERVAL_ID); + return true; } void GDK101Component::dump_config() { @@ -92,12 +117,7 @@ bool GDK101Component::reset_sensor_(uint8_t *data) { // After sending reset command it looks that sensor start performing reset and is unresponsible during read // after a while we can send another reset command and read "0x01" as confirmation // Documentation not going in to such details unfortunately - if (!this->read_bytes_with_retry_(GDK101_REG_RESET, data, 2)) { - ESP_LOGE(TAG, "Updating GDK101 failed!"); - return false; - } - - return true; + return this->read_bytes_with_retry_(GDK101_REG_RESET, data, 2); } bool GDK101Component::read_dose_1m_(uint8_t *data) { diff --git a/esphome/components/gdk101/gdk101.h b/esphome/components/gdk101/gdk101.h index abe417e0f9..abe3fd60d8 100644 --- a/esphome/components/gdk101/gdk101.h +++ b/esphome/components/gdk101/gdk101.h @@ -44,12 +44,15 @@ class GDK101Component : public PollingComponent, public i2c::I2CDevice { protected: bool read_bytes_with_retry_(uint8_t a_register, uint8_t *data, uint8_t len); + bool try_reset_(); bool reset_sensor_(uint8_t *data); bool read_dose_1m_(uint8_t *data); bool read_dose_10m_(uint8_t *data); bool read_status_(uint8_t *data); bool read_fw_version_(uint8_t *data); bool read_measurement_duration_(uint8_t *data); + bool reset_complete_{false}; + uint8_t reset_retries_remaining_{0}; }; } // namespace gdk101 diff --git a/esphome/components/gl_r01_i2c/sensor.py b/esphome/components/gl_r01_i2c/sensor.py index 58db72540e..6a8d47213c 100644 --- a/esphome/components/gl_r01_i2c/sensor.py +++ b/esphome/components/gl_r01_i2c/sensor.py @@ -13,7 +13,7 @@ DEPENDENCIES = ["i2c"] gl_r01_i2c_ns = cg.esphome_ns.namespace("gl_r01_i2c") GLR01I2CComponent = gl_r01_i2c_ns.class_( - "GLR01I2CComponent", i2c.I2CDevice, cg.PollingComponent + "GLR01I2CComponent", sensor.Sensor, i2c.I2CDevice, cg.PollingComponent ) CONFIG_SCHEMA = ( diff --git a/esphome/components/globals/__init__.py b/esphome/components/globals/__init__.py index fe83b1ea7c..ec6730a41c 100644 --- a/esphome/components/globals/__init__.py +++ b/esphome/components/globals/__init__.py @@ -109,7 +109,7 @@ async def globals_set_to_code(config, action_id, template_arg, args): template_arg = cg.TemplateArguments(full_id.type, *template_arg) var = cg.new_Pvariable(action_id, template_arg, paren) templ = await cg.templatable( - config[CONF_VALUE], args, None, to_exp=cg.RawExpression + config[CONF_VALUE], args, None, to_exp=cg.RawExpression, wrap_constant=True ) cg.add(var.set_value(templ)) return var diff --git a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp index 38ebbc90e4..39b1a2f713 100644 --- a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp +++ b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp @@ -23,9 +23,8 @@ static const LogString *gpio_mode_to_string(bool use_interrupt) { void IRAM_ATTR GPIOBinarySensorStore::gpio_intr(GPIOBinarySensorStore *arg) { bool new_state = arg->isr_pin_.digital_read(); - if (new_state != arg->last_state_) { + if (new_state != arg->state_) { arg->state_ = new_state; - arg->last_state_ = new_state; arg->changed_ = true; // Wake up the component from its disabled loop state if (arg->component_ != nullptr) { @@ -34,28 +33,27 @@ void IRAM_ATTR GPIOBinarySensorStore::gpio_intr(GPIOBinarySensorStore *arg) { } } -void GPIOBinarySensorStore::setup(InternalGPIOPin *pin, gpio::InterruptType type, Component *component) { +void GPIOBinarySensorStore::setup(InternalGPIOPin *pin, Component *component) { pin->setup(); this->isr_pin_ = pin->to_isr(); this->component_ = component; // Read initial state - this->last_state_ = pin->digital_read(); - this->state_ = this->last_state_; + this->state_ = pin->digital_read(); // Attach interrupt - from this point on, any changes will be caught by the interrupt - pin->attach_interrupt(&GPIOBinarySensorStore::gpio_intr, this, type); + pin->attach_interrupt(&GPIOBinarySensorStore::gpio_intr, this, this->interrupt_type_); } void GPIOBinarySensor::setup() { - if (this->use_interrupt_ && !this->pin_->is_internal()) { + if (this->store_.use_interrupt_ && !this->pin_->is_internal()) { ESP_LOGD(TAG, "GPIO is not internal, falling back to polling mode"); - this->use_interrupt_ = false; + this->store_.use_interrupt_ = false; } - if (this->use_interrupt_) { + if (this->store_.use_interrupt_) { auto *internal_pin = static_cast(this->pin_); - this->store_.setup(internal_pin, this->interrupt_type_, this); + this->store_.setup(internal_pin, this); this->publish_initial_state(this->store_.get_state()); } else { this->pin_->setup(); @@ -66,14 +64,14 @@ void GPIOBinarySensor::setup() { void GPIOBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "GPIO Binary Sensor", this); LOG_PIN(" Pin: ", this->pin_); - ESP_LOGCONFIG(TAG, " Mode: %s", LOG_STR_ARG(gpio_mode_to_string(this->use_interrupt_))); - if (this->use_interrupt_) { - ESP_LOGCONFIG(TAG, " Interrupt Type: %s", LOG_STR_ARG(interrupt_type_to_string(this->interrupt_type_))); + ESP_LOGCONFIG(TAG, " Mode: %s", LOG_STR_ARG(gpio_mode_to_string(this->store_.use_interrupt_))); + if (this->store_.use_interrupt_) { + ESP_LOGCONFIG(TAG, " Interrupt Type: %s", LOG_STR_ARG(interrupt_type_to_string(this->store_.interrupt_type_))); } } void GPIOBinarySensor::loop() { - if (this->use_interrupt_) { + if (this->store_.use_interrupt_) { if (this->store_.is_changed()) { // Clear the flag immediately to minimize the window where we might miss changes this->store_.clear_changed(); diff --git a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h index 8cf52f540b..24efc2a0e6 100644 --- a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h +++ b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h @@ -8,10 +8,10 @@ namespace esphome { namespace gpio { -// Store class for ISR data (no vtables, ISR-safe) +// Store class for ISR data and configuration (no vtables, ISR-safe) class GPIOBinarySensorStore { public: - void setup(InternalGPIOPin *pin, gpio::InterruptType type, Component *component); + void setup(InternalGPIOPin *pin, Component *component); static void gpio_intr(GPIOBinarySensorStore *arg); @@ -32,21 +32,23 @@ class GPIOBinarySensorStore { } protected: + friend class GPIOBinarySensor; ISRInternalGPIOPin isr_pin_; - volatile bool state_{false}; - volatile bool last_state_{false}; - volatile bool changed_{false}; Component *component_{nullptr}; // Pointer to the component for enable_loop_soon_any_context() + volatile bool state_{false}; + volatile bool changed_{false}; + bool use_interrupt_{true}; + gpio::InterruptType interrupt_type_{gpio::INTERRUPT_ANY_EDGE}; }; -class GPIOBinarySensor : public binary_sensor::BinarySensor, public Component { +class GPIOBinarySensor final : public binary_sensor::BinarySensor, public Component { public: // No destructor needed: ESPHome components are created at boot and live forever. // Interrupts are only detached on reboot when memory is cleared anyway. - void set_pin(GPIOPin *pin) { pin_ = pin; } - void set_use_interrupt(bool use_interrupt) { use_interrupt_ = use_interrupt; } - void set_interrupt_type(gpio::InterruptType type) { interrupt_type_ = type; } + void set_pin(GPIOPin *pin) { this->pin_ = pin; } + void set_use_interrupt(bool use_interrupt) { this->store_.use_interrupt_ = use_interrupt; } + void set_interrupt_type(gpio::InterruptType type) { this->store_.interrupt_type_ = type; } // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) /// Setup pin @@ -59,8 +61,6 @@ class GPIOBinarySensor : public binary_sensor::BinarySensor, public Component { protected: GPIOPin *pin_; - bool use_interrupt_{true}; - gpio::InterruptType interrupt_type_{gpio::INTERRUPT_ANY_EDGE}; GPIOBinarySensorStore store_; }; diff --git a/esphome/components/gpio/switch/__init__.py b/esphome/components/gpio/switch/__init__.py index 604de6d809..9462cd0161 100644 --- a/esphome/components/gpio/switch/__init__.py +++ b/esphome/components/gpio/switch/__init__.py @@ -32,6 +32,7 @@ async def to_code(config): cg.add(var.set_pin(pin)) if CONF_INTERLOCK in config: + cg.add_define("USE_GPIO_SWITCH_INTERLOCK") interlock = [] for it in config[CONF_INTERLOCK]: lock = await cg.get_variable(it) diff --git a/esphome/components/gpio/switch/gpio_switch.cpp b/esphome/components/gpio/switch/gpio_switch.cpp index 9043a6a493..9c6464815a 100644 --- a/esphome/components/gpio/switch/gpio_switch.cpp +++ b/esphome/components/gpio/switch/gpio_switch.cpp @@ -5,6 +5,9 @@ namespace esphome { namespace gpio { static const char *const TAG = "switch.gpio"; +#ifdef USE_GPIO_SWITCH_INTERLOCK +static constexpr uint32_t INTERLOCK_TIMEOUT_ID = 0; +#endif float GPIOSwitch::get_setup_priority() const { return setup_priority::HARDWARE; } void GPIOSwitch::setup() { @@ -27,6 +30,7 @@ void GPIOSwitch::setup() { void GPIOSwitch::dump_config() { LOG_SWITCH("", "GPIO Switch", this); LOG_PIN(" Pin: ", this->pin_); +#ifdef USE_GPIO_SWITCH_INTERLOCK if (!this->interlock_.empty()) { ESP_LOGCONFIG(TAG, " Interlocks:"); for (auto *lock : this->interlock_) { @@ -35,8 +39,10 @@ void GPIOSwitch::dump_config() { ESP_LOGCONFIG(TAG, " %s", lock->get_name().c_str()); } } +#endif } void GPIOSwitch::write_state(bool state) { +#ifdef USE_GPIO_SWITCH_INTERLOCK if (state != this->inverted_) { // Turning ON, check interlocking @@ -51,7 +57,7 @@ void GPIOSwitch::write_state(bool state) { } } if (found && this->interlock_wait_time_ != 0) { - this->set_timeout("interlock", this->interlock_wait_time_, [this, state] { + this->set_timeout(INTERLOCK_TIMEOUT_ID, this->interlock_wait_time_, [this, state] { // Don't write directly, call the function again // (some other switch may have changed state while we were waiting) this->write_state(state); @@ -61,13 +67,17 @@ void GPIOSwitch::write_state(bool state) { } else if (this->interlock_wait_time_ != 0) { // If we are switched off during the interlock wait time, cancel any pending // re-activations - this->cancel_timeout("interlock"); + this->cancel_timeout(INTERLOCK_TIMEOUT_ID); } +#endif this->pin_->digital_write(state); this->publish_state(state); } + +#ifdef USE_GPIO_SWITCH_INTERLOCK void GPIOSwitch::set_interlock(const std::initializer_list &interlock) { this->interlock_ = interlock; } +#endif } // namespace gpio } // namespace esphome diff --git a/esphome/components/gpio/switch/gpio_switch.h b/esphome/components/gpio/switch/gpio_switch.h index 080decac08..f7415d1dba 100644 --- a/esphome/components/gpio/switch/gpio_switch.h +++ b/esphome/components/gpio/switch/gpio_switch.h @@ -8,7 +8,7 @@ namespace esphome { namespace gpio { -class GPIOSwitch : public switch_::Switch, public Component { +class GPIOSwitch final : public switch_::Switch, public Component { public: void set_pin(GPIOPin *pin) { pin_ = pin; } @@ -18,15 +18,19 @@ class GPIOSwitch : public switch_::Switch, public Component { void setup() override; void dump_config() override; +#ifdef USE_GPIO_SWITCH_INTERLOCK void set_interlock(const std::initializer_list &interlock); void set_interlock_wait_time(uint32_t interlock_wait_time) { interlock_wait_time_ = interlock_wait_time; } +#endif protected: void write_state(bool state) override; GPIOPin *pin_; +#ifdef USE_GPIO_SWITCH_INTERLOCK FixedVector interlock_; uint32_t interlock_wait_time_{0}; +#endif }; } // namespace gpio diff --git a/esphome/components/gpio_expander/cached_gpio.h b/esphome/components/gpio_expander/cached_gpio.h index eeff98cb6e..ddb9e63686 100644 --- a/esphome/components/gpio_expander/cached_gpio.h +++ b/esphome/components/gpio_expander/cached_gpio.h @@ -28,7 +28,10 @@ namespace esphome::gpio_expander { template 256), uint16_t, uint8_t>::type> class CachedGpioExpander { public: - /// @brief Read the state of the given pin. This will invalidate the cache for the given pin number. + /// @brief Read the state of the given pin. + /// By default, each read invalidates the pin's cache entry so the next read + /// of the same pin triggers a fresh hardware read. When invalidate_on_read + /// is disabled, the cache stays valid until explicitly cleared via reset_pin_cache_(). /// @param pin Pin number to read /// @return Pin state bool digital_read(P pin) { @@ -36,14 +39,17 @@ class CachedGpioExpander { const T pin_mask = (1 << (pin % BANK_SIZE)); // Check if specific pin cache is valid if (this->read_cache_valid_[bank] & pin_mask) { - // Invalidate pin - this->read_cache_valid_[bank] &= ~pin_mask; + if (this->invalidate_on_read_) { + // Invalidate pin so next read triggers hardware read + this->read_cache_valid_[bank] &= ~pin_mask; + } } else { // Read whole bank from hardware if (!this->digital_read_hw(pin)) return false; // Mark bank cache as valid except the pin that is being returned now - this->read_cache_valid_[bank] = std::numeric_limits::max() & ~pin_mask; + // (when not invalidating on read, mark all pins including this one as valid) + this->read_cache_valid_[bank] = std::numeric_limits::max() & ~(this->invalidate_on_read_ ? pin_mask : 0); } return this->digital_read_cache(pin); } @@ -71,12 +77,18 @@ class CachedGpioExpander { /// @brief Invalidate cache. This function should be called in component loop(). void reset_pin_cache_() { memset(this->read_cache_valid_, 0x00, CACHE_SIZE_BYTES); } + /// @brief Control whether digital_read() invalidates the pin's cache entry after reading. + /// When enabled (default), each read self-invalidates so the next read triggers a hardware read. + /// When disabled, cache stays valid until reset_pin_cache_() is explicitly called. + void set_invalidate_on_read_(bool invalidate) { this->invalidate_on_read_ = invalidate; } + static constexpr uint16_t BITS_PER_BYTE = 8; static constexpr uint16_t BANK_SIZE = sizeof(T) * BITS_PER_BYTE; static constexpr size_t BANKS = N / BANK_SIZE; static constexpr size_t CACHE_SIZE_BYTES = BANKS * sizeof(T); T read_cache_valid_[BANKS]{0}; + bool invalidate_on_read_{true}; }; } // namespace esphome::gpio_expander diff --git a/esphome/components/gps/time/gps_time.cpp b/esphome/components/gps/time/gps_time.cpp index cff8c1fb07..fb662a3d60 100644 --- a/esphome/components/gps/time/gps_time.cpp +++ b/esphome/components/gps/time/gps_time.cpp @@ -16,10 +16,6 @@ void GPSTime::from_tiny_gps_(TinyGPSPlus &tiny_gps) { val.year = tiny_gps.date.year(); val.month = tiny_gps.date.month(); val.day_of_month = tiny_gps.date.day(); - // Set these to valid value for recalc_timestamp_utc - it's not used for calculation - val.day_of_week = 1; - val.day_of_year = 1; - val.hour = tiny_gps.time.hour(); val.minute = tiny_gps.time.minute(); val.second = tiny_gps.time.second(); diff --git a/esphome/components/graph/__init__.py b/esphome/components/graph/__init__.py index d72fe40dd2..0749d7e2a3 100644 --- a/esphome/components/graph/__init__.py +++ b/esphome/components/graph/__init__.py @@ -110,7 +110,7 @@ GRAPH_SCHEMA = cv.Schema( cv.Optional(CONF_MIN_RANGE): cv.float_range(min=0, min_included=False), cv.Optional(CONF_MAX_RANGE): cv.float_range(min=0, min_included=False), cv.Optional(CONF_TRACES): cv.ensure_list(GRAPH_TRACE_SCHEMA), - cv.Optional(CONF_LEGEND): cv.ensure_list(GRAPH_LEGEND_SCHEMA), + cv.Optional(CONF_LEGEND): GRAPH_LEGEND_SCHEMA, } ) @@ -192,7 +192,7 @@ async def to_code(config): cg.add(var.add_trace(tr)) # Add legend if CONF_LEGEND in config: - lgd = config[CONF_LEGEND][0] + lgd = config[CONF_LEGEND] legend = cg.new_Pvariable(lgd[CONF_ID], GraphLegend()) if CONF_NAME_FONT in lgd: font = await cg.get_variable(lgd[CONF_NAME_FONT]) diff --git a/esphome/components/graphical_display_menu/graphical_display_menu.h b/esphome/components/graphical_display_menu/graphical_display_menu.h index 96f2bd79fd..ce1db18525 100644 --- a/esphome/components/graphical_display_menu/graphical_display_menu.h +++ b/esphome/components/graphical_display_menu/graphical_display_menu.h @@ -44,7 +44,7 @@ class GraphicalDisplayMenu : public display_menu_base::DisplayMenuComponent { void set_foreground_color(Color foreground_color); void set_background_color(Color background_color); - void add_on_redraw_callback(std::function &&cb) { this->on_redraw_callbacks_.add(std::move(cb)); } + template void add_on_redraw_callback(F &&cb) { this->on_redraw_callbacks_.add(std::forward(cb)); } void draw(display::Display *display, const display::Rect *bounds); @@ -75,9 +75,12 @@ class GraphicalDisplayMenu : public display_menu_base::DisplayMenuComponent { class GraphicalDisplayMenuOnRedrawTrigger : public Trigger { public: - explicit GraphicalDisplayMenuOnRedrawTrigger(GraphicalDisplayMenu *parent) { - parent->add_on_redraw_callback([this, parent]() { this->trigger(parent); }); + explicit GraphicalDisplayMenuOnRedrawTrigger(GraphicalDisplayMenu *parent) : parent_(parent) { + parent->add_on_redraw_callback([this]() { this->trigger(this->parent_); }); } + + protected: + GraphicalDisplayMenu *parent_; }; } // namespace graphical_display_menu diff --git a/esphome/components/grove_tb6612fng/__init__.py b/esphome/components/grove_tb6612fng/__init__.py index 210e2f7bab..ae64c049f5 100644 --- a/esphome/components/grove_tb6612fng/__init__.py +++ b/esphome/components/grove_tb6612fng/__init__.py @@ -78,13 +78,11 @@ async def grove_tb6612fng_run_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) - template_channel = await cg.templatable(config[CONF_CHANNEL], args, int) + template_channel = await cg.templatable(config[CONF_CHANNEL], args, cg.uint8) template_speed = await cg.templatable(config[CONF_SPEED], args, cg.uint16) - template_speed = ( - template_speed if config[CONF_DIRECTION] == "FORWARD" else -template_speed - ) cg.add(var.set_channel(template_channel)) cg.add(var.set_speed(template_speed)) + cg.add(var.set_direction(config[CONF_DIRECTION] == "FORWARD")) return var @@ -103,7 +101,7 @@ async def grove_tb6612fng_break_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) - template_channel = await cg.templatable(config[CONF_CHANNEL], args, int) + template_channel = await cg.templatable(config[CONF_CHANNEL], args, cg.uint8) cg.add(var.set_channel(template_channel)) return var @@ -123,7 +121,7 @@ async def grove_tb6612fng_stop_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) - template_channel = await cg.templatable(config[CONF_CHANNEL], args, int) + template_channel = await cg.templatable(config[CONF_CHANNEL], args, cg.uint8) cg.add(var.set_channel(template_channel)) return var @@ -177,6 +175,6 @@ async def grove_tb6612fng_change_address_to_code(config, action_id, template_arg var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) - template_channel = await cg.templatable(config[CONF_ADDRESS], args, int) + template_channel = await cg.templatable(config[CONF_ADDRESS], args, cg.uint8) cg.add(var.set_address(template_channel)) return var diff --git a/esphome/components/grove_tb6612fng/grove_tb6612fng.h b/esphome/components/grove_tb6612fng/grove_tb6612fng.h index a36cb85cff..bf47163226 100644 --- a/esphome/components/grove_tb6612fng/grove_tb6612fng.h +++ b/esphome/components/grove_tb6612fng/grove_tb6612fng.h @@ -168,11 +168,19 @@ class GROVETB6612FNGMotorRunAction : public Action, public Parentedforward_ = forward; } + void play(const Ts &...x) override { auto channel = this->channel_.value(x...); - auto speed = this->speed_.value(x...); + int16_t speed = this->speed_.value(x...); + if (!this->forward_) { + speed = -speed; + } this->parent_->dc_motor_run(channel, speed); } + + protected: + bool forward_{true}; }; template diff --git a/esphome/components/growatt_solar/sensor.py b/esphome/components/growatt_solar/sensor.py index 19f3adfd0e..7458b88b72 100644 --- a/esphome/components/growatt_solar/sensor.py +++ b/esphome/components/growatt_solar/sensor.py @@ -12,7 +12,9 @@ from esphome.const import ( CONF_VOLTAGE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_FREQUENCY, DEVICE_CLASS_POWER, + DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, ICON_CURRENT_AC, STATE_CLASS_MEASUREMENT, @@ -53,6 +55,7 @@ PHASE_SENSORS = { unit_of_measurement=UNIT_VOLT, accuracy_decimals=1, device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), CONF_CURRENT: sensor.sensor_schema( unit_of_measurement=UNIT_AMPERE, @@ -72,6 +75,7 @@ PV_SENSORS = { unit_of_measurement=UNIT_VOLT, accuracy_decimals=1, device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), CONF_CURRENT: sensor.sensor_schema( unit_of_measurement=UNIT_AMPERE, @@ -118,6 +122,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_HERTZ, icon=ICON_CURRENT_AC, accuracy_decimals=2, + device_class=DEVICE_CLASS_FREQUENCY, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ACTIVE_POWER): sensor.sensor_schema( @@ -147,6 +152,7 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_INVERTER_MODULE_TEMP): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, ), } diff --git a/esphome/components/haier/climate.py b/esphome/components/haier/climate.py index caaaa18dd6..424ef46392 100644 --- a/esphome/components/haier/climate.py +++ b/esphome/components/haier/climate.py @@ -22,7 +22,6 @@ from esphome.const import ( CONF_SUPPORTED_SWING_MODES, CONF_TARGET_TEMPERATURE, CONF_TEMPERATURE_STEP, - CONF_TRIGGER_ID, CONF_VISUAL, CONF_WIFI, ) @@ -122,21 +121,6 @@ SUPPORTED_HON_CONTROL_METHODS = { "SET_SINGLE_PARAMETER": HonControlMethod.SET_SINGLE_PARAMETER, } -HaierAlarmStartTrigger = haier_ns.class_( - "HaierAlarmStartTrigger", - automation.Trigger.template(cg.uint8, cg.const_char_ptr), -) - -HaierAlarmEndTrigger = haier_ns.class_( - "HaierAlarmEndTrigger", - automation.Trigger.template(cg.uint8, cg.const_char_ptr), -) - -StatusMessageTrigger = haier_ns.class_( - "StatusMessageTrigger", - automation.Trigger.template(cg.const_char_ptr, cg.size_t), -) - def validate_visual(config): if CONF_VISUAL in config: @@ -203,13 +187,7 @@ def _base_config_schema(class_: MockObjClass) -> cv.Schema: cv.Optional( CONF_ANSWER_TIMEOUT, ): cv.positive_time_period_milliseconds, - cv.Optional(CONF_ON_STATUS_MESSAGE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - StatusMessageTrigger - ), - } - ), + cv.Optional(CONF_ON_STATUS_MESSAGE): automation.validate_automation({}), } ) .extend(uart.UART_DEVICE_SCHEMA) @@ -237,9 +215,7 @@ CONFIG_SCHEMA = cv.All( { cv.Optional( CONF_CONTROL_METHOD, default="SET_GROUP_PARAMETERS" - ): cv.ensure_list( - cv.enum(SUPPORTED_HON_CONTROL_METHODS, upper=True) - ), + ): cv.enum(SUPPORTED_HON_CONTROL_METHODS, upper=True), cv.Optional(CONF_BEEPER): cv.invalid( f"The {CONF_BEEPER} option is deprecated, use beeper_on/beeper_off actions or beeper switch for a haier platform instead" ), @@ -264,19 +240,9 @@ CONFIG_SCHEMA = cv.All( f"The {CONF_OUTDOOR_TEMPERATURE} option is deprecated, use a sensor for a haier platform instead" ), cv.Optional(CONF_ON_ALARM_START): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - HaierAlarmStartTrigger - ), - } - ), - cv.Optional(CONF_ON_ALARM_END): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - HaierAlarmEndTrigger - ), - } + {} ), + cv.Optional(CONF_ON_ALARM_END): automation.validate_automation({}), } ), }, @@ -488,6 +454,25 @@ def _final_validate(config): FINAL_VALIDATE_SCHEMA = _final_validate +_CALLBACK_AUTOMATIONS = ( + automation.CallbackAutomation( + CONF_ON_ALARM_START, + "add_alarm_start_callback", + [(cg.uint8, "code"), (cg.const_char_ptr, "message")], + ), + automation.CallbackAutomation( + CONF_ON_ALARM_END, + "add_alarm_end_callback", + [(cg.uint8, "code"), (cg.const_char_ptr, "message")], + ), + automation.CallbackAutomation( + CONF_ON_STATUS_MESSAGE, + "add_status_message_callback", + [(cg.const_char_ptr, "data"), (cg.size_t, "data_size")], + ), +) + + async def to_code(config): cg.add(haier_ns.init_haier_protocol_logging()) var = await climate.new_climate(config) @@ -529,20 +514,6 @@ async def to_code(config): cg.add( var.set_status_message_header_size(config[CONF_STATUS_MESSAGE_HEADER_SIZE]) ) - for conf in config.get(CONF_ON_ALARM_START, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf - ) - for conf in config.get(CONF_ON_ALARM_END, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf - ) - for conf in config.get(CONF_ON_STATUS_MESSAGE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(cg.const_char_ptr, "data"), (cg.size_t, "data_size")], conf - ) + await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS) # https://github.com/paveldn/HaierProtocol cg.add_library("pavlodn/HaierProtocol", "0.9.31") diff --git a/esphome/components/haier/haier_base.cpp b/esphome/components/haier/haier_base.cpp index 1882aa439e..4a06066d3c 100644 --- a/esphome/components/haier/haier_base.cpp +++ b/esphome/components/haier/haier_base.cpp @@ -197,10 +197,6 @@ void HaierClimateBase::send_custom_command(const haier_protocol::HaierMessage &m this->action_request_ = PendingAction({ActionRequest::SEND_CUSTOM_COMMAND, message}); } -void HaierClimateBase::add_status_message_callback(std::function &&callback) { - this->status_message_callback_.add(std::move(callback)); -} - haier_protocol::HandlerError HaierClimateBase::answer_preprocess_( haier_protocol::FrameType request_message_type, haier_protocol::FrameType expected_request_message_type, haier_protocol::FrameType answer_message_type, haier_protocol::FrameType expected_answer_message_type, @@ -246,7 +242,7 @@ void HaierClimateBase::setup() { this->last_request_timestamp_ = std::chrono::steady_clock::now(); this->set_phase(ProtocolPhases::SENDING_INIT_1); this->haier_protocol_.set_default_timeout_handler( - std::bind(&esphome::haier::HaierClimateBase::timeout_default_handler_, this, std::placeholders::_1)); + [this](haier_protocol::FrameType type) { return this->timeout_default_handler_(type); }); this->set_handlers(); this->initialization(); } diff --git a/esphome/components/haier/haier_base.h b/esphome/components/haier/haier_base.h index e24217bfd9..0c416623c0 100644 --- a/esphome/components/haier/haier_base.h +++ b/esphome/components/haier/haier_base.h @@ -74,7 +74,9 @@ class HaierClimateBase : public esphome::Component, void set_answer_timeout(uint32_t timeout); void set_send_wifi(bool send_wifi); void send_custom_command(const haier_protocol::HaierMessage &message); - void add_status_message_callback(std::function &&callback); + template void add_status_message_callback(F &&callback) { + this->status_message_callback_.add(std::forward(callback)); + } protected: enum class ProtocolPhases { @@ -175,12 +177,5 @@ class HaierClimateBase : public esphome::Component, ESPPreferenceObject base_rtc_; }; -class StatusMessageTrigger : public Trigger { - public: - explicit StatusMessageTrigger(HaierClimateBase *parent) { - parent->add_status_message_callback([this](const char *data, size_t data_size) { this->trigger(data, data_size); }); - } -}; - } // namespace haier } // namespace esphome diff --git a/esphome/components/haier/hon_climate.cpp b/esphome/components/haier/hon_climate.cpp index b027b0f295..1e9cb42f38 100644 --- a/esphome/components/haier/hon_climate.cpp +++ b/esphome/components/haier/hon_climate.cpp @@ -114,14 +114,6 @@ void HonClimate::start_steri_cleaning() { } } -void HonClimate::add_alarm_start_callback(std::function &&callback) { - this->alarm_start_callback_.add(std::move(callback)); -} - -void HonClimate::add_alarm_end_callback(std::function &&callback) { - this->alarm_end_callback_.add(std::move(callback)); -} - haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size) { @@ -309,32 +301,38 @@ void HonClimate::set_handlers() { // Set handlers this->haier_protocol_.set_answer_handler( haier_protocol::FrameType::GET_DEVICE_VERSION, - std::bind(&HonClimate::get_device_version_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3, std::placeholders::_4)); + [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) { + return this->get_device_version_answer_handler_(req, msg, data, size); + }); this->haier_protocol_.set_answer_handler( haier_protocol::FrameType::GET_DEVICE_ID, - std::bind(&HonClimate::get_device_id_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3, std::placeholders::_4)); + [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) { + return this->get_device_id_answer_handler_(req, msg, data, size); + }); this->haier_protocol_.set_answer_handler( haier_protocol::FrameType::CONTROL, - std::bind(&HonClimate::status_handler_, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, - std::placeholders::_4)); + [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) { + return this->status_handler_(req, msg, data, size); + }); this->haier_protocol_.set_answer_handler( haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION, - std::bind(&HonClimate::get_management_information_answer_handler_, this, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) { + return this->get_management_information_answer_handler_(req, msg, data, size); + }); this->haier_protocol_.set_answer_handler( haier_protocol::FrameType::GET_ALARM_STATUS, - std::bind(&HonClimate::get_alarm_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3, std::placeholders::_4)); + [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) { + return this->get_alarm_status_answer_handler_(req, msg, data, size); + }); this->haier_protocol_.set_answer_handler( haier_protocol::FrameType::REPORT_NETWORK_STATUS, - std::bind(&HonClimate::report_network_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3, std::placeholders::_4)); - this->haier_protocol_.set_message_handler( - haier_protocol::FrameType::ALARM_STATUS, - std::bind(&HonClimate::alarm_status_message_handler_, this, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3)); + [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) { + return this->report_network_status_answer_handler_(req, msg, data, size); + }); + this->haier_protocol_.set_message_handler(haier_protocol::FrameType::ALARM_STATUS, + [this](haier_protocol::FrameType type, const uint8_t *data, size_t size) { + return this->alarm_status_message_handler_(type, data, size); + }); } void HonClimate::dump_config() { @@ -749,7 +747,7 @@ void HonClimate::update_sub_sensor_(SubSensorType type, float value) { if (type < SubSensorType::SUB_SENSOR_TYPE_COUNT) { size_t index = (size_t) type; if ((this->sub_sensors_[index] != nullptr) && - ((!this->sub_sensors_[index]->has_state()) || (this->sub_sensors_[index]->raw_state != value))) + ((!this->sub_sensors_[index]->has_state()) || (this->sub_sensors_[index]->get_raw_state() != value))) this->sub_sensors_[index]->publish_state(value); } } diff --git a/esphome/components/haier/hon_climate.h b/esphome/components/haier/hon_climate.h index 9bddac3f92..7a87f27b66 100644 --- a/esphome/components/haier/hon_climate.h +++ b/esphome/components/haier/hon_climate.h @@ -124,8 +124,12 @@ class HonClimate : public HaierClimateBase { void set_extra_sensors_packet_bytes_size(size_t size) { this->extra_sensors_packet_bytes_ = size; }; void set_status_message_header_size(size_t size) { this->status_message_header_size_ = size; }; void set_control_method(HonControlMethod method) { this->control_method_ = method; }; - void add_alarm_start_callback(std::function &&callback); - void add_alarm_end_callback(std::function &&callback); + template void add_alarm_start_callback(F &&callback) { + this->alarm_start_callback_.add(std::forward(callback)); + } + template void add_alarm_end_callback(F &&callback) { + this->alarm_end_callback_.add(std::forward(callback)); + } float get_active_alarm_count() const { return this->active_alarm_count_; } protected: @@ -196,21 +200,5 @@ class HonClimate : public HaierClimateBase { SwitchState quiet_mode_state_{SwitchState::OFF}; }; -class HaierAlarmStartTrigger : public Trigger { - public: - explicit HaierAlarmStartTrigger(HonClimate *parent) { - parent->add_alarm_start_callback( - [this](uint8_t alarm_code, const char *alarm_message) { this->trigger(alarm_code, alarm_message); }); - } -}; - -class HaierAlarmEndTrigger : public Trigger { - public: - explicit HaierAlarmEndTrigger(HonClimate *parent) { - parent->add_alarm_end_callback( - [this](uint8_t alarm_code, const char *alarm_message) { this->trigger(alarm_code, alarm_message); }); - } -}; - } // namespace haier } // namespace esphome diff --git a/esphome/components/haier/smartair2_climate.cpp b/esphome/components/haier/smartair2_climate.cpp index e91224e2d8..2be5d13050 100644 --- a/esphome/components/haier/smartair2_climate.cpp +++ b/esphome/components/haier/smartair2_climate.cpp @@ -106,18 +106,21 @@ void Smartair2Climate::set_handlers() { // Set handlers this->haier_protocol_.set_answer_handler( haier_protocol::FrameType::GET_DEVICE_VERSION, - std::bind(&Smartair2Climate::get_device_version_answer_handler_, this, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) { + return this->get_device_version_answer_handler_(req, msg, data, size); + }); this->haier_protocol_.set_answer_handler( haier_protocol::FrameType::CONTROL, - std::bind(&Smartair2Climate::status_handler_, this, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3, std::placeholders::_4)); + [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) { + return this->status_handler_(req, msg, data, size); + }); this->haier_protocol_.set_answer_handler( haier_protocol::FrameType::REPORT_NETWORK_STATUS, - std::bind(&Smartair2Climate::report_network_status_answer_handler_, this, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) { + return this->report_network_status_answer_handler_(req, msg, data, size); + }); this->haier_protocol_.set_default_timeout_handler( - std::bind(&Smartair2Climate::messages_timeout_handler_with_cycle_for_init_, this, std::placeholders::_1)); + [this](haier_protocol::FrameType type) { return this->messages_timeout_handler_with_cycle_for_init_(type); }); } void Smartair2Climate::dump_config() { diff --git a/esphome/components/havells_solar/sensor.py b/esphome/components/havells_solar/sensor.py index 532315a1d1..f0683e1d9c 100644 --- a/esphome/components/havells_solar/sensor.py +++ b/esphome/components/havells_solar/sensor.py @@ -13,7 +13,9 @@ from esphome.const import ( CONF_VOLTAGE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_FREQUENCY, DEVICE_CLASS_POWER, + DEVICE_CLASS_REACTIVE_POWER, DEVICE_CLASS_VOLTAGE, ICON_CURRENT_AC, STATE_CLASS_MEASUREMENT, @@ -64,6 +66,7 @@ PHASE_SENSORS = { unit_of_measurement=UNIT_VOLT, accuracy_decimals=2, device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), CONF_CURRENT: sensor.sensor_schema( unit_of_measurement=UNIT_AMPERE, @@ -77,6 +80,7 @@ PV_SENSORS = { unit_of_measurement=UNIT_VOLT, accuracy_decimals=2, device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), CONF_CURRENT: sensor.sensor_schema( unit_of_measurement=UNIT_AMPERE, @@ -123,6 +127,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_HERTZ, icon=ICON_CURRENT_AC, accuracy_decimals=2, + device_class=DEVICE_CLASS_FREQUENCY, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ACTIVE_POWER): sensor.sensor_schema( @@ -134,6 +139,7 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, accuracy_decimals=2, + device_class=DEVICE_CLASS_REACTIVE_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ENERGY_PRODUCTION_DAY): sensor.sensor_schema( @@ -171,6 +177,7 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_INVERTER_BUS_VOLTAGE): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_INSULATION_OF_PV_N_TO_GROUND): sensor.sensor_schema( @@ -181,21 +188,25 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_GFCI_VALUE): sensor.sensor_schema( unit_of_measurement=UNIT_MILLIAMPERE, accuracy_decimals=0, + device_class=DEVICE_CLASS_CURRENT, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_DCI_OF_R): sensor.sensor_schema( unit_of_measurement=UNIT_MILLIAMPERE, accuracy_decimals=0, + device_class=DEVICE_CLASS_CURRENT, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_DCI_OF_S): sensor.sensor_schema( unit_of_measurement=UNIT_MILLIAMPERE, accuracy_decimals=0, + device_class=DEVICE_CLASS_CURRENT, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_DCI_OF_T): sensor.sensor_schema( unit_of_measurement=UNIT_MILLIAMPERE, accuracy_decimals=0, + device_class=DEVICE_CLASS_CURRENT, state_class=STATE_CLASS_MEASUREMENT, ), } diff --git a/esphome/components/hbridge/fan/hbridge_fan.cpp b/esphome/components/hbridge/fan/hbridge_fan.cpp index 89c162eebf..d548128b99 100644 --- a/esphome/components/hbridge/fan/hbridge_fan.cpp +++ b/esphome/components/hbridge/fan/hbridge_fan.cpp @@ -30,7 +30,6 @@ fan::FanCall HBridgeFan::brake() { void HBridgeFan::setup() { // Construct traits before restore so preset modes can be looked up by index this->traits_ = fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_); - this->traits_.set_supported_preset_modes(this->preset_modes_); auto restore = this->restore_state_(); if (restore.has_value()) { diff --git a/esphome/components/hbridge/fan/hbridge_fan.h b/esphome/components/hbridge/fan/hbridge_fan.h index ec1e8ada0e..997f66ae48 100644 --- a/esphome/components/hbridge/fan/hbridge_fan.h +++ b/esphome/components/hbridge/fan/hbridge_fan.h @@ -20,11 +20,14 @@ class HBridgeFan : public Component, public fan::Fan { void set_pin_a(output::FloatOutput *pin_a) { pin_a_ = pin_a; } void set_pin_b(output::FloatOutput *pin_b) { pin_b_ = pin_b; } void set_enable_pin(output::FloatOutput *enable) { enable_ = enable; } - void set_preset_modes(std::initializer_list presets) { preset_modes_ = presets; } + void set_preset_modes(std::initializer_list presets) { this->set_supported_preset_modes(presets); } void setup() override; void dump_config() override; - fan::FanTraits get_traits() override { return this->traits_; } + fan::FanTraits get_traits() override { + this->wire_preset_modes_(this->traits_); + return this->traits_; + } fan::FanCall brake(); @@ -36,7 +39,6 @@ class HBridgeFan : public Component, public fan::Fan { int speed_count_{}; DecayMode decay_mode_{DECAY_MODE_SLOW}; fan::FanTraits traits_; - std::vector preset_modes_{}; void control(const fan::FanCall &call) override; void write_state_(); diff --git a/esphome/components/hdc2080/__init__.py b/esphome/components/hdc2080/__init__.py new file mode 100644 index 0000000000..341ea61048 --- /dev/null +++ b/esphome/components/hdc2080/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@G-Pereira", "@jesserockz"] diff --git a/esphome/components/hdc2080/hdc2080.cpp b/esphome/components/hdc2080/hdc2080.cpp new file mode 100644 index 0000000000..dcb207e099 --- /dev/null +++ b/esphome/components/hdc2080/hdc2080.cpp @@ -0,0 +1,71 @@ +#include "hdc2080.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome::hdc2080 { + +static const char *const TAG = "hdc2080"; + +// Register map (Table 8-6) +static constexpr uint8_t REG_TEMPERATURE_LOW = 0x00; // Temperature [7:0] +static constexpr uint8_t REG_TEMPERATURE_HIGH = 0x01; // Temperature [15:8] +static constexpr uint8_t REG_HUMIDITY_LOW = 0x02; // Humidity [7:0] +static constexpr uint8_t REG_HUMIDITY_HIGH = 0x03; // Humidity [15:8] +static constexpr uint8_t REG_RESET_DRDY_INT_CONF = 0x0E; // Soft Reset and Interrupt Configuration +static constexpr uint8_t REG_MEASUREMENT_CONFIGURATION = 0x0F; + +// Measurement register (0x0F) bit fields +static constexpr uint8_t MEAS_TRIG = 0x01; // Bit 0: start measurement +static constexpr uint8_t MEAS_CONF_TEMP = 0x02; // Bits 2:1 = 01: temperature only +static constexpr uint8_t MEAS_CONF_HUM = 0x04; // Bits 2:1 = 10: humidity only + +void HDC2080Component::setup() { + const uint8_t data = 0x00; // automatic measurement mode disabled, heater off + if (this->write_register(REG_RESET_DRDY_INT_CONF, &data, 1) != i2c::ERROR_OK) { + this->mark_failed(ESP_LOG_MSG_COMM_FAIL); + return; + } +} + +void HDC2080Component::dump_config() { + ESP_LOGCONFIG(TAG, "HDC2080:"); + LOG_I2C_DEVICE(this); + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); + if (this->is_failed()) { + ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); + } +} + +void HDC2080Component::update() { + uint8_t data = MEAS_TRIG; // 14-bit resolution, measure both, start + if (this->temperature_sensor_ != nullptr && this->humidity_sensor_ == nullptr) { + data = MEAS_TRIG | MEAS_CONF_TEMP; + } else if (this->temperature_sensor_ == nullptr && this->humidity_sensor_ != nullptr) { + data = MEAS_TRIG | MEAS_CONF_HUM; + } + if (this->write_register(REG_MEASUREMENT_CONFIGURATION, &data, 1) != i2c::ERROR_OK) { + this->status_set_warning(ESP_LOG_MSG_COMM_FAIL); + return; + } + // wait for conversion to complete 2ms should be enough, more is fine + this->set_timeout(5, [this]() { + uint8_t raw_data[4]; + if (this->read_register(REG_TEMPERATURE_LOW, raw_data, 4) != i2c::ERROR_OK) { + this->status_set_warning(ESP_LOG_MSG_COMM_FAIL); + return; + } + this->status_clear_warning(); + if (this->temperature_sensor_ != nullptr) { + float temp = encode_uint16(raw_data[1], raw_data[0]) * (165.0f / 65536.0f) - 40.5f; + this->temperature_sensor_->publish_state(temp); + } + if (this->humidity_sensor_ != nullptr) { + float humidity = encode_uint16(raw_data[3], raw_data[2]) * (100.0f / 65536.0f); + this->humidity_sensor_->publish_state(humidity); + } + }); +} + +} // namespace esphome::hdc2080 diff --git a/esphome/components/hdc2080/hdc2080.h b/esphome/components/hdc2080/hdc2080.h new file mode 100644 index 0000000000..daa10d371d --- /dev/null +++ b/esphome/components/hdc2080/hdc2080.h @@ -0,0 +1,24 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" + +namespace esphome::hdc2080 { + +class HDC2080Component : public PollingComponent, public i2c::I2CDevice { + public: + void set_temperature(sensor::Sensor *temperature) { this->temperature_sensor_ = temperature; } + void set_humidity(sensor::Sensor *humidity) { this->humidity_sensor_ = humidity; } + + /// Setup the sensor and check for connection. + void setup() override; + void dump_config() override; + void update() override; + + protected: + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; +}; + +} // namespace esphome::hdc2080 diff --git a/esphome/components/hdc2080/sensor.py b/esphome/components/hdc2080/sensor.py new file mode 100644 index 0000000000..777fc51cba --- /dev/null +++ b/esphome/components/hdc2080/sensor.py @@ -0,0 +1,57 @@ +import esphome.codegen as cg +from esphome.components import i2c, sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_HUMIDITY, + CONF_ID, + CONF_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_PERCENT, +) + +DEPENDENCIES = ["i2c"] + +hdc2080_ns = cg.esphome_ns.namespace("hdc2080") +HDC2080Component = hdc2080_ns.class_( + "HDC2080Component", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(HDC2080Component), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x40)) + .add_extra(cv.has_at_least_one_key(CONF_TEMPERATURE, CONF_HUMIDITY)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if temperature_config := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temperature_config) + cg.add(var.set_temperature(sens)) + + if humidity_config := config.get(CONF_HUMIDITY): + sens = await sensor.new_sensor(humidity_config) + cg.add(var.set_humidity(sens)) diff --git a/esphome/components/hlk_fm22x/__init__.py b/esphome/components/hlk_fm22x/__init__.py index cb6d5cdfd6..c1aa81f6d4 100644 --- a/esphome/components/hlk_fm22x/__init__.py +++ b/esphome/components/hlk_fm22x/__init__.py @@ -8,7 +8,6 @@ from esphome.const import ( CONF_NAME, CONF_ON_ENROLLMENT_DONE, CONF_ON_ENROLLMENT_FAILED, - CONF_TRIGGER_ID, ) CODEOWNERS = ["@OnFreund"] @@ -28,33 +27,6 @@ HlkFm22xComponent = hlk_fm22x_ns.class_( "HlkFm22xComponent", cg.PollingComponent, uart.UARTDevice ) -FaceScanMatchedTrigger = hlk_fm22x_ns.class_( - "FaceScanMatchedTrigger", automation.Trigger.template(cg.int16, cg.std_string) -) - -FaceScanUnmatchedTrigger = hlk_fm22x_ns.class_( - "FaceScanUnmatchedTrigger", automation.Trigger.template() -) - -FaceScanInvalidTrigger = hlk_fm22x_ns.class_( - "FaceScanInvalidTrigger", automation.Trigger.template(cg.uint8) -) - -FaceInfoTrigger = hlk_fm22x_ns.class_( - "FaceInfoTrigger", - automation.Trigger.template( - cg.int16, cg.int16, cg.int16, cg.int16, cg.int16, cg.int16, cg.int16, cg.int16 - ), -) - -EnrollmentDoneTrigger = hlk_fm22x_ns.class_( - "EnrollmentDoneTrigger", automation.Trigger.template(cg.int16, cg.uint8) -) - -EnrollmentFailedTrigger = hlk_fm22x_ns.class_( - "EnrollmentFailedTrigger", automation.Trigger.template(cg.uint8) -) - EnrollmentAction = hlk_fm22x_ns.class_("EnrollmentAction", automation.Action) DeleteAction = hlk_fm22x_ns.class_("DeleteAction", automation.Action) DeleteAllAction = hlk_fm22x_ns.class_("DeleteAllAction", automation.Action) @@ -65,46 +37,14 @@ CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(HlkFm22xComponent), - cv.Optional(CONF_ON_FACE_SCAN_MATCHED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - FaceScanMatchedTrigger - ), - } - ), + cv.Optional(CONF_ON_FACE_SCAN_MATCHED): automation.validate_automation({}), cv.Optional(CONF_ON_FACE_SCAN_UNMATCHED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - FaceScanUnmatchedTrigger - ), - } - ), - cv.Optional(CONF_ON_FACE_SCAN_INVALID): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - FaceScanInvalidTrigger - ), - } - ), - cv.Optional(CONF_ON_FACE_INFO): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FaceInfoTrigger), - } - ), - cv.Optional(CONF_ON_ENROLLMENT_DONE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - EnrollmentDoneTrigger - ), - } - ), - cv.Optional(CONF_ON_ENROLLMENT_FAILED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - EnrollmentFailedTrigger - ), - } + {} ), + cv.Optional(CONF_ON_FACE_SCAN_INVALID): automation.validate_automation({}), + cv.Optional(CONF_ON_FACE_INFO): automation.validate_automation({}), + cv.Optional(CONF_ON_ENROLLMENT_DONE): automation.validate_automation({}), + cv.Optional(CONF_ON_ENROLLMENT_FAILED): automation.validate_automation({}), } ) .extend(cv.polling_component_schema("50ms")) @@ -112,51 +52,53 @@ CONFIG_SCHEMA = cv.All( ) +_CALLBACK_AUTOMATIONS = ( + automation.CallbackAutomation( + CONF_ON_FACE_SCAN_MATCHED, + "add_on_face_scan_matched_callback", + [(cg.int16, "face_id"), (cg.std_string, "name")], + ), + automation.CallbackAutomation( + CONF_ON_FACE_SCAN_UNMATCHED, "add_on_face_scan_unmatched_callback" + ), + automation.CallbackAutomation( + CONF_ON_FACE_SCAN_INVALID, + "add_on_face_scan_invalid_callback", + [(cg.uint8, "error")], + ), + automation.CallbackAutomation( + CONF_ON_FACE_INFO, + "add_on_face_info_callback", + [ + (cg.int16, "status"), + (cg.int16, "left"), + (cg.int16, "top"), + (cg.int16, "right"), + (cg.int16, "bottom"), + (cg.int16, "yaw"), + (cg.int16, "pitch"), + (cg.int16, "roll"), + ], + ), + automation.CallbackAutomation( + CONF_ON_ENROLLMENT_DONE, + "add_on_enrollment_done_callback", + [(cg.int16, "face_id"), (cg.uint8, "direction")], + ), + automation.CallbackAutomation( + CONF_ON_ENROLLMENT_FAILED, + "add_on_enrollment_failed_callback", + [(cg.uint8, "error")], + ), +) + + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await uart.register_uart_device(var, config) - for conf in config.get(CONF_ON_FACE_SCAN_MATCHED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(cg.int16, "face_id"), (cg.std_string, "name")], conf - ) - - for conf in config.get(CONF_ON_FACE_SCAN_UNMATCHED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - - for conf in config.get(CONF_ON_FACE_SCAN_INVALID, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.uint8, "error")], conf) - - for conf in config.get(CONF_ON_FACE_INFO, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, - [ - (cg.int16, "status"), - (cg.int16, "left"), - (cg.int16, "top"), - (cg.int16, "right"), - (cg.int16, "bottom"), - (cg.int16, "yaw"), - (cg.int16, "pitch"), - (cg.int16, "roll"), - ], - conf, - ) - - for conf in config.get(CONF_ON_ENROLLMENT_DONE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(cg.int16, "face_id"), (cg.uint8, "direction")], conf - ) - - for conf in config.get(CONF_ON_ENROLLMENT_FAILED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.uint8, "error")], conf) + await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS) @automation.register_action( @@ -189,7 +131,7 @@ async def hlk_fm22x_enroll_to_code(config, action_id, template_arg, args): cv.maybe_simple_value( { cv.GenerateID(): cv.use_id(HlkFm22xComponent), - cv.Required(CONF_FACE_ID): cv.templatable(cv.uint16_t), + cv.Required(CONF_FACE_ID): cv.templatable(cv.int_range(min=0, max=32767)), }, key=CONF_FACE_ID, ), diff --git a/esphome/components/hlk_fm22x/hlk_fm22x.h b/esphome/components/hlk_fm22x/hlk_fm22x.h index 0ea4636281..fd8257b435 100644 --- a/esphome/components/hlk_fm22x/hlk_fm22x.h +++ b/esphome/components/hlk_fm22x/hlk_fm22x.h @@ -91,24 +91,23 @@ class HlkFm22xComponent : public PollingComponent, public uart::UARTDevice { void set_version_text_sensor(text_sensor::TextSensor *version_text_sensor) { this->version_text_sensor_ = version_text_sensor; } - void add_on_face_scan_matched_callback(std::function callback) { - this->face_scan_matched_callback_.add(std::move(callback)); + template void add_on_face_scan_matched_callback(F &&callback) { + this->face_scan_matched_callback_.add(std::forward(callback)); } - void add_on_face_scan_unmatched_callback(std::function callback) { - this->face_scan_unmatched_callback_.add(std::move(callback)); + template void add_on_face_scan_unmatched_callback(F &&callback) { + this->face_scan_unmatched_callback_.add(std::forward(callback)); } - void add_on_face_scan_invalid_callback(std::function callback) { - this->face_scan_invalid_callback_.add(std::move(callback)); + template void add_on_face_scan_invalid_callback(F &&callback) { + this->face_scan_invalid_callback_.add(std::forward(callback)); } - void add_on_face_info_callback( - std::function callback) { - this->face_info_callback_.add(std::move(callback)); + template void add_on_face_info_callback(F &&callback) { + this->face_info_callback_.add(std::forward(callback)); } - void add_on_enrollment_done_callback(std::function callback) { - this->enrollment_done_callback_.add(std::move(callback)); + template void add_on_enrollment_done_callback(F &&callback) { + this->enrollment_done_callback_.add(std::forward(callback)); } - void add_on_enrollment_failed_callback(std::function callback) { - this->enrollment_failed_callback_.add(std::move(callback)); + template void add_on_enrollment_failed_callback(F &&callback) { + this->enrollment_failed_callback_.add(std::forward(callback)); } void enroll_face(const std::string &name, HlkFm22xFaceDirection direction); @@ -142,52 +141,6 @@ class HlkFm22xComponent : public PollingComponent, public uart::UARTDevice { CallbackManager enrollment_failed_callback_; }; -class FaceScanMatchedTrigger : public Trigger { - public: - explicit FaceScanMatchedTrigger(HlkFm22xComponent *parent) { - parent->add_on_face_scan_matched_callback( - [this](int16_t face_id, const std::string &name) { this->trigger(face_id, name); }); - } -}; - -class FaceScanUnmatchedTrigger : public Trigger<> { - public: - explicit FaceScanUnmatchedTrigger(HlkFm22xComponent *parent) { - parent->add_on_face_scan_unmatched_callback([this]() { this->trigger(); }); - } -}; - -class FaceScanInvalidTrigger : public Trigger { - public: - explicit FaceScanInvalidTrigger(HlkFm22xComponent *parent) { - parent->add_on_face_scan_invalid_callback([this](uint8_t error) { this->trigger(error); }); - } -}; - -class FaceInfoTrigger : public Trigger { - public: - explicit FaceInfoTrigger(HlkFm22xComponent *parent) { - parent->add_on_face_info_callback( - [this](int16_t status, int16_t left, int16_t top, int16_t right, int16_t bottom, int16_t yaw, int16_t pitch, - int16_t roll) { this->trigger(status, left, top, right, bottom, yaw, pitch, roll); }); - } -}; - -class EnrollmentDoneTrigger : public Trigger { - public: - explicit EnrollmentDoneTrigger(HlkFm22xComponent *parent) { - parent->add_on_enrollment_done_callback( - [this](int16_t face_id, uint8_t direction) { this->trigger(face_id, direction); }); - } -}; - -class EnrollmentFailedTrigger : public Trigger { - public: - explicit EnrollmentFailedTrigger(HlkFm22xComponent *parent) { - parent->add_on_enrollment_failed_callback([this](uint8_t error) { this->trigger(error); }); - } -}; - template class EnrollmentAction : public Action, public Parented { public: TEMPLATABLE_VALUE(std::string, name) diff --git a/esphome/components/hlw8012/hlw8012.cpp b/esphome/components/hlw8012/hlw8012.cpp index d0fd697d8f..22f292e47e 100644 --- a/esphome/components/hlw8012/hlw8012.cpp +++ b/esphome/components/hlw8012/hlw8012.cpp @@ -73,13 +73,13 @@ void HLW8012Component::update() { // Only read cf1 after one cycle. Apparently it's quite unstable after being changed. if (this->current_mode_) { float current = cf1_hz * this->current_multiplier_; - ESP_LOGD(TAG, "Got power=%.1fW, current=%.1fA", power, current); + ESP_LOGV(TAG, "Got power=%.1fW, current=%.1fA", power, current); if (this->current_sensor_ != nullptr) { this->current_sensor_->publish_state(current); } } else { float voltage = cf1_hz * this->voltage_multiplier_; - ESP_LOGD(TAG, "Got power=%.1fW, voltage=%.1fV", power, voltage); + ESP_LOGV(TAG, "Got power=%.1fW, voltage=%.1fV", power, voltage); if (this->voltage_sensor_ != nullptr) { this->voltage_sensor_->publish_state(voltage); } diff --git a/esphome/components/hlw8032/sensor.py b/esphome/components/hlw8032/sensor.py index 96800e46f4..846c9a398b 100644 --- a/esphome/components/hlw8032/sensor.py +++ b/esphome/components/hlw8032/sensor.py @@ -27,42 +27,46 @@ DEPENDENCIES = ["uart"] hlw8032_ns = cg.esphome_ns.namespace("hlw8032") HLW8032Component = hlw8032_ns.class_("HLW8032Component", cg.Component, uart.UARTDevice) -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(HLW8032Component), - cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( - unit_of_measurement=UNIT_VOLT, - accuracy_decimals=1, - device_class=DEVICE_CLASS_VOLTAGE, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_CURRENT): sensor.sensor_schema( - unit_of_measurement=UNIT_AMPERE, - accuracy_decimals=2, - device_class=DEVICE_CLASS_CURRENT, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_POWER): sensor.sensor_schema( - unit_of_measurement=UNIT_WATT, - accuracy_decimals=1, - device_class=DEVICE_CLASS_POWER, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema( - unit_of_measurement=UNIT_VOLT_AMPS, - accuracy_decimals=1, - device_class=DEVICE_CLASS_APPARENT_POWER, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema( - accuracy_decimals=2, - device_class=DEVICE_CLASS_POWER_FACTOR, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_CURRENT_RESISTOR, default=0.001): cv.resistance, - cv.Optional(CONF_VOLTAGE_DIVIDER, default=1.720): cv.positive_float, - } -).extend(uart.UART_DEVICE_SCHEMA) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(HLW8032Component), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT_AMPS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema( + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER_FACTOR, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CURRENT_RESISTOR, default=0.001): cv.resistance, + cv.Optional(CONF_VOLTAGE_DIVIDER, default=1.720): cv.positive_float, + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( "hlw8032", baud_rate=4800, require_rx=True, data_bits=8, parity="EVEN" diff --git a/esphome/components/hmac_sha256/hmac_sha256.cpp b/esphome/components/hmac_sha256/hmac_sha256.cpp index 2146e961bc..c113cb48a6 100644 --- a/esphome/components/hmac_sha256/hmac_sha256.cpp +++ b/esphome/components/hmac_sha256/hmac_sha256.cpp @@ -7,7 +7,55 @@ namespace esphome::hmac_sha256 { constexpr size_t SHA256_DIGEST_SIZE = 32; -#if defined(USE_ESP32) || defined(USE_LIBRETINY) +#if defined(USE_HMAC_SHA256_PSA) + +// ESP-IDF 6.0 ships mbedtls 4.0 which removed the legacy mbedtls_md HMAC API. +// Use the PSA Crypto MAC API instead. + +HmacSHA256::~HmacSHA256() { + psa_mac_abort(&this->op_); + psa_destroy_key(this->key_id_); +} + +void HmacSHA256::init(const uint8_t *key, size_t len) { + psa_mac_abort(&this->op_); + psa_destroy_key(this->key_id_); + + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + psa_set_key_type(&attributes, PSA_KEY_TYPE_HMAC); + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_SIGN_MESSAGE); + psa_set_key_algorithm(&attributes, PSA_ALG_HMAC(PSA_ALG_SHA_256)); + psa_import_key(&attributes, key, len, &this->key_id_); + + this->op_ = PSA_MAC_OPERATION_INIT; + psa_mac_sign_setup(&this->op_, this->key_id_, PSA_ALG_HMAC(PSA_ALG_SHA_256)); +} + +void HmacSHA256::add(const uint8_t *data, size_t len) { psa_mac_update(&this->op_, data, len); } + +void HmacSHA256::calculate() { + size_t mac_length; + psa_mac_sign_finish(&this->op_, this->digest_, sizeof(this->digest_), &mac_length); +} + +void HmacSHA256::get_bytes(uint8_t *output) { memcpy(output, this->digest_, SHA256_DIGEST_SIZE); } + +void HmacSHA256::get_hex(char *output) { + format_hex_to(output, SHA256_DIGEST_SIZE * 2 + 1, this->digest_, SHA256_DIGEST_SIZE); +} + +bool HmacSHA256::equals_bytes(const uint8_t *expected) { + return memcmp(this->digest_, expected, SHA256_DIGEST_SIZE) == 0; +} + +bool HmacSHA256::equals_hex(const char *expected) { + char hex_output[SHA256_DIGEST_SIZE * 2 + 1]; + this->get_hex(hex_output); + hex_output[SHA256_DIGEST_SIZE * 2] = '\0'; + return strncmp(hex_output, expected, SHA256_DIGEST_SIZE * 2) == 0; +} + +#elif defined(USE_HMAC_SHA256_MBEDTLS) HmacSHA256::~HmacSHA256() { mbedtls_md_free(&this->ctx_); } @@ -93,7 +141,7 @@ bool HmacSHA256::equals_bytes(const uint8_t *expected) { return this->ohash_.equ bool HmacSHA256::equals_hex(const char *expected) { return this->ohash_.equals_hex(expected); } -#endif // USE_ESP32 || USE_LIBRETINY +#endif // USE_HMAC_SHA256_PSA / USE_HMAC_SHA256_MBEDTLS } // namespace esphome::hmac_sha256 #endif diff --git a/esphome/components/hmac_sha256/hmac_sha256.h b/esphome/components/hmac_sha256/hmac_sha256.h index 85622cac46..22129b1182 100644 --- a/esphome/components/hmac_sha256/hmac_sha256.h +++ b/esphome/components/hmac_sha256/hmac_sha256.h @@ -5,7 +5,19 @@ #include -#if defined(USE_ESP32) || defined(USE_LIBRETINY) +#if defined(USE_ESP32) +#include +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) +// mbedtls 4.0 (IDF 6.0) removed the legacy mbedtls_md HMAC API. +// Use the PSA Crypto MAC API instead. +#define USE_HMAC_SHA256_PSA +#include +#else +#define USE_HMAC_SHA256_MBEDTLS +#include "mbedtls/md.h" +#endif +#elif defined(USE_LIBRETINY) +#define USE_HMAC_SHA256_MBEDTLS #include "mbedtls/md.h" #else #include "esphome/components/sha256/sha256.h" @@ -45,7 +57,12 @@ class HmacSHA256 { bool equals_hex(const char *expected); protected: -#if defined(USE_ESP32) || defined(USE_LIBRETINY) +#if defined(USE_HMAC_SHA256_PSA) + static constexpr size_t SHA256_DIGEST_SIZE = 32; + psa_mac_operation_t op_ = PSA_MAC_OPERATION_INIT; + mbedtls_svc_key_id_t key_id_ = MBEDTLS_SVC_KEY_ID_INIT; + uint8_t digest_[SHA256_DIGEST_SIZE]{}; +#elif defined(USE_HMAC_SHA256_MBEDTLS) static constexpr size_t SHA256_DIGEST_SIZE = 32; mbedtls_md_context_t ctx_{}; uint8_t digest_[SHA256_DIGEST_SIZE]{}; diff --git a/esphome/components/hmc5883l/sensor.py b/esphome/components/hmc5883l/sensor.py index 96d0313008..cf3c594f36 100644 --- a/esphome/components/hmc5883l/sensor.py +++ b/esphome/components/hmc5883l/sensor.py @@ -87,6 +87,7 @@ heading_schema = sensor.sensor_schema( unit_of_measurement=UNIT_DEGREES, icon=ICON_SCREEN_ROTATION, accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, ) CONFIG_SCHEMA = ( diff --git a/esphome/components/homeassistant/number/homeassistant_number.cpp b/esphome/components/homeassistant/number/homeassistant_number.cpp index 00ea88ff16..da802b7fe9 100644 --- a/esphome/components/homeassistant/number/homeassistant_number.cpp +++ b/esphome/components/homeassistant/number/homeassistant_number.cpp @@ -55,15 +55,15 @@ void HomeassistantNumber::step_retrieved_(StringRef step) { } void HomeassistantNumber::setup() { - api::global_api_server->subscribe_home_assistant_state( - this->entity_id_, nullptr, std::bind(&HomeassistantNumber::state_changed_, this, std::placeholders::_1)); + api::global_api_server->subscribe_home_assistant_state(this->entity_id_, nullptr, + [this](StringRef state) { this->state_changed_(state); }); - api::global_api_server->get_home_assistant_state( - this->entity_id_, "min", std::bind(&HomeassistantNumber::min_retrieved_, this, std::placeholders::_1)); - api::global_api_server->get_home_assistant_state( - this->entity_id_, "max", std::bind(&HomeassistantNumber::max_retrieved_, this, std::placeholders::_1)); - api::global_api_server->get_home_assistant_state( - this->entity_id_, "step", std::bind(&HomeassistantNumber::step_retrieved_, this, std::placeholders::_1)); + api::global_api_server->get_home_assistant_state(this->entity_id_, "min", + [this](StringRef min) { this->min_retrieved_(min); }); + api::global_api_server->get_home_assistant_state(this->entity_id_, "max", + [this](StringRef max) { this->max_retrieved_(max); }); + api::global_api_server->get_home_assistant_state(this->entity_id_, "step", + [this](StringRef step) { this->step_retrieved_(step); }); } void HomeassistantNumber::dump_config() { diff --git a/esphome/components/homeassistant/time/homeassistant_time.h b/esphome/components/homeassistant/time/homeassistant_time.h index 7b5842fefd..455ded2022 100644 --- a/esphome/components/homeassistant/time/homeassistant_time.h +++ b/esphome/components/homeassistant/time/homeassistant_time.h @@ -7,7 +7,7 @@ namespace esphome { namespace homeassistant { -class HomeassistantTime : public time::RealTimeClock { +class HomeassistantTime final : public time::RealTimeClock { public: void setup() override; void update() override; diff --git a/esphome/components/host/core.cpp b/esphome/components/host/core.cpp index d5c61ec986..0ade4274fe 100644 --- a/esphome/components/host/core.cpp +++ b/esphome/components/host/core.cpp @@ -5,11 +5,17 @@ #include "esphome/core/helpers.h" #include "preferences.h" +#include #include #include #include #include +namespace { +volatile sig_atomic_t s_signal_received = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +void signal_handler(int signal) { s_signal_received = signal; } +} // namespace + namespace esphome { void HOT yield() { ::sched_yield(); } @@ -58,9 +64,6 @@ void HOT arch_feed_wdt() { // pass } -uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; } -const char *progmem_read_ptr(const char *const *addr) { return *addr; } -uint16_t progmem_read_uint16(const uint16_t *addr) { return *addr; } uint32_t arch_get_cpu_cycle_count() { struct timespec spec; clock_gettime(CLOCK_MONOTONIC, &spec); @@ -75,11 +78,17 @@ uint32_t arch_get_cpu_freq_hz() { return 1000000000U; } void setup(); void loop(); int main() { + // Install signal handlers for graceful shutdown (flushes preferences to disk) + std::signal(SIGINT, signal_handler); + std::signal(SIGTERM, signal_handler); + esphome::host::setup_preferences(); setup(); - while (true) { + while (s_signal_received == 0) { loop(); } + esphome::App.run_safe_shutdown_hooks(); + return 0; } #endif // USE_HOST diff --git a/esphome/components/host/helpers.cpp b/esphome/components/host/helpers.cpp index fdad4f5cb6..7e8849b3e1 100644 --- a/esphome/components/host/helpers.cpp +++ b/esphome/components/host/helpers.cpp @@ -8,8 +8,6 @@ #include #endif #include -#include -#include #include "esphome/core/defines.h" #include "esphome/core/log.h" @@ -18,13 +16,6 @@ namespace esphome { static const char *const TAG = "helpers.host"; -uint32_t random_uint32() { - std::random_device dev; - std::mt19937 rng(dev()); - std::uniform_int_distribution dist(0, std::numeric_limits::max()); - return dist(rng); -} - bool random_bytes(uint8_t *data, size_t len) { FILE *fp = fopen("/dev/urandom", "r"); if (fp == nullptr) { diff --git a/esphome/components/host/preference_backend.h b/esphome/components/host/preference_backend.h new file mode 100644 index 0000000000..68537cad28 --- /dev/null +++ b/esphome/components/host/preference_backend.h @@ -0,0 +1,29 @@ +#pragma once +#ifdef USE_HOST + +#include +#include + +namespace esphome::host { + +class HostPreferenceBackend final { + public: + explicit HostPreferenceBackend(uint32_t key) : key_(key) {} + + bool save(const uint8_t *data, size_t len); + bool load(uint8_t *data, size_t len); + + protected: + uint32_t key_{}; +}; + +class HostPreferences; +HostPreferences *get_preferences(); + +} // namespace esphome::host + +namespace esphome { +using PreferenceBackend = host::HostPreferenceBackend; +} // namespace esphome + +#endif // USE_HOST diff --git a/esphome/components/host/preferences.cpp b/esphome/components/host/preferences.cpp index 275c202e3e..c0be270062 100644 --- a/esphome/components/host/preferences.cpp +++ b/esphome/components/host/preferences.cpp @@ -6,11 +6,10 @@ #include "esphome/core/application.h" #include "esphome/core/log.h" -namespace esphome { -namespace host { +namespace esphome::host { namespace fs = std::filesystem; -static const char *const TAG = "host.preferences"; +static const char *const TAG = "preferences"; void HostPreferences::setup_() { if (this->setup_complete_) @@ -77,6 +76,8 @@ ESPPreferenceObject HostPreferences::make_preference(size_t length, uint32_t typ static HostPreferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +HostPreferences *get_preferences() { return &s_preferences; } + void setup_preferences() { host_preferences = &s_preferences; global_preferences = &s_preferences; @@ -88,9 +89,11 @@ bool HostPreferenceBackend::save(const uint8_t *data, size_t len) { bool HostPreferenceBackend::load(uint8_t *data, size_t len) { return host_preferences->load(this->key_, data, len); } -HostPreferences *host_preferences; -} // namespace host +HostPreferences *host_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +} // namespace esphome::host + +namespace esphome { ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace esphome diff --git a/esphome/components/host/preferences.h b/esphome/components/host/preferences.h index 6b2e7eb8f9..25858799ff 100644 --- a/esphome/components/host/preferences.h +++ b/esphome/components/host/preferences.h @@ -1,33 +1,22 @@ #pragma once - #ifdef USE_HOST -#include "esphome/core/preferences.h" +#include "esphome/core/preference_backend.h" +#include #include +#include +#include -namespace esphome { -namespace host { +namespace esphome::host { -class HostPreferenceBackend : public ESPPreferenceBackend { +class HostPreferences final : public PreferencesMixin { public: - explicit HostPreferenceBackend(uint32_t key) { this->key_ = key; } + using PreferencesMixin::make_preference; + bool sync(); + bool reset(); - bool save(const uint8_t *data, size_t len) override; - bool load(uint8_t *data, size_t len) override; - - protected: - uint32_t key_{}; -}; - -class HostPreferences : public ESPPreferences { - public: - bool sync() override; - bool reset() override; - - ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override; - ESPPreferenceObject make_preference(size_t length, uint32_t type) override { - return make_preference(length, type, false); - } + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash); + ESPPreferenceObject make_preference(size_t length, uint32_t type) { return make_preference(length, type, false); } bool save(uint32_t key, const uint8_t *data, size_t len) { if (len > 255) @@ -58,10 +47,12 @@ class HostPreferences : public ESPPreferences { std::string filename_{}; std::map> data{}; }; + void setup_preferences(); extern HostPreferences *host_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -} // namespace host -} // namespace esphome +} // namespace esphome::host + +DECLARE_PREFERENCE_ALIASES(esphome::host::HostPreferences) #endif // USE_HOST diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index 416432cfc4..90879c459e 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -302,11 +302,13 @@ async def http_request_action_to_code(config, action_id, template_arg, args): template_ = await cg.templatable(config[CONF_URL], args, cg.std_string) cg.add(var.set_url(template_)) - cg.add(var.set_method(config[CONF_METHOD])) + template_ = await cg.templatable(config[CONF_METHOD], args, cg.const_char_ptr) + cg.add(var.set_method(template_)) capture_response = config[CONF_CAPTURE_RESPONSE] if capture_response: - cg.add(var.set_capture_response(capture_response)) + template_ = await cg.templatable(capture_response, args, cg.bool_) + cg.add(var.set_capture_response(template_)) cg.add_define("USE_HTTP_REQUEST_RESPONSE") cg.add(var.set_max_response_buffer_size(config[CONF_MAX_RESPONSE_BUFFER_SIZE])) diff --git a/esphome/components/http_request/http_request.cpp b/esphome/components/http_request/http_request.cpp index 6590d2018e..2c74638f12 100644 --- a/esphome/components/http_request/http_request.cpp +++ b/esphome/components/http_request/http_request.cpp @@ -11,7 +11,7 @@ static const char *const TAG = "http_request"; void HttpRequestComponent::dump_config() { ESP_LOGCONFIG(TAG, "HTTP Request:\n" - " Timeout: %ums\n" + " Timeout: %" PRIu32 "ms\n" " User-Agent: %s\n" " Follow redirects: %s\n" " Redirect limit: %d", diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index 8bdea470b5..ae73983bab 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -457,7 +457,7 @@ template class HttpRequestSendAction : public Action { #endif void init_request_headers(size_t count) { this->request_headers_.init(count); } - void add_request_header(const char *key, TemplatableValue value) { + void add_request_header(const char *key, TemplatableFn value) { this->request_headers_.push_back({key, value}); } @@ -487,12 +487,10 @@ template class HttpRequestSendAction : public Action { body = this->body_.value(x...); } if (!this->json_.empty()) { - auto f = std::bind(&HttpRequestSendAction::encode_json_, this, x..., std::placeholders::_1); - body = json::build_json(f); + body = json::build_json([this, x...](JsonObject root) { this->encode_json_(x..., root); }); } if (this->json_func_ != nullptr) { - auto f = std::bind(&HttpRequestSendAction::encode_json_func_, this, x..., std::placeholders::_1); - body = json::build_json(f); + body = json::build_json([this, x...](JsonObject root) { this->json_func_(x..., root); }); } std::vector
request_headers; request_headers.reserve(this->request_headers_.size()); @@ -561,9 +559,8 @@ template class HttpRequestSendAction : public Action { root[item.first] = val.value(x...); } } - void encode_json_func_(Ts... x, JsonObject root) { this->json_func_(x..., root); } HttpRequestComponent *parent_; - FixedVector>> request_headers_{}; + FixedVector>> request_headers_{}; std::vector lower_case_collect_headers_{"content-type", "content-length"}; FixedVector>> json_{}; std::function json_func_{nullptr}; diff --git a/esphome/components/http_request/http_request_idf.cpp b/esphome/components/http_request/http_request_idf.cpp index dda61e2400..30f53eecdc 100644 --- a/esphome/components/http_request/http_request_idf.cpp +++ b/esphome/components/http_request/http_request_idf.cpp @@ -17,6 +17,7 @@ namespace esphome::http_request { static const char *const TAG = "http_request.idf"; +static constexpr uint32_t ERROR_DURATION_MS = 1000; struct UserData { const std::vector &lower_case_collect_headers; @@ -57,7 +58,7 @@ std::shared_ptr HttpRequestIDF::perform(const std::string &url, c const std::vector
&request_headers, const std::vector &lower_case_collect_headers) { if (!network::is_connected()) { - this->status_momentary_error("failed", 1000); + this->status_momentary_error("failed", ERROR_DURATION_MS); ESP_LOGE(TAG, "HTTP Request failed; Not connected to network"); return nullptr; } @@ -74,7 +75,7 @@ std::shared_ptr HttpRequestIDF::perform(const std::string &url, c } else if (method == "PATCH") { method_idf = HTTP_METHOD_PATCH; } else { - this->status_momentary_error("failed", 1000); + this->status_momentary_error("failed", ERROR_DURATION_MS); ESP_LOGE(TAG, "HTTP Request failed; Unsupported method"); return nullptr; } @@ -112,6 +113,11 @@ std::shared_ptr HttpRequestIDF::perform(const std::string &url, c config.event_handler = http_event_handler; esp_http_client_handle_t client = esp_http_client_init(&config); + if (client == nullptr) { + this->status_momentary_error("failed", ERROR_DURATION_MS); + ESP_LOGE(TAG, "HTTP Request failed; client could not be initialized"); + return nullptr; + } std::shared_ptr container = std::make_shared(client); container->set_parent(this); @@ -129,7 +135,7 @@ std::shared_ptr HttpRequestIDF::perform(const std::string &url, c esp_err_t err = esp_http_client_open(client, body_len); if (err != ESP_OK) { - this->status_momentary_error("failed", 1000); + this->status_momentary_error("failed", ERROR_DURATION_MS); ESP_LOGE(TAG, "HTTP Request failed: %s", esp_err_to_name(err)); esp_http_client_cleanup(client); return nullptr; @@ -151,7 +157,7 @@ std::shared_ptr HttpRequestIDF::perform(const std::string &url, c } if (err != ESP_OK) { - this->status_momentary_error("failed", 1000); + this->status_momentary_error("failed", ERROR_DURATION_MS); ESP_LOGE(TAG, "HTTP Request failed: %s", esp_err_to_name(err)); esp_http_client_cleanup(client); return nullptr; @@ -176,7 +182,7 @@ std::shared_ptr HttpRequestIDF::perform(const std::string &url, c err = esp_http_client_set_redirection(client); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_http_client_set_redirection failed: %s", esp_err_to_name(err)); - this->status_momentary_error("failed", 1000); + this->status_momentary_error("failed", ERROR_DURATION_MS); esp_http_client_cleanup(client); return nullptr; } @@ -189,7 +195,7 @@ std::shared_ptr HttpRequestIDF::perform(const std::string &url, c err = esp_http_client_open(client, 0); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_http_client_open failed: %s", esp_err_to_name(err)); - this->status_momentary_error("failed", 1000); + this->status_momentary_error("failed", ERROR_DURATION_MS); esp_http_client_cleanup(client); return nullptr; } @@ -214,7 +220,7 @@ std::shared_ptr HttpRequestIDF::perform(const std::string &url, c } ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), container->status_code); - this->status_momentary_error("failed", 1000); + this->status_momentary_error("failed", ERROR_DURATION_MS); return container; } diff --git a/esphome/components/htu21d/sensor.py b/esphome/components/htu21d/sensor.py index 92c088a22f..8808dc70f5 100644 --- a/esphome/components/htu21d/sensor.py +++ b/esphome/components/htu21d/sensor.py @@ -98,7 +98,7 @@ async def to_code(config): async def set_heater_level_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) - level_ = await cg.templatable(config[CONF_LEVEL], args, int) + level_ = await cg.templatable(config[CONF_LEVEL], args, cg.uint8) cg.add(var.set_level(level_)) return var @@ -118,6 +118,6 @@ async def set_heater_level_to_code(config, action_id, template_arg, args): async def set_heater_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) - status_ = await cg.templatable(config[CONF_LEVEL], args, bool) + status_ = await cg.templatable(config[CONF_STATUS], args, cg.bool_) cg.add(var.set_status(status_)) return var diff --git a/esphome/components/hub75/display.py b/esphome/components/hub75/display.py index 4f62ce7a94..0d1b87941d 100644 --- a/esphome/components/hub75/display.py +++ b/esphome/components/hub75/display.py @@ -128,6 +128,7 @@ SCAN_WIRINGS = { "STANDARD_TWO_SCAN": Hub75ScanWiring.STANDARD_TWO_SCAN, "SCAN_1_4_16PX_HIGH": Hub75ScanWiring.SCAN_1_4_16PX_HIGH, "SCAN_1_8_32PX_HIGH": Hub75ScanWiring.SCAN_1_8_32PX_HIGH, + "SCAN_1_8_32PX_FULL": Hub75ScanWiring.SCAN_1_8_32PX_FULL, "SCAN_1_8_40PX_HIGH": Hub75ScanWiring.SCAN_1_8_40PX_HIGH, "SCAN_1_8_64PX_HIGH": Hub75ScanWiring.SCAN_1_8_64PX_HIGH, } @@ -587,7 +588,7 @@ def _build_config_struct( async def to_code(config: ConfigType) -> None: add_idf_component( name="esphome/esp-hub75", - ref="0.3.2", + ref="0.3.5", ) # Set compile-time configuration via build flags (so external library sees them) diff --git a/esphome/components/hub75/hub75.cpp b/esphome/components/hub75/hub75.cpp index cf8661b2b3..ba652d427d 100644 --- a/esphome/components/hub75/hub75.cpp +++ b/esphome/components/hub75/hub75.cpp @@ -1,6 +1,8 @@ #include "hub75_component.h" #include "esphome/core/application.h" +#include + #ifdef USE_ESP32 namespace esphome::hub75 { @@ -58,7 +60,7 @@ void HUB75Display::dump_config() { config_.pins.oe, config_.pins.clk); ESP_LOGCONFIG(TAG, - " Clock Speed: %u MHz\n" + " Clock Speed: %" PRIu32 " MHz\n" " Latch Blanking: %i\n" " Clock Phase: %s\n" " Min Refresh Rate: %i Hz\n" diff --git a/esphome/components/hydreon_rgxx/sensor.py b/esphome/components/hydreon_rgxx/sensor.py index f270b72e24..fdb606182f 100644 --- a/esphome/components/hydreon_rgxx/sensor.py +++ b/esphome/components/hydreon_rgxx/sensor.py @@ -9,6 +9,7 @@ from esphome.const import ( CONF_TEMPERATURE, DEVICE_CLASS_PRECIPITATION, DEVICE_CLASS_PRECIPITATION_INTENSITY, + DEVICE_CLASS_TEMPERATURE, ICON_THERMOMETER, STATE_CLASS_MEASUREMENT, STATE_CLASS_TOTAL_INCREASING, @@ -117,6 +118,7 @@ CONFIG_SCHEMA = cv.All( unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=0, icon=ICON_THERMOMETER, + device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_DISABLE_LED): cv.boolean, diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index eaefabf75b..4aca4f0fae 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -185,7 +185,7 @@ ErrorCode IDFI2CBus::write_readv(uint8_t address, const uint8_t *write_buffer, s jobs[num_jobs++].command = I2C_MASTER_CMD_STOP; ESP_LOGV(TAG, "Sending %zu jobs", num_jobs); esp_err_t err = i2c_master_execute_defined_operations(this->dev_, jobs, num_jobs, 100); - if (err == ESP_ERR_INVALID_STATE) { + if (err == ESP_ERR_INVALID_STATE || err == ESP_ERR_INVALID_RESPONSE) { ESP_LOGV(TAG, "TX to %02X failed: not acked", address); return ERROR_NOT_ACKNOWLEDGED; } else if (err == ESP_ERR_TIMEOUT) { diff --git a/esphome/components/i2s_audio/__init__.py b/esphome/components/i2s_audio/__init__.py index 65b09b93f6..ffa63f5ee8 100644 --- a/esphome/components/i2s_audio/__init__.py +++ b/esphome/components/i2s_audio/__init__.py @@ -1,3 +1,5 @@ +from dataclasses import dataclass, field + from esphome import pins import esphome.codegen as cg from esphome.components.esp32 import ( @@ -26,6 +28,9 @@ CODEOWNERS = ["@jesserockz"] DEPENDENCIES = ["esp32"] MULTI_CONF = True +CONF_PDM = "pdm" +CONF_ADC_TYPE = "adc_type" + CONF_I2S_DOUT_PIN = "i2s_dout_pin" CONF_I2S_DIN_PIN = "i2s_din_pin" CONF_I2S_MCLK_PIN = "i2s_mclk_pin" @@ -48,8 +53,6 @@ CONF_RIGHT = "right" CONF_STEREO = "stereo" CONF_BOTH = "both" -CONF_USE_LEGACY = "use_legacy" - i2s_audio_ns = cg.esphome_ns.namespace("i2s_audio") I2SAudioComponent = i2s_audio_ns.class_("I2SAudioComponent", cg.Component) I2SAudioBase = i2s_audio_ns.class_( @@ -149,20 +152,6 @@ def validate_mclk_divisible_by_3(config): return config -# Key for storing legacy driver setting in CORE.data -I2S_USE_LEGACY_DRIVER_KEY = "i2s_use_legacy_driver" - - -def _get_use_legacy_driver(): - """Get the legacy driver setting from CORE.data.""" - return CORE.data.get(I2S_USE_LEGACY_DRIVER_KEY) - - -def _set_use_legacy_driver(value: bool) -> None: - """Set the legacy driver setting in CORE.data.""" - CORE.data[I2S_USE_LEGACY_DRIVER_KEY] = value - - def i2s_audio_component_schema( class_: MockObjClass, *, @@ -187,10 +176,6 @@ def i2s_audio_component_schema( *I2S_MODE_OPTIONS, lower=True ), cv.Optional(CONF_USE_APLL, default=False): cv.boolean, - cv.Optional(CONF_BITS_PER_CHANNEL, default="default"): cv.All( - cv.Any(cv.float_with_unit("bits", "bit"), "default"), - cv.one_of(*I2S_BITS_PER_CHANNEL), - ), cv.Optional(CONF_MCLK_MULTIPLE, default=256): cv.one_of(*I2S_MCLK_MULTIPLE), } ) @@ -198,62 +183,82 @@ def i2s_audio_component_schema( async def register_i2s_audio_component(var, config): await cg.register_parented(var, config[CONF_I2S_AUDIO_ID]) - if use_legacy(): - cg.add(var.set_i2s_mode(I2S_MODE_OPTIONS[config[CONF_I2S_MODE]])) - cg.add(var.set_channel(I2S_CHANNELS[config[CONF_CHANNEL]])) - cg.add( - var.set_bits_per_sample(I2S_BITS_PER_SAMPLE[config[CONF_BITS_PER_SAMPLE]]) - ) - cg.add( - var.set_bits_per_channel( - I2S_BITS_PER_CHANNEL[config[CONF_BITS_PER_CHANNEL]] - ) - ) - else: - cg.add(var.set_i2s_role(I2S_ROLE_OPTIONS[config[CONF_I2S_MODE]])) - slot_mode = config[CONF_CHANNEL] - if slot_mode != CONF_STEREO: - slot_mode = CONF_MONO - slot_mask = config[CONF_CHANNEL] - if slot_mask not in [CONF_LEFT, CONF_RIGHT]: - slot_mask = CONF_BOTH - cg.add(var.set_slot_mode(I2S_SLOT_MODE[slot_mode])) - cg.add(var.set_std_slot_mask(I2S_STD_SLOT_MASK[slot_mask])) - cg.add(var.set_slot_bit_width(I2S_SLOT_BIT_WIDTH[config[CONF_BITS_PER_SAMPLE]])) + cg.add(var.set_i2s_role(I2S_ROLE_OPTIONS[config[CONF_I2S_MODE]])) + slot_mode = config[CONF_CHANNEL] + if slot_mode != CONF_STEREO: + slot_mode = CONF_MONO + slot_mask = config[CONF_CHANNEL] + if slot_mask not in [CONF_LEFT, CONF_RIGHT]: + slot_mask = CONF_BOTH + cg.add(var.set_slot_mode(I2S_SLOT_MODE[slot_mode])) + cg.add(var.set_std_slot_mask(I2S_STD_SLOT_MASK[slot_mask])) + cg.add(var.set_slot_bit_width(I2S_SLOT_BIT_WIDTH[config[CONF_BITS_PER_SAMPLE]])) cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE])) cg.add(var.set_use_apll(config[CONF_USE_APLL])) cg.add(var.set_mclk_multiple(I2S_MCLK_MULTIPLE[config[CONF_MCLK_MULTIPLE]])) -def validate_use_legacy(value): - if CONF_USE_LEGACY in value: - existing_value = _get_use_legacy_driver() - if (existing_value is not None) and (existing_value != value[CONF_USE_LEGACY]): - raise cv.Invalid( - f"All i2s_audio components must set {CONF_USE_LEGACY} to the same value." - ) - if (not value[CONF_USE_LEGACY]) and (CORE.using_arduino): - raise cv.Invalid("Arduino supports only the legacy i2s driver") - _set_use_legacy_driver(value[CONF_USE_LEGACY]) - elif CORE.using_arduino: - _set_use_legacy_driver(True) - return value - - -CONFIG_SCHEMA = cv.All( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(I2SAudioComponent), - cv.Required(CONF_I2S_LRCLK_PIN): pins.internal_gpio_output_pin_number, - cv.Optional(CONF_I2S_BCLK_PIN): pins.internal_gpio_output_pin_number, - cv.Optional(CONF_I2S_MCLK_PIN): pins.internal_gpio_output_pin_number, - cv.Optional(CONF_USE_LEGACY): cv.boolean, - }, - ), - validate_use_legacy, +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(I2SAudioComponent), + cv.Required(CONF_I2S_LRCLK_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_I2S_BCLK_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_I2S_MCLK_PIN): pins.internal_gpio_output_pin_number, + }, ) +@dataclass +class I2SAudioData: + """I2S audio component state stored in CORE.data.""" + + port_map: dict[str, int] = field(default_factory=dict) + + +def _get_data() -> I2SAudioData: + if CONF_I2S_AUDIO not in CORE.data: + CORE.data[CONF_I2S_AUDIO] = I2SAudioData() + return CORE.data[CONF_I2S_AUDIO] + + +def _assign_ports() -> None: + """Assign I2S port numbers, prioritizing instances with microphone children. + + Microphones (especially PDM) require port 0 on most ESP32 variants. + This runs once and stores the mapping in CORE.data. + """ + data = _get_data() + if data.port_map: + return + + full_config = fv.full_config.get() + i2s_configs = full_config[CONF_I2S_AUDIO] + + # Find i2s_audio instances with microphones that require port 0 + # (PDM and internal ADC only work on I2S port 0) + port0_parent_id = None + for mic_config in full_config.get("microphone", []): + if CONF_I2S_AUDIO_ID not in mic_config: + continue + if mic_config.get(CONF_PDM) or mic_config.get(CONF_ADC_TYPE) == "internal": + if port0_parent_id is not None: + raise cv.Invalid( + "Only one PDM/ADC microphone is supported (requires I2S port 0)" + ) + port0_parent_id = str(mic_config[CONF_I2S_AUDIO_ID]) + + # Assign ports: port 0 parent first (if any), rest get sequential + next_port = 0 + if port0_parent_id is not None: + data.port_map[port0_parent_id] = next_port + next_port += 1 + for config in i2s_configs: + config_id = str(config[CONF_ID]) + if config_id != port0_parent_id: + data.port_map[config_id] = next_port + next_port += 1 + + def _final_validate(_): i2s_audio_configs = fv.full_config.get()[CONF_I2S_AUDIO] variant = get_esp32_variant() @@ -263,10 +268,7 @@ def _final_validate(_): raise cv.Invalid( f"Only {I2S_PORTS[variant]} I2S audio ports are supported on {variant}" ) - - -def use_legacy(): - return _get_use_legacy_driver() + _assign_ports() FINAL_VALIDATE_SCHEMA = _final_validate @@ -276,14 +278,15 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) + # Assign I2S port from _final_validate computed mapping + data = _get_data() + if (port := data.port_map.get(str(config[CONF_ID]))) is None: + raise ValueError(f"No I2S port assigned for {config[CONF_ID]}") + cg.add(var.set_port(port)) + # Re-enable ESP-IDF's I2S driver (excluded by default to save compile time) include_builtin_idf_component("esp_driver_i2s") - if use_legacy(): - cg.add_define("USE_I2S_LEGACY") - # Legacy I2S API lives in the "driver" shim component (driver/i2s.h) - include_builtin_idf_component("driver") - # Helps avoid callbacks being skipped due to processor load add_idf_sdkconfig_option("CONFIG_I2S_ISR_IRAM_SAFE", True) diff --git a/esphome/components/i2s_audio/i2s_audio.cpp b/esphome/components/i2s_audio/i2s_audio.cpp deleted file mode 100644 index 43064498cc..0000000000 --- a/esphome/components/i2s_audio/i2s_audio.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "i2s_audio.h" - -#ifdef USE_ESP32 - -#include "esphome/core/log.h" - -namespace esphome { -namespace i2s_audio { - -static const char *const TAG = "i2s_audio"; - -void I2SAudioComponent::setup() { - static i2s_port_t next_port_num = I2S_NUM_0; - if (next_port_num >= SOC_I2S_NUM) { - ESP_LOGE(TAG, "Too many components"); - this->mark_failed(); - return; - } - - this->port_ = next_port_num; - next_port_num = (i2s_port_t) (next_port_num + 1); -} - -} // namespace i2s_audio -} // namespace esphome - -#endif // USE_ESP32 diff --git a/esphome/components/i2s_audio/i2s_audio.h b/esphome/components/i2s_audio/i2s_audio.h index cfccf7e01f..5b260fa7ed 100644 --- a/esphome/components/i2s_audio/i2s_audio.h +++ b/esphome/components/i2s_audio/i2s_audio.h @@ -5,11 +5,8 @@ #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/core/helpers.h" -#ifdef USE_I2S_LEGACY -#include -#else +#include #include -#endif namespace esphome { namespace i2s_audio { @@ -18,33 +15,19 @@ class I2SAudioComponent; class I2SAudioBase : public Parented { public: -#ifdef USE_I2S_LEGACY - void set_i2s_mode(i2s_mode_t mode) { this->i2s_mode_ = mode; } - void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; } - void set_bits_per_sample(i2s_bits_per_sample_t bits_per_sample) { this->bits_per_sample_ = bits_per_sample; } - void set_bits_per_channel(i2s_bits_per_chan_t bits_per_channel) { this->bits_per_channel_ = bits_per_channel; } -#else void set_i2s_role(i2s_role_t role) { this->i2s_role_ = role; } void set_slot_mode(i2s_slot_mode_t slot_mode) { this->slot_mode_ = slot_mode; } void set_std_slot_mask(i2s_std_slot_mask_t std_slot_mask) { this->std_slot_mask_ = std_slot_mask; } void set_slot_bit_width(i2s_slot_bit_width_t slot_bit_width) { this->slot_bit_width_ = slot_bit_width; } -#endif void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; } void set_use_apll(uint32_t use_apll) { this->use_apll_ = use_apll; } void set_mclk_multiple(i2s_mclk_multiple_t mclk_multiple) { this->mclk_multiple_ = mclk_multiple; } protected: -#ifdef USE_I2S_LEGACY - i2s_mode_t i2s_mode_{}; - i2s_channel_fmt_t channel_; - i2s_bits_per_sample_t bits_per_sample_; - i2s_bits_per_chan_t bits_per_channel_; -#else i2s_role_t i2s_role_{}; i2s_slot_mode_t slot_mode_; i2s_std_slot_mask_t std_slot_mask_; i2s_slot_bit_width_t slot_bit_width_; -#endif uint32_t sample_rate_; bool use_apll_; i2s_mclk_multiple_t mclk_multiple_; @@ -56,19 +39,6 @@ class I2SAudioOut : public I2SAudioBase {}; class I2SAudioComponent : public Component { public: - void setup() override; - -#ifdef USE_I2S_LEGACY - i2s_pin_config_t get_pin_config() const { - return { - .mck_io_num = this->mclk_pin_, - .bck_io_num = this->bclk_pin_, - .ws_io_num = this->lrclk_pin_, - .data_out_num = I2S_PIN_NO_CHANGE, - .data_in_num = I2S_PIN_NO_CHANGE, - }; - } -#else i2s_std_gpio_config_t get_pin_config() const { return {.mclk = (gpio_num_t) this->mclk_pin_, .bclk = (gpio_num_t) this->bclk_pin_, @@ -81,32 +51,30 @@ class I2SAudioComponent : public Component { .ws_inv = false, }}; } -#endif void set_mclk_pin(int pin) { this->mclk_pin_ = pin; } void set_bclk_pin(int pin) { this->bclk_pin_ = pin; } void set_lrclk_pin(int pin) { this->lrclk_pin_ = pin; } + void set_port(int port) { this->port_ = port; } +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + int get_port() const { return this->port_; } +#else + i2s_port_t get_port() const { return static_cast(this->port_); } +#endif void lock() { this->lock_.lock(); } bool try_lock() { return this->lock_.try_lock(); } void unlock() { this->lock_.unlock(); } - i2s_port_t get_port() const { return this->port_; } - protected: Mutex lock_; I2SAudioIn *audio_in_{nullptr}; I2SAudioOut *audio_out_{nullptr}; -#ifdef USE_I2S_LEGACY - int mclk_pin_{I2S_PIN_NO_CHANGE}; - int bclk_pin_{I2S_PIN_NO_CHANGE}; -#else int mclk_pin_{I2S_GPIO_UNUSED}; int bclk_pin_{I2S_GPIO_UNUSED}; -#endif int lrclk_pin_; - i2s_port_t port_{}; + int port_{}; }; } // namespace i2s_audio diff --git a/esphome/components/i2s_audio/media_player/__init__.py b/esphome/components/i2s_audio/media_player/__init__.py index 426b211f47..b366d4fb05 100644 --- a/esphome/components/i2s_audio/media_player/__init__.py +++ b/esphome/components/i2s_audio/media_player/__init__.py @@ -1,121 +1,7 @@ -from esphome import pins -import esphome.codegen as cg -from esphome.components import esp32, media_player import esphome.config_validation as cv -from esphome.const import CONF_MODE -from .. import ( - CONF_I2S_AUDIO_ID, - CONF_I2S_DOUT_PIN, - CONF_LEFT, - CONF_MONO, - CONF_RIGHT, - CONF_STEREO, - I2SAudioComponent, - I2SAudioOut, - i2s_audio_ns, - use_legacy, +CONFIG_SCHEMA = cv.invalid( + "The I2S audio media player has been removed. " + "Use the speaker media player component instead. " + "See https://esphome.io/components/media_player/speaker.html for details." ) - -CODEOWNERS = ["@jesserockz"] -DEPENDENCIES = ["i2s_audio"] - -I2SAudioMediaPlayer = i2s_audio_ns.class_( - "I2SAudioMediaPlayer", cg.Component, media_player.MediaPlayer, I2SAudioOut -) - -i2s_dac_mode_t = cg.global_ns.enum("i2s_dac_mode_t") - - -CONF_MUTE_PIN = "mute_pin" -CONF_AUDIO_ID = "audio_id" -CONF_DAC_TYPE = "dac_type" -CONF_I2S_COMM_FMT = "i2s_comm_fmt" - -INTERNAL_DAC_OPTIONS = { - CONF_LEFT: i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN, - CONF_RIGHT: i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN, - CONF_STEREO: i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN, -} - -EXTERNAL_DAC_OPTIONS = [CONF_MONO, CONF_STEREO] - -NO_INTERNAL_DAC_VARIANTS = [esp32.VARIANT_ESP32S2] - -I2C_COMM_FMT_OPTIONS = ["lsb", "msb"] - - -def validate_esp32_variant(config): - if config[CONF_DAC_TYPE] != "internal": - return config - variant = esp32.get_esp32_variant() - if variant in NO_INTERNAL_DAC_VARIANTS: - raise cv.Invalid(f"{variant} does not have an internal DAC") - return config - - -CONFIG_SCHEMA = cv.All( - cv.typed_schema( - { - "internal": media_player.media_player_schema(I2SAudioMediaPlayer) - .extend( - { - cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), - cv.Required(CONF_MODE): cv.enum(INTERNAL_DAC_OPTIONS, lower=True), - } - ) - .extend(cv.COMPONENT_SCHEMA), - "external": media_player.media_player_schema(I2SAudioMediaPlayer) - .extend( - { - cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), - cv.Required( - CONF_I2S_DOUT_PIN - ): pins.internal_gpio_output_pin_number, - cv.Optional(CONF_MUTE_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_MODE, default="mono"): cv.one_of( - *EXTERNAL_DAC_OPTIONS, lower=True - ), - cv.Optional(CONF_I2S_COMM_FMT, default="msb"): cv.one_of( - *I2C_COMM_FMT_OPTIONS, lower=True - ), - } - ) - .extend(cv.COMPONENT_SCHEMA), - }, - key=CONF_DAC_TYPE, - ), - cv.only_with_arduino, - validate_esp32_variant, -) - - -def _final_validate(_): - if not use_legacy(): - raise cv.Invalid("I2S media player is only compatible with legacy i2s driver") - - -FINAL_VALIDATE_SCHEMA = _final_validate - - -async def to_code(config): - var = await media_player.new_media_player(config) - await cg.register_component(var, config) - - await cg.register_parented(var, config[CONF_I2S_AUDIO_ID]) - - if config[CONF_DAC_TYPE] == "internal": - cg.add(var.set_internal_dac_mode(config[CONF_MODE])) - else: - cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN])) - if CONF_MUTE_PIN in config: - pin = await cg.gpio_pin_expression(config[CONF_MUTE_PIN]) - cg.add(var.set_mute_pin(pin)) - cg.add(var.set_external_dac_channels(2 if config[CONF_MODE] == "stereo" else 1)) - cg.add(var.set_i2s_comm_fmt_lsb(config[CONF_I2S_COMM_FMT] == "lsb")) - - cg.add_library("WiFi", None) - cg.add_library("NetworkClientSecure", None) - cg.add_library("HTTPClient", None) - cg.add_library("esphome/ESP32-audioI2S", "2.3.0") - cg.add_build_flag("-DAUDIO_NO_SD_FS") diff --git a/esphome/components/i2s_audio/media_player/i2s_audio_media_player.cpp b/esphome/components/i2s_audio/media_player/i2s_audio_media_player.cpp deleted file mode 100644 index 369c964a85..0000000000 --- a/esphome/components/i2s_audio/media_player/i2s_audio_media_player.cpp +++ /dev/null @@ -1,260 +0,0 @@ -#include "i2s_audio_media_player.h" - -#ifdef USE_ESP32_FRAMEWORK_ARDUINO - -#include "esphome/core/log.h" - -namespace esphome { -namespace i2s_audio { - -static const char *const TAG = "audio"; - -void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) { - media_player::MediaPlayerState play_state = media_player::MEDIA_PLAYER_STATE_PLAYING; - auto announcement = call.get_announcement(); - if (announcement.has_value()) { - play_state = *announcement ? media_player::MEDIA_PLAYER_STATE_ANNOUNCING : media_player::MEDIA_PLAYER_STATE_PLAYING; - } - auto media_url = call.get_media_url(); - if (media_url.has_value()) { - this->current_url_ = media_url; - if (this->i2s_state_ != I2S_STATE_STOPPED && this->audio_ != nullptr) { - if (this->audio_->isRunning()) { - this->audio_->stopSong(); - } - this->audio_->connecttohost(media_url->c_str()); - this->state = play_state; - } else { - this->start(); - } - } - - if (play_state == media_player::MEDIA_PLAYER_STATE_ANNOUNCING) { - this->is_announcement_ = true; - } - - auto vol = call.get_volume(); - if (vol.has_value()) { - this->volume = *vol; - this->set_volume_(volume); - this->unmute_(); - } - auto cmd = call.get_command(); - if (cmd.has_value()) { - switch (*cmd) { - case media_player::MEDIA_PLAYER_COMMAND_MUTE: - this->mute_(); - break; - case media_player::MEDIA_PLAYER_COMMAND_UNMUTE: - this->unmute_(); - break; - case media_player::MEDIA_PLAYER_COMMAND_VOLUME_UP: { - float new_volume = this->volume + 0.1f; - if (new_volume > 1.0f) - new_volume = 1.0f; - this->set_volume_(new_volume); - this->unmute_(); - break; - } - case media_player::MEDIA_PLAYER_COMMAND_VOLUME_DOWN: { - float new_volume = this->volume - 0.1f; - if (new_volume < 0.0f) - new_volume = 0.0f; - this->set_volume_(new_volume); - this->unmute_(); - break; - } - default: - break; - } - if (this->i2s_state_ != I2S_STATE_RUNNING) { - return; - } - switch (*cmd) { - case media_player::MEDIA_PLAYER_COMMAND_PLAY: - if (!this->audio_->isRunning()) - this->audio_->pauseResume(); - this->state = play_state; - break; - case media_player::MEDIA_PLAYER_COMMAND_PAUSE: - if (this->audio_->isRunning()) - this->audio_->pauseResume(); - this->state = media_player::MEDIA_PLAYER_STATE_PAUSED; - break; - case media_player::MEDIA_PLAYER_COMMAND_STOP: - this->stop(); - break; - case media_player::MEDIA_PLAYER_COMMAND_TOGGLE: - this->audio_->pauseResume(); - if (this->audio_->isRunning()) { - this->state = media_player::MEDIA_PLAYER_STATE_PLAYING; - } else { - this->state = media_player::MEDIA_PLAYER_STATE_PAUSED; - } - break; - default: - break; - } - } - this->publish_state(); -} - -void I2SAudioMediaPlayer::mute_() { - if (this->mute_pin_ != nullptr) { - this->mute_pin_->digital_write(true); - } else { - this->set_volume_(0.0f, false); - } - this->muted_ = true; -} -void I2SAudioMediaPlayer::unmute_() { - if (this->mute_pin_ != nullptr) { - this->mute_pin_->digital_write(false); - } else { - this->set_volume_(this->volume, false); - } - this->muted_ = false; -} -void I2SAudioMediaPlayer::set_volume_(float volume, bool publish) { - if (this->audio_ != nullptr) - this->audio_->setVolume(remap(volume, 0.0f, 1.0f, 0, 21)); - if (publish) - this->volume = volume; -} - -void I2SAudioMediaPlayer::setup() { this->state = media_player::MEDIA_PLAYER_STATE_IDLE; } - -void I2SAudioMediaPlayer::loop() { - switch (this->i2s_state_) { - case I2S_STATE_STARTING: - this->start_(); - break; - case I2S_STATE_RUNNING: - this->play_(); - break; - case I2S_STATE_STOPPING: - this->stop_(); - break; - case I2S_STATE_STOPPED: - break; - } -} - -void I2SAudioMediaPlayer::play_() { - this->audio_->loop(); - if ((this->state == media_player::MEDIA_PLAYER_STATE_PLAYING || - this->state == media_player::MEDIA_PLAYER_STATE_ANNOUNCING) && - !this->audio_->isRunning()) { - this->stop(); - } -} - -void I2SAudioMediaPlayer::start() { this->i2s_state_ = I2S_STATE_STARTING; } -void I2SAudioMediaPlayer::start_() { - if (!this->parent_->try_lock()) { - return; // Waiting for another i2s to return lock - } - -#if SOC_I2S_SUPPORTS_DAC - if (this->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) { - this->audio_ = make_unique