diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b752701920..4c9e8c58bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -945,13 +945,13 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} cache-key: ${{ needs.common.outputs.cache-key }} - name: Download target analysis JSON - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 with: name: memory-analysis-target path: ./memory-analysis continue-on-error: true - name: Download PR analysis JSON - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 with: name: memory-analysis-pr path: ./memory-analysis diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0cca4dcaf6..17a2616dff 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -171,7 +171,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Download digests - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 with: pattern: digests-* path: /tmp/digests diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index a34747a183..998913ecec 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -890,10 +890,10 @@ def final_validate(config): ) ) if advanced[CONF_EXECUTE_FROM_PSRAM]: - if config[CONF_VARIANT] != VARIANT_ESP32S3: + if config[CONF_VARIANT] not in {VARIANT_ESP32S3, VARIANT_ESP32P4}: errs.append( cv.Invalid( - f"'{CONF_EXECUTE_FROM_PSRAM}' is only supported on {VARIANT_ESP32S3} variant", + f"'{CONF_EXECUTE_FROM_PSRAM}' is not available on this esp32 variant", path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_EXECUTE_FROM_PSRAM], ) ) @@ -1627,8 +1627,13 @@ async def to_code(config): _configure_lwip_max_sockets(conf) if advanced[CONF_EXECUTE_FROM_PSRAM]: - add_idf_sdkconfig_option("CONFIG_SPIRAM_FETCH_INSTRUCTIONS", True) - add_idf_sdkconfig_option("CONFIG_SPIRAM_RODATA", True) + if variant == VARIANT_ESP32S3: + add_idf_sdkconfig_option("CONFIG_SPIRAM_FETCH_INSTRUCTIONS", True) + add_idf_sdkconfig_option("CONFIG_SPIRAM_RODATA", True) + elif variant == VARIANT_ESP32P4: + add_idf_sdkconfig_option("CONFIG_SPIRAM_XIP_FROM_PSRAM", True) + else: + raise ValueError("Unhandled ESP32 variant") # Apply LWIP core locking for better socket performance # This is already enabled by default in Arduino framework, where it provides diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 09570106df..f49069960b 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -405,12 +405,6 @@ void MQTTComponent::process_resend() { this->schedule_resend_state(); } } -void MQTTComponent::call_dump_config() { - if (this->is_internal()) - return; - - this->dump_config(); -} void MQTTComponent::schedule_resend_state() { this->resend_state_ = true; } bool MQTTComponent::is_connected_() const { return global_mqtt_client->is_connected(); } diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index 853712940a..0ffe6341d3 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -98,8 +98,6 @@ class MQTTComponent : public Component { /// Override setup_ so that we can call send_discovery() when needed. void call_setup() override; - void call_dump_config() override; - /// Send discovery info the Home Assistant, override this. virtual void send_discovery(JsonObject root, SendDiscoveryConfig &config) = 0; diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index e27c2cdfc6..b1ece86701 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -256,7 +256,7 @@ void Application::process_dump_config_() { #endif } - this->components_[this->dump_config_at_]->call_dump_config(); + this->components_[this->dump_config_at_]->call_dump_config_(); this->dump_config_at_++; } diff --git a/esphome/core/color.cpp b/esphome/core/color.cpp index 14c41c2b0d..edbc771472 100644 --- a/esphome/core/color.cpp +++ b/esphome/core/color.cpp @@ -7,12 +7,12 @@ constinit const Color Color::BLACK(0, 0, 0, 0); constinit const Color Color::WHITE(255, 255, 255, 255); Color Color::gradient(const Color &to_color, uint8_t amnt) { + uint8_t inv = 255 - amnt; Color new_color; - float amnt_f = float(amnt) / 255.0f; - new_color.r = amnt_f * (to_color.r - this->r) + this->r; - new_color.g = amnt_f * (to_color.g - this->g) + this->g; - new_color.b = amnt_f * (to_color.b - this->b) + this->b; - new_color.w = amnt_f * (to_color.w - this->w) + this->w; + new_color.r = (uint16_t(this->r) * inv + uint16_t(to_color.r) * amnt) / 255; + new_color.g = (uint16_t(this->g) * inv + uint16_t(to_color.g) * amnt) / 255; + new_color.b = (uint16_t(this->b) * inv + uint16_t(to_color.b) * amnt) / 255; + new_color.w = (uint16_t(this->w) * inv + uint16_t(to_color.w) * amnt) / 255; return new_color; } diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index b4a19a0776..e14fae5e08 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -211,7 +211,7 @@ bool Component::cancel_retry(uint32_t id) { void Component::call_loop_() { this->loop(); } void Component::call_setup() { this->setup(); } -void Component::call_dump_config() { +void Component::call_dump_config_() { this->dump_config(); if (this->is_failed()) { // Look up error message from global vector diff --git a/esphome/core/component.h b/esphome/core/component.h index 7ea9fdf3b3..2620e8eb2a 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -291,7 +291,7 @@ class Component { void call_loop_(); virtual void call_setup(); - virtual void call_dump_config(); + void call_dump_config_(); /// Helper to set component state (clears state bits and sets new state) void set_component_state_(uint8_t state); diff --git a/requirements_test.txt b/requirements_test.txt index 88a38ffa99..6b2617b656 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==4.0.5 flake8==7.3.0 # also change in .pre-commit-config.yaml when updating -ruff==0.15.3 # also change in .pre-commit-config.yaml when updating +ruff==0.15.4 # also change in .pre-commit-config.yaml when updating pyupgrade==3.21.2 # also change in .pre-commit-config.yaml when updating pre-commit diff --git a/tests/component_tests/esp32/config/execute_from_psram_disabled.yaml b/tests/component_tests/esp32/config/execute_from_psram_disabled.yaml new file mode 100644 index 0000000000..b52255c5ad --- /dev/null +++ b/tests/component_tests/esp32/config/execute_from_psram_disabled.yaml @@ -0,0 +1,10 @@ +esphome: + name: test + +esp32: + variant: esp32s3 + framework: + type: esp-idf + +psram: + mode: octal diff --git a/tests/component_tests/esp32/config/execute_from_psram_p4.yaml b/tests/component_tests/esp32/config/execute_from_psram_p4.yaml new file mode 100644 index 0000000000..e9070a5ae1 --- /dev/null +++ b/tests/component_tests/esp32/config/execute_from_psram_p4.yaml @@ -0,0 +1,11 @@ +esphome: + name: test + +esp32: + variant: esp32p4 + framework: + type: esp-idf + advanced: + execute_from_psram: true + +psram: diff --git a/tests/component_tests/esp32/config/execute_from_psram_s3.yaml b/tests/component_tests/esp32/config/execute_from_psram_s3.yaml new file mode 100644 index 0000000000..aec020ddff --- /dev/null +++ b/tests/component_tests/esp32/config/execute_from_psram_s3.yaml @@ -0,0 +1,12 @@ +esphome: + name: test + +esp32: + variant: esp32s3 + framework: + type: esp-idf + advanced: + execute_from_psram: true + +psram: + mode: octal diff --git a/tests/component_tests/esp32/test_esp32.py b/tests/component_tests/esp32/test_esp32.py index 68bd3a5965..bd4f9828ce 100644 --- a/tests/component_tests/esp32/test_esp32.py +++ b/tests/component_tests/esp32/test_esp32.py @@ -2,13 +2,17 @@ Test ESP32 configuration """ +from collections.abc import Callable +from pathlib import Path from typing import Any import pytest from esphome.components.esp32 import VARIANTS +from esphome.components.esp32.const import KEY_ESP32, KEY_SDKCONFIG_OPTIONS import esphome.config_validation as cv from esphome.const import CONF_ESPHOME, PlatformFramework +from esphome.core import CORE from tests.component_tests.types import SetCoreConfigCallable @@ -70,7 +74,7 @@ def test_esp32_config( "advanced": {"execute_from_psram": True}, }, }, - r"'execute_from_psram' is only supported on ESP32S3 variant @ data\['framework'\]\['advanced'\]\['execute_from_psram'\]", + r"'execute_from_psram' is not available on this esp32 variant @ data\['framework'\]\['advanced'\]\['execute_from_psram'\]", id="execute_from_psram_invalid_for_variant_config", ), pytest.param( @@ -82,7 +86,18 @@ def test_esp32_config( }, }, r"'execute_from_psram' requires PSRAM to be configured @ data\['framework'\]\['advanced'\]\['execute_from_psram'\]", - id="execute_from_psram_requires_psram_config", + id="execute_from_psram_requires_psram_s3_config", + ), + pytest.param( + { + "variant": "esp32p4", + "framework": { + "type": "esp-idf", + "advanced": {"execute_from_psram": True}, + }, + }, + r"'execute_from_psram' requires PSRAM to be configured @ data\['framework'\]\['advanced'\]\['execute_from_psram'\]", + id="execute_from_psram_requires_psram_p4_config", ), pytest.param( { @@ -108,3 +123,39 @@ def test_esp32_configuration_errors( with pytest.raises(cv.Invalid, match=error_match): FINAL_VALIDATE_SCHEMA(CONFIG_SCHEMA(config)) + + +def test_execute_from_psram_s3_sdkconfig( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """Test that execute_from_psram on ESP32-S3 sets the correct sdkconfig options.""" + generate_main(component_config_path("execute_from_psram_s3.yaml")) + sdkconfig = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] + assert sdkconfig.get("CONFIG_SPIRAM_FETCH_INSTRUCTIONS") is True + assert sdkconfig.get("CONFIG_SPIRAM_RODATA") is True + assert "CONFIG_SPIRAM_XIP_FROM_PSRAM" not in sdkconfig + + +def test_execute_from_psram_p4_sdkconfig( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """Test that execute_from_psram on ESP32-P4 sets the correct sdkconfig options.""" + generate_main(component_config_path("execute_from_psram_p4.yaml")) + sdkconfig = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] + assert sdkconfig.get("CONFIG_SPIRAM_XIP_FROM_PSRAM") is True + assert "CONFIG_SPIRAM_FETCH_INSTRUCTIONS" not in sdkconfig + assert "CONFIG_SPIRAM_RODATA" not in sdkconfig + + +def test_execute_from_psram_disabled_sdkconfig( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """Test that without execute_from_psram, no XIP sdkconfig options are set.""" + generate_main(component_config_path("execute_from_psram_disabled.yaml")) + sdkconfig = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] + assert "CONFIG_SPIRAM_FETCH_INSTRUCTIONS" not in sdkconfig + assert "CONFIG_SPIRAM_RODATA" not in sdkconfig + assert "CONFIG_SPIRAM_XIP_FROM_PSRAM" not in sdkconfig