diff --git a/.clang-tidy.hash b/.clang-tidy.hash index 240b205158..e1f5e096c0 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -4268ab0b5150f79ab1c317e8f3834c8bb0b4c8122da4f6b1fd67c49d0f2098c9 +5ac05ac603766d76b86a05cdf6a43febcaae807fe9e2406d812c47d4b5fed91d diff --git a/esphome/components/alarm_control_panel/alarm_control_panel.cpp b/esphome/components/alarm_control_panel/alarm_control_panel.cpp index f938155dd3..89c0908a74 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel.cpp @@ -8,8 +8,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -namespace esphome { -namespace alarm_control_panel { +namespace esphome::alarm_control_panel { static const char *const TAG = "alarm_control_panel"; @@ -115,5 +114,4 @@ void AlarmControlPanel::disarm(optional code) { call.perform(); } -} // namespace alarm_control_panel -} // namespace esphome +} // namespace esphome::alarm_control_panel diff --git a/esphome/components/alarm_control_panel/alarm_control_panel.h b/esphome/components/alarm_control_panel/alarm_control_panel.h index 59ccf0e484..340f15bcd6 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel.h +++ b/esphome/components/alarm_control_panel/alarm_control_panel.h @@ -1,7 +1,5 @@ #pragma once -#include - #include "alarm_control_panel_call.h" #include "alarm_control_panel_state.h" @@ -9,8 +7,7 @@ #include "esphome/core/entity_base.h" #include "esphome/core/log.h" -namespace esphome { -namespace alarm_control_panel { +namespace esphome::alarm_control_panel { enum AlarmControlPanelFeature : uint8_t { // Matches Home Assistant values @@ -141,5 +138,4 @@ class AlarmControlPanel : public EntityBase { LazyCallbackManager ready_callback_{}; }; -} // namespace alarm_control_panel -} // namespace esphome +} // namespace esphome::alarm_control_panel diff --git a/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp b/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp index 7bb9b9989c..5e98d58368 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp @@ -4,8 +4,7 @@ #include "esphome/core/log.h" -namespace esphome { -namespace alarm_control_panel { +namespace esphome::alarm_control_panel { static const char *const TAG = "alarm_control_panel"; @@ -99,5 +98,4 @@ void AlarmControlPanelCall::perform() { } } -} // namespace alarm_control_panel -} // namespace esphome +} // namespace esphome::alarm_control_panel diff --git a/esphome/components/alarm_control_panel/alarm_control_panel_call.h b/esphome/components/alarm_control_panel/alarm_control_panel_call.h index 034e3142da..cff00900dd 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel_call.h +++ b/esphome/components/alarm_control_panel/alarm_control_panel_call.h @@ -6,8 +6,7 @@ #include "esphome/core/helpers.h" -namespace esphome { -namespace alarm_control_panel { +namespace esphome::alarm_control_panel { class AlarmControlPanel; @@ -36,5 +35,4 @@ class AlarmControlPanelCall { void validate_(); }; -} // namespace alarm_control_panel -} // namespace esphome +} // namespace esphome::alarm_control_panel diff --git a/esphome/components/alarm_control_panel/alarm_control_panel_state.cpp b/esphome/components/alarm_control_panel/alarm_control_panel_state.cpp index abe6f51995..862c620497 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel_state.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel_state.cpp @@ -1,7 +1,6 @@ #include "alarm_control_panel_state.h" -namespace esphome { -namespace alarm_control_panel { +namespace esphome::alarm_control_panel { const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state) { switch (state) { @@ -30,5 +29,4 @@ const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState stat } } -} // namespace alarm_control_panel -} // namespace esphome +} // namespace esphome::alarm_control_panel diff --git a/esphome/components/alarm_control_panel/alarm_control_panel_state.h b/esphome/components/alarm_control_panel/alarm_control_panel_state.h index ad16222dc0..dd0b91f064 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel_state.h +++ b/esphome/components/alarm_control_panel/alarm_control_panel_state.h @@ -3,8 +3,7 @@ #include #include "esphome/core/log.h" -namespace esphome { -namespace alarm_control_panel { +namespace esphome::alarm_control_panel { enum AlarmControlPanelState : uint8_t { ACP_STATE_DISARMED = 0, @@ -25,5 +24,4 @@ enum AlarmControlPanelState : uint8_t { */ const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state); -} // namespace alarm_control_panel -} // namespace esphome +} // namespace esphome::alarm_control_panel diff --git a/esphome/components/alarm_control_panel/automation.h b/esphome/components/alarm_control_panel/automation.h index af4a14e27a..ce5ceadb47 100644 --- a/esphome/components/alarm_control_panel/automation.h +++ b/esphome/components/alarm_control_panel/automation.h @@ -3,8 +3,7 @@ #include "esphome/core/automation.h" #include "alarm_control_panel.h" -namespace esphome { -namespace alarm_control_panel { +namespace esphome::alarm_control_panel { /// Trigger on any state change class StateTrigger : public Trigger<> { @@ -165,5 +164,4 @@ template class AlarmControlPanelCondition : public Conditionwifi_soft_ap_ip(); -#ifdef USE_ESP_IDF +#if defined(USE_ESP32) // Create DNS server instance for ESP-IDF this->dns_server_ = make_unique(); this->dns_server_->start(ip); -#endif -#ifdef USE_ARDUINO +#elif defined(USE_ARDUINO) this->dns_server_ = make_unique(); this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError); this->dns_server_->start(53, ESPHOME_F("*"), ip); diff --git a/esphome/components/captive_portal/captive_portal.h b/esphome/components/captive_portal/captive_portal.h index f48c286f0c..0c63a3670a 100644 --- a/esphome/components/captive_portal/captive_portal.h +++ b/esphome/components/captive_portal/captive_portal.h @@ -2,11 +2,10 @@ #include "esphome/core/defines.h" #ifdef USE_CAPTIVE_PORTAL #include -#ifdef USE_ARDUINO -#include -#endif -#ifdef USE_ESP_IDF +#if defined(USE_ESP32) #include "dns_server_esp32_idf.h" +#elif defined(USE_ARDUINO) +#include #endif #include "esphome/core/component.h" #include "esphome/core/helpers.h" @@ -23,15 +22,14 @@ class CaptivePortal : public AsyncWebHandler, public Component { void setup() override; void dump_config() override; void loop() override { -#ifdef USE_ARDUINO - if (this->dns_server_ != nullptr) { - this->dns_server_->processNextRequest(); - } -#endif -#ifdef USE_ESP_IDF +#if defined(USE_ESP32) if (this->dns_server_ != nullptr) { this->dns_server_->process_next_request(); } +#elif defined(USE_ARDUINO) + if (this->dns_server_ != nullptr) { + this->dns_server_->processNextRequest(); + } #endif } float get_setup_priority() const override; @@ -64,7 +62,7 @@ class CaptivePortal : public AsyncWebHandler, public Component { web_server_base::WebServerBase *base_; bool initialized_{false}; bool active_{false}; -#if defined(USE_ARDUINO) || defined(USE_ESP_IDF) +#if defined(USE_ARDUINO) || defined(USE_ESP32) std::unique_ptr dns_server_{nullptr}; #endif }; diff --git a/esphome/components/captive_portal/dns_server_esp32_idf.cpp b/esphome/components/captive_portal/dns_server_esp32_idf.cpp index 740107400a..5188b2047f 100644 --- a/esphome/components/captive_portal/dns_server_esp32_idf.cpp +++ b/esphome/components/captive_portal/dns_server_esp32_idf.cpp @@ -1,5 +1,5 @@ #include "dns_server_esp32_idf.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/core/log.h" #include "esphome/core/hal.h" @@ -202,4 +202,4 @@ void DNSServer::process_next_request() { } // namespace esphome::captive_portal -#endif // USE_ESP_IDF +#endif // USE_ESP32 diff --git a/esphome/components/captive_portal/dns_server_esp32_idf.h b/esphome/components/captive_portal/dns_server_esp32_idf.h index 13d9def8e3..3e0ac07373 100644 --- a/esphome/components/captive_portal/dns_server_esp32_idf.h +++ b/esphome/components/captive_portal/dns_server_esp32_idf.h @@ -1,5 +1,5 @@ #pragma once -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include #include "esphome/core/helpers.h" @@ -24,4 +24,4 @@ class DNSServer { } // namespace esphome::captive_portal -#endif // USE_ESP_IDF +#endif // USE_ESP32 diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 229862ce01..2d35509493 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -369,7 +369,7 @@ optional Climate::restore_state_() { } void Climate::save_state_() { -#if (defined(USE_ESP_IDF) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0))) && \ +#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" #define TEMP_IGNORE_MEMACCESS diff --git a/esphome/components/debug/debug_esp32.cpp b/esphome/components/debug/debug_esp32.cpp index 1c3dc3699b..25852b32a7 100644 --- a/esphome/components/debug/debug_esp32.cpp +++ b/esphome/components/debug/debug_esp32.cpp @@ -200,7 +200,7 @@ void DebugComponent::get_device_info_(std::string &device_info) { #ifdef USE_ARDUINO ESP_LOGD(TAG, "Framework: Arduino"); device_info += "Arduino"; -#elif defined(USE_ESP_IDF) +#elif defined(USE_ESP32) ESP_LOGD(TAG, "Framework: ESP-IDF"); device_info += "ESP-IDF"; #else diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 42f8ab8fd4..87b5e2b738 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -24,7 +24,9 @@ extern "C" { #include #ifdef USE_ARDUINO -#include +// Prevent Arduino from releasing BT memory at startup (esp32-hal-misc.c). +// Without this, esp_bt_controller_init() fails with ESP_ERR_INVALID_STATE. +extern "C" bool btInUse() { return true; } // NOLINT(readability-identifier-naming) #endif namespace esphome::esp32_ble { @@ -165,12 +167,6 @@ void ESP32BLE::advertising_init_() { bool ESP32BLE::ble_setup_() { esp_err_t err; #ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID -#ifdef USE_ARDUINO - if (!btStart()) { - ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status()); - return false; - } -#else if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { // start bt controller if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) { @@ -195,7 +191,6 @@ bool ESP32BLE::ble_setup_() { return false; } } -#endif esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); #else @@ -334,12 +329,6 @@ bool ESP32BLE::ble_dismantle_() { } #ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID -#ifdef USE_ARDUINO - if (!btStop()) { - ESP_LOGE(TAG, "btStop failed: %d", esp_bt_controller_get_status()); - return false; - } -#else if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_IDLE) { // stop bt controller if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED) { @@ -363,7 +352,6 @@ bool ESP32BLE::ble_dismantle_() { return false; } } -#endif #else if (esp_hosted_bt_controller_disable() != ESP_OK) { ESP_LOGW(TAG, "esp_hosted_bt_controller_disable failed"); diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index a74f9ee8ce..c4969a79b2 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -191,7 +191,8 @@ async def to_code(config): cg.add_define(ThreadModel.SINGLE) cg.add_platformio_option( - "extra_scripts", ["pre:testing_mode.py", "post:post_build.py"] + "extra_scripts", + ["pre:testing_mode.py", "pre:exclude_updater.py", "post:post_build.py"], ) conf = config[CONF_FRAMEWORK] @@ -278,3 +279,8 @@ def copy_files(): testing_mode_file, CORE.relative_build_path("testing_mode.py"), ) + exclude_updater_file = dir / "exclude_updater.py.script" + copy_file_if_changed( + exclude_updater_file, + CORE.relative_build_path("exclude_updater.py"), + ) diff --git a/esphome/components/esp8266/exclude_updater.py.script b/esphome/components/esp8266/exclude_updater.py.script new file mode 100644 index 0000000000..69331e3b03 --- /dev/null +++ b/esphome/components/esp8266/exclude_updater.py.script @@ -0,0 +1,21 @@ +# pylint: disable=E0602 +Import("env") # noqa + +import os + +# Filter out Updater.cpp from the Arduino core build +# This saves 228 bytes of .bss by not instantiating the global Update object +# ESPHome uses its own native OTA backend instead + + +def filter_updater_from_core(env, node): + """Filter callback to exclude Updater.cpp from framework build.""" + path = node.get_path() + if path.endswith("Updater.cpp"): + print(f"ESPHome: Excluding {os.path.basename(path)} from build (using native OTA backend)") + return None + return node + + +# Apply the filter to framework sources +env.AddBuildMiddleware(filter_updater_from_core, "**/cores/esp8266/Updater.cpp") diff --git a/esphome/components/esp_ldo/__init__.py b/esphome/components/esp_ldo/__init__.py index 38e684c537..f136dd149b 100644 --- a/esphome/components/esp_ldo/__init__.py +++ b/esphome/components/esp_ldo/__init__.py @@ -31,7 +31,7 @@ CONFIG_SCHEMA = cv.All( } ) ), - cv.only_with_esp_idf, + cv.only_on_esp32, only_on_variant(supported=[VARIANT_ESP32P4]), ) diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index b589a6119f..f9984e1425 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -10,7 +10,7 @@ #endif #include "esphome/components/network/util.h" #include "esphome/components/ota/ota_backend.h" -#include "esphome/components/ota/ota_backend_arduino_esp8266.h" +#include "esphome/components/ota/ota_backend_esp8266.h" #include "esphome/components/ota/ota_backend_arduino_libretiny.h" #include "esphome/components/ota/ota_backend_arduino_rp2040.h" #include "esphome/components/ota/ota_backend_esp_idf.h" diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 793ebdec42..114000401f 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -644,6 +644,12 @@ void EthernetComponent::dump_connect_params_() { 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]; ESP_LOGCONFIG(TAG, " IP Address: %s\n" " Hostname: '%s'\n" @@ -651,9 +657,9 @@ void EthernetComponent::dump_connect_params_() { " Gateway: %s\n" " DNS1: %s\n" " DNS2: %s", - network::IPAddress(&ip.ip).str().c_str(), App.get_name().c_str(), - network::IPAddress(&ip.netmask).str().c_str(), network::IPAddress(&ip.gw).str().c_str(), - network::IPAddress(dns_ip1).str().c_str(), network::IPAddress(dns_ip2).str().c_str()); + 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)); #if USE_NETWORK_IPV6 struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES]; @@ -665,12 +671,13 @@ void EthernetComponent::dump_connect_params_() { } #endif /* USE_NETWORK_IPV6 */ + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; ESP_LOGCONFIG(TAG, " MAC Address: %s\n" " Is Full Duplex: %s\n" " Link Speed: %u", - this->get_eth_mac_address_pretty().c_str(), YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL), - this->get_link_speed() == ETH_SPEED_100M ? 100 : 10); + 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); } #ifdef USE_ETHERNET_SPI @@ -711,11 +718,16 @@ void EthernetComponent::get_eth_mac_address_raw(uint8_t *mac) { } 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); - char buf[18]; - format_mac_addr_upper(mac, buf); - return std::string(buf); + format_mac_addr_upper(mac, buf.data()); + return buf.data(); } eth_duplex_t EthernetComponent::get_duplex_mode() { diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index bffed4dc4a..490a9d026e 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/core/hal.h" +#include "esphome/core/helpers.h" #include "esphome/components/network/ip_address.h" #ifdef USE_ESP32 @@ -93,6 +94,7 @@ class EthernetComponent : public Component { void set_use_address(const char *use_address); void get_eth_mac_address_raw(uint8_t *mac); 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(); diff --git a/esphome/components/http_request/ota/ota_http_request.cpp b/esphome/components/http_request/ota/ota_http_request.cpp index 058579752e..2cd7489e38 100644 --- a/esphome/components/http_request/ota/ota_http_request.cpp +++ b/esphome/components/http_request/ota/ota_http_request.cpp @@ -7,7 +7,7 @@ #include "esphome/components/md5/md5.h" #include "esphome/components/watchdog/watchdog.h" #include "esphome/components/ota/ota_backend.h" -#include "esphome/components/ota/ota_backend_arduino_esp8266.h" +#include "esphome/components/ota/ota_backend_esp8266.h" #include "esphome/components/ota/ota_backend_arduino_rp2040.h" #include "esphome/components/ota/ota_backend_esp_idf.h" diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index 1d528b2f73..91077a1ff4 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -5,7 +5,7 @@ Constants already defined in esphome.const are not duplicated here and must be i """ import logging -from typing import TYPE_CHECKING, Any +from typing import Any from esphome import codegen as cg, config_validation as cv from esphome.const import CONF_ITEMS @@ -96,13 +96,9 @@ class LValidator: return None if isinstance(value, Lambda): # Local import to avoid circular import - from .lvcode import CodeContext, LambdaContext + from .lvcode import get_lambda_context_args - if TYPE_CHECKING: - # CodeContext does not have get_automation_parameters - # so we need to assert the type here - assert isinstance(CodeContext.code_context, LambdaContext) - args = args or CodeContext.code_context.get_automation_parameters() + args = args or get_lambda_context_args() return cg.RawExpression( call_lambda( await cg.process_lambda(value, args, return_type=self.rtype) diff --git a/esphome/components/lvgl/lv_validation.py b/esphome/components/lvgl/lv_validation.py index 9c1dd22085..947e44b131 100644 --- a/esphome/components/lvgl/lv_validation.py +++ b/esphome/components/lvgl/lv_validation.py @@ -1,5 +1,5 @@ import re -from typing import TYPE_CHECKING, Any +from typing import Any import esphome.codegen as cg from esphome.components import image @@ -404,14 +404,9 @@ class TextValidator(LValidator): self, value: Any, args: list[tuple[SafeExpType, str]] | None = None ) -> Expression: # Local import to avoid circular import at module level + from .lvcode import get_lambda_context_args - from .lvcode import CodeContext, LambdaContext - - if TYPE_CHECKING: - # CodeContext does not have get_automation_parameters - # so we need to assert the type here - assert isinstance(CodeContext.code_context, LambdaContext) - args = args or CodeContext.code_context.get_automation_parameters() + args = args or get_lambda_context_args() if isinstance(value, dict): if format_str := value.get(CONF_FORMAT): diff --git a/esphome/components/lvgl/lvcode.py b/esphome/components/lvgl/lvcode.py index e2c70642a8..b79d1e88dd 100644 --- a/esphome/components/lvgl/lvcode.py +++ b/esphome/components/lvgl/lvcode.py @@ -1,4 +1,5 @@ import abc +from typing import TYPE_CHECKING from esphome import codegen as cg from esphome.config import Config @@ -200,6 +201,21 @@ class LvContext(LambdaContext): return self.add(*args) +def get_lambda_context_args() -> list[tuple[SafeExpType, str]]: + """Get automation parameters from the current lambda context if available. + + When called from outside LVGL's context (e.g., from interval), + CodeContext.code_context will be None, so return empty args. + """ + if CodeContext.code_context is None: + return [] + if TYPE_CHECKING: + # CodeContext base class doesn't define get_automation_parameters(), + # but LambdaContext and LvContext (the concrete implementations) do. + assert isinstance(CodeContext.code_context, LambdaContext) + return CodeContext.code_context.get_automation_parameters() + + class LocalVariable(MockObj): """ Create a local variable and enclose the code using it within a block. diff --git a/esphome/components/micro_wake_word/__init__.py b/esphome/components/micro_wake_word/__init__.py index 0d478f749b..74696584da 100644 --- a/esphome/components/micro_wake_word/__init__.py +++ b/esphome/components/micro_wake_word/__init__.py @@ -368,7 +368,7 @@ CONFIG_SCHEMA = cv.All( ), } ).extend(cv.COMPONENT_SCHEMA), - cv.only_with_esp_idf, + cv.only_on_esp32, ) diff --git a/esphome/components/micro_wake_word/automation.h b/esphome/components/micro_wake_word/automation.h index e1795a7e64..218ce9e4bc 100644 --- a/esphome/components/micro_wake_word/automation.h +++ b/esphome/components/micro_wake_word/automation.h @@ -3,7 +3,7 @@ #include "micro_wake_word.h" #include "streaming_model.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 namespace esphome { namespace micro_wake_word { diff --git a/esphome/components/micro_wake_word/micro_wake_word.cpp b/esphome/components/micro_wake_word/micro_wake_word.cpp index b8377ead38..d7e80efc84 100644 --- a/esphome/components/micro_wake_word/micro_wake_word.cpp +++ b/esphome/components/micro_wake_word/micro_wake_word.cpp @@ -1,6 +1,6 @@ #include "micro_wake_word.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/core/application.h" #include "esphome/core/hal.h" @@ -473,4 +473,4 @@ bool MicroWakeWord::update_model_probabilities_(const int8_t audio_features[PREP } // namespace micro_wake_word } // namespace esphome -#endif // USE_ESP_IDF +#endif // USE_ESP32 diff --git a/esphome/components/micro_wake_word/micro_wake_word.h b/esphome/components/micro_wake_word/micro_wake_word.h index 84261eaa5b..b427e4dfcb 100644 --- a/esphome/components/micro_wake_word/micro_wake_word.h +++ b/esphome/components/micro_wake_word/micro_wake_word.h @@ -1,6 +1,6 @@ #pragma once -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "preprocessor_settings.h" #include "streaming_model.h" @@ -140,4 +140,4 @@ class MicroWakeWord : public Component } // namespace micro_wake_word } // namespace esphome -#endif // USE_ESP_IDF +#endif // USE_ESP32 diff --git a/esphome/components/micro_wake_word/preprocessor_settings.h b/esphome/components/micro_wake_word/preprocessor_settings.h index 3de21de92e..c9d195b49b 100644 --- a/esphome/components/micro_wake_word/preprocessor_settings.h +++ b/esphome/components/micro_wake_word/preprocessor_settings.h @@ -1,6 +1,6 @@ #pragma once -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include diff --git a/esphome/components/micro_wake_word/streaming_model.cpp b/esphome/components/micro_wake_word/streaming_model.cpp index 2b073cce56..47d2c70e13 100644 --- a/esphome/components/micro_wake_word/streaming_model.cpp +++ b/esphome/components/micro_wake_word/streaming_model.cpp @@ -1,6 +1,6 @@ #include "streaming_model.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/core/helpers.h" #include "esphome/core/log.h" diff --git a/esphome/components/micro_wake_word/streaming_model.h b/esphome/components/micro_wake_word/streaming_model.h index b7b22b9700..0811bfb19b 100644 --- a/esphome/components/micro_wake_word/streaming_model.h +++ b/esphome/components/micro_wake_word/streaming_model.h @@ -1,6 +1,6 @@ #pragma once -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "preprocessor_settings.h" diff --git a/esphome/components/mipi_dsi/display.py b/esphome/components/mipi_dsi/display.py index 90c4cc082e..c288b33cd2 100644 --- a/esphome/components/mipi_dsi/display.py +++ b/esphome/components/mipi_dsi/display.py @@ -165,8 +165,8 @@ def model_schema(config): ) return cv.All( schema, + cv.only_on_esp32, only_on_variant(supported=[VARIANT_ESP32P4]), - cv.only_with_esp_idf, ) diff --git a/esphome/components/mipi_rgb/display.py b/esphome/components/mipi_rgb/display.py index 61dbeb8ed4..96e167b2e6 100644 --- a/esphome/components/mipi_rgb/display.py +++ b/esphome/components/mipi_rgb/display.py @@ -224,8 +224,8 @@ def _config_schema(config): schema = model_schema(config) return cv.All( schema, + cv.only_on_esp32, only_on_variant(supported=[VARIANT_ESP32S3]), - cv.only_with_esp_idf, )(config) diff --git a/esphome/components/mipi_spi/display.py b/esphome/components/mipi_spi/display.py index 50ea826eab..69bf133c68 100644 --- a/esphome/components/mipi_spi/display.py +++ b/esphome/components/mipi_spi/display.py @@ -224,7 +224,7 @@ def model_schema(config): } ) if bus_mode != TYPE_SINGLE: - return cv.All(schema, cv.only_with_esp_idf) + return cv.All(schema, cv.only_on_esp32) return schema diff --git a/esphome/components/mixer/speaker/__init__.py b/esphome/components/mixer/speaker/__init__.py index 46729f8eda..c4069851af 100644 --- a/esphome/components/mixer/speaker/__init__.py +++ b/esphome/components/mixer/speaker/__init__.py @@ -93,9 +93,7 @@ CONFIG_SCHEMA = cv.All( ), cv.Optional(CONF_NUM_CHANNELS): cv.int_range(min=1, max=2), cv.Optional(CONF_QUEUE_MODE, default=False): cv.boolean, - cv.SplitDefault(CONF_TASK_STACK_IN_PSRAM, esp32_idf=False): cv.All( - cv.boolean, cv.only_with_esp_idf - ), + cv.Optional(CONF_TASK_STACK_IN_PSRAM, default=False): cv.boolean, } ), cv.only_on([PLATFORM_ESP32]), diff --git a/esphome/components/mqtt/mqtt_backend_esp32.cpp b/esphome/components/mqtt/mqtt_backend_esp32.cpp index dcc51ed60e..e3105f4860 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.cpp +++ b/esphome/components/mqtt/mqtt_backend_esp32.cpp @@ -232,16 +232,6 @@ void MQTTBackendESP32::esphome_mqtt_task(void *params) { this_mqtt->mqtt_event_pool_.release(elem); } } - - // Clean up any remaining items in the queue - struct QueueElement *elem; - while ((elem = this_mqtt->mqtt_queue_.pop()) != nullptr) { - this_mqtt->mqtt_event_pool_.release(elem); - } - - // Note: EventPool destructor will clean up the pool itself - // Task will delete itself - vTaskDelete(nullptr); } bool MQTTBackendESP32::enqueue_(MqttQueueTypeT type, const char *topic, int qos, bool retain, const char *payload, diff --git a/esphome/components/network/ip_address.h b/esphome/components/network/ip_address.h index 3d8b062d0b..b719d1a70e 100644 --- a/esphome/components/network/ip_address.h +++ b/esphome/components/network/ip_address.h @@ -8,7 +8,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/macros.h" -#if defined(USE_ESP_IDF) || defined(USE_LIBRETINY) || USE_ARDUINO_VERSION_CODE > VERSION_CODE(3, 0, 0) +#if defined(USE_ESP32) || defined(USE_LIBRETINY) || USE_ARDUINO_VERSION_CODE > VERSION_CODE(3, 0, 0) #include #endif @@ -40,6 +40,9 @@ using ip4_addr_t = in_addr; namespace esphome { namespace network { +/// Buffer size for IP address string (IPv6 max: 39 chars + null) +static constexpr size_t IP_ADDRESS_BUFFER_SIZE = 40; + struct IPAddress { public: #ifdef USE_HOST @@ -50,6 +53,10 @@ struct IPAddress { IPAddress(const std::string &in_address) { inet_aton(in_address.c_str(), &ip_addr_); } IPAddress(const ip_addr_t *other_ip) { ip_addr_ = *other_ip; } std::string str() const { return str_lower_case(inet_ntoa(ip_addr_)); } + /// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes. + char *str_to(char *buf) const { + return const_cast(inet_ntop(AF_INET, &ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE)); + } #else IPAddress() { ip_addr_set_zero(&ip_addr_); } IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) { @@ -128,6 +135,8 @@ struct IPAddress { bool is_ip6() const { return IP_IS_V6(&ip_addr_); } bool is_multicast() const { return ip_addr_ismulticast(&ip_addr_); } std::string str() const { return str_lower_case(ipaddr_ntoa(&ip_addr_)); } + /// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes. + char *str_to(char *buf) const { return ipaddr_ntoa_r(&ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE); } bool operator==(const IPAddress &other) const { return ip_addr_cmp(&ip_addr_, &other.ip_addr_); } bool operator!=(const IPAddress &other) const { return !ip_addr_cmp(&ip_addr_, &other.ip_addr_); } IPAddress &operator+=(uint8_t increase) { diff --git a/esphome/components/one_wire/one_wire_bus.cpp b/esphome/components/one_wire/one_wire_bus.cpp index c2542177cf..27b7d58a0f 100644 --- a/esphome/components/one_wire/one_wire_bus.cpp +++ b/esphome/components/one_wire/one_wire_bus.cpp @@ -49,7 +49,8 @@ void OneWireBus::search() { break; auto *address8 = reinterpret_cast(&address); if (crc8(address8, 7) != address8[7]) { - ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex(address).c_str()); + char hex_buf[17]; + ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex_to(hex_buf, address)); } else { this->devices_.push_back(address); } @@ -82,8 +83,9 @@ void OneWireBus::dump_devices_(const char *tag) { ESP_LOGW(tag, " Found no devices!"); } else { ESP_LOGCONFIG(tag, " Found devices:"); + char hex_buf[17]; // uint64_t = 16 hex chars + null for (auto &address : this->devices_) { - ESP_LOGCONFIG(tag, " 0x%s (%s)", format_hex(address).c_str(), LOG_STR_ARG(get_model_str(address & 0xff))); + ESP_LOGCONFIG(tag, " 0x%s (%s)", format_hex_to(hex_buf, address), LOG_STR_ARG(get_model_str(address & 0xff))); } } } diff --git a/esphome/components/openthread/__init__.py b/esphome/components/openthread/__init__.py index 050e45cdc9..26c05a0a86 100644 --- a/esphome/components/openthread/__init__.py +++ b/esphome/components/openthread/__init__.py @@ -152,7 +152,6 @@ CONFIG_SCHEMA = cv.All( } ).extend(_CONNECTION_SCHEMA), cv.has_exactly_one_key(CONF_NETWORK_KEY, CONF_TLV), - cv.only_with_esp_idf, only_on_variant(supported=[VARIANT_ESP32C5, VARIANT_ESP32C6, VARIANT_ESP32H2]), _validate, _require_vfs_select, diff --git a/esphome/components/openthread/openthread_esp.cpp b/esphome/components/openthread/openthread_esp.cpp index b47e4b884a..1f18e51496 100644 --- a/esphome/components/openthread/openthread_esp.cpp +++ b/esphome/components/openthread/openthread_esp.cpp @@ -1,5 +1,5 @@ #include "esphome/core/defines.h" -#if defined(USE_OPENTHREAD) && defined(USE_ESP_IDF) +#if defined(USE_OPENTHREAD) && defined(USE_ESP32) #include #include "openthread.h" diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index 8bed9cee42..a514a7482f 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -148,7 +148,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform( PlatformFramework.ESP32_ARDUINO, PlatformFramework.ESP32_IDF, }, - "ota_backend_arduino_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, + "ota_backend_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO}, "ota_backend_arduino_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO}, "ota_backend_arduino_libretiny.cpp": { PlatformFramework.BK72XX_ARDUINO, diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.cpp b/esphome/components/ota/ota_backend_arduino_esp8266.cpp deleted file mode 100644 index 375c4e7200..0000000000 --- a/esphome/components/ota/ota_backend_arduino_esp8266.cpp +++ /dev/null @@ -1,89 +0,0 @@ -#ifdef USE_ARDUINO -#ifdef USE_ESP8266 -#include "ota_backend_arduino_esp8266.h" -#include "ota_backend.h" - -#include "esphome/components/esp8266/preferences.h" -#include "esphome/core/defines.h" -#include "esphome/core/log.h" - -#include - -namespace esphome { -namespace ota { - -static const char *const TAG = "ota.arduino_esp8266"; - -std::unique_ptr make_ota_backend() { return make_unique(); } - -OTAResponseTypes ArduinoESP8266OTABackend::begin(size_t image_size) { - // Handle UPDATE_SIZE_UNKNOWN (0) by calculating available space - if (image_size == 0) { - // NOLINTNEXTLINE(readability-static-accessed-through-instance) - image_size = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; - } - bool ret = Update.begin(image_size, U_FLASH); - if (ret) { - esp8266::preferences_prevent_write(true); - return OTA_RESPONSE_OK; - } - - uint8_t error = Update.getError(); - if (error == UPDATE_ERROR_BOOTSTRAP) - return OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING; - if (error == UPDATE_ERROR_NEW_FLASH_CONFIG) - return OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG; - if (error == UPDATE_ERROR_FLASH_CONFIG) - return OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG; - if (error == UPDATE_ERROR_SPACE) - return OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE; - - ESP_LOGE(TAG, "Begin error: %d", error); - - return OTA_RESPONSE_ERROR_UNKNOWN; -} - -void ArduinoESP8266OTABackend::set_update_md5(const char *md5) { - Update.setMD5(md5); - this->md5_set_ = true; -} - -OTAResponseTypes ArduinoESP8266OTABackend::write(uint8_t *data, size_t len) { - size_t written = Update.write(data, len); - if (written == len) { - return OTA_RESPONSE_OK; - } - - uint8_t error = Update.getError(); - ESP_LOGE(TAG, "Write error: %d", error); - - return OTA_RESPONSE_ERROR_WRITING_FLASH; -} - -OTAResponseTypes ArduinoESP8266OTABackend::end() { - // Use strict validation (false) when MD5 is set, lenient validation (true) when no MD5 - // This matches the behavior of the old web_server OTA implementation - bool success = Update.end(!this->md5_set_); - - // On ESP8266, Update.end() might return false even with error code 0 - // Check the actual error code to determine success - uint8_t error = Update.getError(); - - if (success || error == UPDATE_ERROR_OK) { - return OTA_RESPONSE_OK; - } - - ESP_LOGE(TAG, "End error: %d", error); - return OTA_RESPONSE_ERROR_UPDATE_END; -} - -void ArduinoESP8266OTABackend::abort() { - Update.end(); - esp8266::preferences_prevent_write(false); -} - -} // namespace ota -} // namespace esphome - -#endif -#endif diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.h b/esphome/components/ota/ota_backend_arduino_esp8266.h deleted file mode 100644 index e1b9015cc7..0000000000 --- a/esphome/components/ota/ota_backend_arduino_esp8266.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once -#ifdef USE_ARDUINO -#ifdef USE_ESP8266 -#include "ota_backend.h" - -#include "esphome/core/defines.h" -#include "esphome/core/macros.h" - -namespace esphome { -namespace ota { - -class ArduinoESP8266OTABackend : public OTABackend { - public: - OTAResponseTypes begin(size_t image_size) override; - void set_update_md5(const char *md5) override; - OTAResponseTypes write(uint8_t *data, size_t len) override; - OTAResponseTypes end() override; - void abort() override; -#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) - bool supports_compression() override { return true; } -#else - bool supports_compression() override { return false; } -#endif - - private: - bool md5_set_{false}; -}; - -} // namespace ota -} // namespace esphome - -#endif -#endif diff --git a/esphome/components/ota/ota_backend_esp8266.cpp b/esphome/components/ota/ota_backend_esp8266.cpp new file mode 100644 index 0000000000..4b84708cd9 --- /dev/null +++ b/esphome/components/ota/ota_backend_esp8266.cpp @@ -0,0 +1,356 @@ +#ifdef USE_ESP8266 +#include "ota_backend_esp8266.h" +#include "ota_backend.h" + +#include "esphome/components/esp8266/preferences.h" +#include "esphome/core/application.h" +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#include +#include + +#include + +extern "C" { +#include +#include +#include +#include +#include +} + +// Note: FLASH_SECTOR_SIZE (0x1000) is already defined in spi_flash_geometry.h + +// Flash header offsets +static constexpr uint8_t FLASH_MODE_OFFSET = 2; + +// Firmware magic bytes +static constexpr uint8_t FIRMWARE_MAGIC = 0xE9; +static constexpr uint8_t GZIP_MAGIC_1 = 0x1F; +static constexpr uint8_t GZIP_MAGIC_2 = 0x8B; + +// ESP8266 flash memory base address (memory-mapped flash starts here) +static constexpr uint32_t FLASH_BASE_ADDRESS = 0x40200000; + +// Boot mode extraction from GPI register (bits 16-19 contain boot mode) +static constexpr int BOOT_MODE_SHIFT = 16; +static constexpr int BOOT_MODE_MASK = 0xf; + +// Boot mode indicating UART download mode (OTA not possible) +static constexpr int BOOT_MODE_UART_DOWNLOAD = 1; + +// Minimum buffer size when memory is constrained +static constexpr size_t MIN_BUFFER_SIZE = 256; + +namespace esphome::ota { + +static const char *const TAG = "ota.esp8266"; + +std::unique_ptr make_ota_backend() { return make_unique(); } + +OTAResponseTypes ESP8266OTABackend::begin(size_t image_size) { + // Handle UPDATE_SIZE_UNKNOWN (0) by calculating available space + if (image_size == 0) { + // Round down to sector boundary: subtract one sector, then mask to sector alignment + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + image_size = (ESP.getFreeSketchSpace() - FLASH_SECTOR_SIZE) & ~(FLASH_SECTOR_SIZE - 1); + } + + // Check boot mode - if boot mode is UART download mode, + // we will not be able to reset into normal mode once update is done + int boot_mode = (GPI >> BOOT_MODE_SHIFT) & BOOT_MODE_MASK; + if (boot_mode == BOOT_MODE_UART_DOWNLOAD) { + return OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING; + } + + // Check flash configuration - real size must be >= configured size + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + if (!ESP.checkFlashConfig(false)) { + return OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG; + } + + // Get current sketch size + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + uint32_t sketch_size = ESP.getSketchSize(); + + // Size of current sketch rounded to sector boundary + uint32_t current_sketch_size = (sketch_size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); + + // Size of update rounded to sector boundary + uint32_t rounded_size = (image_size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); + + // End of available space for sketch and update (start of filesystem) + uint32_t update_end_address = FS_start - FLASH_BASE_ADDRESS; + + // Calculate start address for the update (write from end backwards) + this->start_address_ = (update_end_address > rounded_size) ? (update_end_address - rounded_size) : 0; + + // Check if there's enough space for both current sketch and update + if (this->start_address_ < current_sketch_size) { + return OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE; + } + + // Allocate buffer for sector writes (use smaller buffer if memory constrained) + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + this->buffer_size_ = (ESP.getFreeHeap() > 2 * FLASH_SECTOR_SIZE) ? FLASH_SECTOR_SIZE : MIN_BUFFER_SIZE; + + // ESP8266's umm_malloc guarantees 4-byte aligned allocations, which is required + // for spi_flash_write(). This is the same pattern used by Arduino's Updater class. + this->buffer_ = make_unique(this->buffer_size_); + if (!this->buffer_) { + return OTA_RESPONSE_ERROR_UNKNOWN; + } + + this->current_address_ = this->start_address_; + this->image_size_ = image_size; + this->buffer_len_ = 0; + this->md5_set_ = false; + + // Disable WiFi sleep during update + wifi_set_sleep_type(NONE_SLEEP_T); + + // Prevent preference writes during update + esp8266::preferences_prevent_write(true); + + // Initialize MD5 computation + this->md5_.init(); + + ESP_LOGD(TAG, "OTA begin: start=0x%08" PRIX32 ", size=%zu", this->start_address_, image_size); + + return OTA_RESPONSE_OK; +} + +void ESP8266OTABackend::set_update_md5(const char *md5) { + // Parse hex string to bytes + if (parse_hex(md5, this->expected_md5_, 16)) { + this->md5_set_ = true; + } +} + +OTAResponseTypes ESP8266OTABackend::write(uint8_t *data, size_t len) { + if (!this->buffer_) { + return OTA_RESPONSE_ERROR_UNKNOWN; + } + + size_t written = 0; + while (written < len) { + // Calculate how much we can buffer + size_t to_buffer = std::min(len - written, this->buffer_size_ - this->buffer_len_); + memcpy(this->buffer_.get() + this->buffer_len_, data + written, to_buffer); + this->buffer_len_ += to_buffer; + written += to_buffer; + + // If buffer is full, write to flash + if (this->buffer_len_ == this->buffer_size_ && !this->write_buffer_()) { + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + } + + return OTA_RESPONSE_OK; +} + +bool ESP8266OTABackend::erase_sector_if_needed_() { + if ((this->current_address_ % FLASH_SECTOR_SIZE) != 0) { + return true; // Not at sector boundary + } + + App.feed_wdt(); + if (spi_flash_erase_sector(this->current_address_ / FLASH_SECTOR_SIZE) != SPI_FLASH_RESULT_OK) { + ESP_LOGE(TAG, "Flash erase failed at 0x%08" PRIX32, this->current_address_); + return false; + } + return true; +} + +bool ESP8266OTABackend::flash_write_() { + App.feed_wdt(); + if (spi_flash_write(this->current_address_, reinterpret_cast(this->buffer_.get()), this->buffer_len_) != + SPI_FLASH_RESULT_OK) { + ESP_LOGE(TAG, "Flash write failed at 0x%08" PRIX32, this->current_address_); + return false; + } + return true; +} + +bool ESP8266OTABackend::write_buffer_() { + if (this->buffer_len_ == 0) { + return true; + } + + if (!this->erase_sector_if_needed_()) { + return false; + } + + // Patch flash mode in first sector if needed + // This is analogous to what esptool.py does when it receives a --flash_mode argument + bool is_first_sector = (this->current_address_ == this->start_address_); + uint8_t original_flash_mode = 0; + bool patched_flash_mode = false; + + // Only patch if we have enough bytes to access flash mode offset and it's not GZIP + if (is_first_sector && this->buffer_len_ > FLASH_MODE_OFFSET && this->buffer_[0] != GZIP_MAGIC_1) { + // Not GZIP compressed - check and patch flash mode + uint8_t current_flash_mode = this->get_flash_chip_mode_(); + uint8_t buffer_flash_mode = this->buffer_[FLASH_MODE_OFFSET]; + + if (buffer_flash_mode != current_flash_mode) { + original_flash_mode = buffer_flash_mode; + this->buffer_[FLASH_MODE_OFFSET] = current_flash_mode; + patched_flash_mode = true; + } + } + + if (!this->flash_write_()) { + return false; + } + + // Restore original flash mode for MD5 calculation + if (patched_flash_mode) { + this->buffer_[FLASH_MODE_OFFSET] = original_flash_mode; + } + + // Update MD5 with original (unpatched) data + this->md5_.add(this->buffer_.get(), this->buffer_len_); + + this->current_address_ += this->buffer_len_; + this->buffer_len_ = 0; + + return true; +} + +bool ESP8266OTABackend::write_buffer_final_() { + // Similar to write_buffer_(), but without flash mode patching or MD5 update (for final padded write) + if (this->buffer_len_ == 0) { + return true; + } + + if (!this->erase_sector_if_needed_() || !this->flash_write_()) { + return false; + } + + this->current_address_ += this->buffer_len_; + this->buffer_len_ = 0; + + return true; +} + +OTAResponseTypes ESP8266OTABackend::end() { + // Write any remaining buffered data + if (this->buffer_len_ > 0) { + // Add actual data to MD5 before padding + this->md5_.add(this->buffer_.get(), this->buffer_len_); + + // Pad to 4-byte alignment for flash write + while (this->buffer_len_ % 4 != 0) { + this->buffer_[this->buffer_len_++] = 0xFF; + } + if (!this->write_buffer_final_()) { + this->abort(); + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + } + + // Calculate actual bytes written + size_t actual_size = this->current_address_ - this->start_address_; + + // Check if any data was written + if (actual_size == 0) { + ESP_LOGE(TAG, "No data written"); + this->abort(); + return OTA_RESPONSE_ERROR_UPDATE_END; + } + + // Verify MD5 if set (strict mode), otherwise use lenient mode + // In lenient mode (no MD5), we accept whatever was written + if (this->md5_set_) { + this->md5_.calculate(); + if (!this->md5_.equals_bytes(this->expected_md5_)) { + ESP_LOGE(TAG, "MD5 mismatch"); + this->abort(); + return OTA_RESPONSE_ERROR_MD5_MISMATCH; + } + } else { + // Lenient mode: adjust size to what was actually written + // This matches Arduino's Update.end(true) behavior + this->image_size_ = actual_size; + } + + // Verify firmware header + if (!this->verify_end_()) { + this->abort(); + return OTA_RESPONSE_ERROR_UPDATE_END; + } + + // Write eboot command to copy firmware on next boot + eboot_command ebcmd; + ebcmd.action = ACTION_COPY_RAW; + ebcmd.args[0] = this->start_address_; + ebcmd.args[1] = 0x00000; // Destination: start of flash + ebcmd.args[2] = this->image_size_; + eboot_command_write(&ebcmd); + + ESP_LOGI(TAG, "OTA update staged: 0x%08" PRIX32 " -> 0x00000, size=%zu", this->start_address_, this->image_size_); + + // Clean up + this->buffer_.reset(); + esp8266::preferences_prevent_write(false); + + return OTA_RESPONSE_OK; +} + +void ESP8266OTABackend::abort() { + this->buffer_.reset(); + this->buffer_len_ = 0; + this->image_size_ = 0; + esp8266::preferences_prevent_write(false); +} + +bool ESP8266OTABackend::verify_end_() { + uint32_t buf; + if (spi_flash_read(this->start_address_, &buf, 4) != SPI_FLASH_RESULT_OK) { + ESP_LOGE(TAG, "Failed to read firmware header"); + return false; + } + + uint8_t *bytes = reinterpret_cast(&buf); + + // Check for GZIP (compressed firmware) + if (bytes[0] == GZIP_MAGIC_1 && bytes[1] == GZIP_MAGIC_2) { + // GZIP compressed - can't verify further + return true; + } + + // Check firmware magic byte + if (bytes[0] != FIRMWARE_MAGIC) { + ESP_LOGE(TAG, "Invalid firmware magic: 0x%02X (expected 0x%02X)", bytes[0], FIRMWARE_MAGIC); + return false; + } + +#if !FLASH_MAP_SUPPORT + // Check if new firmware's flash size fits (only when auto-detection is disabled) + // With FLASH_MAP_SUPPORT (modern cores), flash size is auto-detected from chip + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + uint32_t bin_flash_size = ESP.magicFlashChipSize((bytes[3] & 0xf0) >> 4); + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + if (bin_flash_size > ESP.getFlashChipRealSize()) { + ESP_LOGE(TAG, "Firmware flash size (%" PRIu32 ") exceeds chip size (%" PRIu32 ")", bin_flash_size, + ESP.getFlashChipRealSize()); + return false; + } +#endif + + return true; +} + +uint8_t ESP8266OTABackend::get_flash_chip_mode_() { + uint32_t data; + if (spi_flash_read(0x0000, &data, 4) != SPI_FLASH_RESULT_OK) { + return 0; // Default to QIO + } + return (reinterpret_cast(&data))[FLASH_MODE_OFFSET]; +} + +} // namespace esphome::ota +#endif // USE_ESP8266 diff --git a/esphome/components/ota/ota_backend_esp8266.h b/esphome/components/ota/ota_backend_esp8266.h new file mode 100644 index 0000000000..a9d6dd2ccc --- /dev/null +++ b/esphome/components/ota/ota_backend_esp8266.h @@ -0,0 +1,58 @@ +#pragma once +#ifdef USE_ESP8266 +#include "ota_backend.h" + +#include "esphome/components/md5/md5.h" +#include "esphome/core/defines.h" + +#include + +namespace esphome::ota { + +/// OTA backend for ESP8266 using native SDK functions. +/// This implementation bypasses the Arduino Updater library to save ~228 bytes of RAM +/// by not having a global Update object in .bss. +class ESP8266OTABackend : public OTABackend { + public: + OTAResponseTypes begin(size_t image_size) override; + void set_update_md5(const char *md5) override; + OTAResponseTypes write(uint8_t *data, size_t len) override; + OTAResponseTypes end() override; + void abort() override; + // Compression supported in all ESP8266 Arduino versions ESPHome supports (>= 2.7.0) + bool supports_compression() override { return true; } + + protected: + /// Erase flash sector if current address is at sector boundary + bool erase_sector_if_needed_(); + + /// Write buffer to flash (does not update address or clear buffer) + bool flash_write_(); + + /// Write buffered data to flash and update MD5 + bool write_buffer_(); + + /// Write buffered data to flash without MD5 update (for final padded write) + bool write_buffer_final_(); + + /// Verify the firmware header is valid + bool verify_end_(); + + /// Get current flash chip mode from flash header + uint8_t get_flash_chip_mode_(); + + std::unique_ptr buffer_; + size_t buffer_size_{0}; + size_t buffer_len_{0}; + + uint32_t start_address_{0}; + uint32_t current_address_{0}; + size_t image_size_{0}; + + md5::MD5Digest md5_{}; + uint8_t expected_md5_[16]; // Fixed-size buffer for 128-bit (16-byte) MD5 digest + bool md5_set_{false}; +}; + +} // namespace esphome::ota +#endif // USE_ESP8266 diff --git a/esphome/components/qspi_dbi/display.py b/esphome/components/qspi_dbi/display.py index 74d837a794..e4440c9b81 100644 --- a/esphome/components/qspi_dbi/display.py +++ b/esphome/components/qspi_dbi/display.py @@ -154,7 +154,7 @@ CONFIG_SCHEMA = cv.All( upper=True, key=CONF_MODEL, ), - cv.only_with_esp_idf, + cv.only_on_esp32, ) diff --git a/esphome/components/qspi_dbi/qspi_dbi.cpp b/esphome/components/qspi_dbi/qspi_dbi.cpp index 6c95bb7cf2..24b9a0ce0a 100644 --- a/esphome/components/qspi_dbi/qspi_dbi.cpp +++ b/esphome/components/qspi_dbi/qspi_dbi.cpp @@ -1,4 +1,4 @@ -#if defined(USE_ESP_IDF) && defined(USE_ESP32_VARIANT_ESP32S3) +#if defined(USE_ESP32) && defined(USE_ESP32_VARIANT_ESP32S3) #include "qspi_dbi.h" #include "esphome/core/log.h" diff --git a/esphome/components/qspi_dbi/qspi_dbi.h b/esphome/components/qspi_dbi/qspi_dbi.h index f35f0e519c..3eee9bec47 100644 --- a/esphome/components/qspi_dbi/qspi_dbi.h +++ b/esphome/components/qspi_dbi/qspi_dbi.h @@ -3,7 +3,7 @@ // #pragma once -#if defined(USE_ESP_IDF) && defined(USE_ESP32_VARIANT_ESP32S3) +#if defined(USE_ESP32) && defined(USE_ESP32_VARIANT_ESP32S3) #include "esphome/components/spi/spi.h" #include "esphome/components/display/display.h" #include "esphome/components/display/display_buffer.h" diff --git a/esphome/components/rpi_dpi_rgb/display.py b/esphome/components/rpi_dpi_rgb/display.py index 8e9da43a74..e92eee7c0c 100644 --- a/esphome/components/rpi_dpi_rgb/display.py +++ b/esphome/components/rpi_dpi_rgb/display.py @@ -122,7 +122,6 @@ CONFIG_SCHEMA = cv.All( ) ), only_on_variant(supported=[VARIANT_ESP32S3]), - cv.only_with_esp_idf, ) diff --git a/esphome/components/speaker/media_player/__init__.py b/esphome/components/speaker/media_player/__init__.py index 4ca57f2c4a..370b4576a7 100644 --- a/esphome/components/speaker/media_player/__init__.py +++ b/esphome/components/speaker/media_player/__init__.py @@ -315,7 +315,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_ON_VOLUME): automation.validate_automation(single=True), } ), - cv.only_with_esp_idf, + cv.only_on_esp32, _validate_repeated_speaker, _request_high_performance_networking, ) diff --git a/esphome/components/speaker/media_player/audio_pipeline.cpp b/esphome/components/speaker/media_player/audio_pipeline.cpp index dc8572ae43..8be37d740a 100644 --- a/esphome/components/speaker/media_player/audio_pipeline.cpp +++ b/esphome/components/speaker/media_player/audio_pipeline.cpp @@ -1,6 +1,6 @@ #include "audio_pipeline.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/core/defines.h" #include "esphome/core/hal.h" diff --git a/esphome/components/speaker/media_player/audio_pipeline.h b/esphome/components/speaker/media_player/audio_pipeline.h index 98f43fda6e..6fffde6c20 100644 --- a/esphome/components/speaker/media_player/audio_pipeline.h +++ b/esphome/components/speaker/media_player/audio_pipeline.h @@ -1,6 +1,6 @@ #pragma once -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/components/audio/audio.h" #include "esphome/components/audio/audio_reader.h" diff --git a/esphome/components/speaker/media_player/automation.h b/esphome/components/speaker/media_player/automation.h index fdf3db07f9..6270da7bd4 100644 --- a/esphome/components/speaker/media_player/automation.h +++ b/esphome/components/speaker/media_player/automation.h @@ -2,7 +2,7 @@ #include "speaker_media_player.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/components/audio/audio.h" #include "esphome/core/automation.h" diff --git a/esphome/components/speaker/media_player/speaker_media_player.cpp b/esphome/components/speaker/media_player/speaker_media_player.cpp index 5722aab195..9a3a47bac8 100644 --- a/esphome/components/speaker/media_player/speaker_media_player.cpp +++ b/esphome/components/speaker/media_player/speaker_media_player.cpp @@ -1,6 +1,6 @@ #include "speaker_media_player.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "esphome/core/log.h" diff --git a/esphome/components/speaker/media_player/speaker_media_player.h b/esphome/components/speaker/media_player/speaker_media_player.h index f1c564b63d..065926d0cf 100644 --- a/esphome/components/speaker/media_player/speaker_media_player.h +++ b/esphome/components/speaker/media_player/speaker_media_player.h @@ -1,6 +1,6 @@ #pragma once -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #include "audio_pipeline.h" diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index ad279dcf1a..045cdd09d3 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -311,7 +311,7 @@ def spi_mode_schema(mode): if mode == TYPE_SINGLE: return SPI_SINGLE_SCHEMA pin_count = 4 if mode == TYPE_QUAD else 8 - onlys = [cv.only_on([PLATFORM_ESP32]), cv.only_with_esp_idf] + onlys = [cv.only_on([PLATFORM_ESP32])] if pin_count == 8: onlys.append( only_on_variant( @@ -399,7 +399,7 @@ def spi_device_schema( cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum( SPI_MODE_OPTIONS, upper=True ), - cv.Optional(CONF_RELEASE_DEVICE): cv.All(cv.boolean, cv.only_with_esp_idf), + cv.Optional(CONF_RELEASE_DEVICE): cv.All(cv.boolean, cv.only_on_esp32), } if cs_pin_required: schema[cv.Required(CONF_CS_PIN)] = pins.gpio_output_pin_schema diff --git a/esphome/components/st7701s/display.py b/esphome/components/st7701s/display.py index 6e4bff6431..3078158d25 100644 --- a/esphome/components/st7701s/display.py +++ b/esphome/components/st7701s/display.py @@ -161,8 +161,8 @@ CONFIG_SCHEMA = cv.All( } ).extend(spi.spi_device_schema(cs_pin_required=False, default_data_rate=1e6)) ), + cv.only_on_esp32, only_on_variant(supported=[VARIANT_ESP32S3]), - cv.only_with_esp_idf, ) FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( diff --git a/esphome/components/sx126x/sx126x.cpp b/esphome/components/sx126x/sx126x.cpp index bb59f26b79..707d6f1fbf 100644 --- a/esphome/components/sx126x/sx126x.cpp +++ b/esphome/components/sx126x/sx126x.cpp @@ -527,7 +527,9 @@ void SX126x::dump_config() { this->spreading_factor_, cr, this->preamble_size_); } if (!this->sync_value_.empty()) { - ESP_LOGCONFIG(TAG, " Sync Value: 0x%s", format_hex(this->sync_value_).c_str()); + char hex_buf[17]; // 8 bytes max = 16 hex chars + null + ESP_LOGCONFIG(TAG, " Sync Value: 0x%s", + format_hex_to(hex_buf, this->sync_value_.data(), this->sync_value_.size())); } if (this->is_failed()) { ESP_LOGE(TAG, "Configuring SX126x failed"); diff --git a/esphome/components/sx127x/sx127x.cpp b/esphome/components/sx127x/sx127x.cpp index 8e6db5dc9e..3185574b1a 100644 --- a/esphome/components/sx127x/sx127x.cpp +++ b/esphome/components/sx127x/sx127x.cpp @@ -476,7 +476,9 @@ void SX127x::dump_config() { ESP_LOGCONFIG(TAG, " Payload Length: %" PRIu32, this->payload_length_); } if (!this->sync_value_.empty()) { - ESP_LOGCONFIG(TAG, " Sync Value: 0x%s", format_hex(this->sync_value_).c_str()); + char hex_buf[17]; // 8 bytes max = 16 hex chars + null + ESP_LOGCONFIG(TAG, " Sync Value: 0x%s", + format_hex_to(hex_buf, this->sync_value_.data(), this->sync_value_.size())); } if (this->preamble_size_ > 0 || this->preamble_detect_ > 0) { ESP_LOGCONFIG(TAG, diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index ad1dc0f521..8dfb9dad05 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -78,8 +78,8 @@ void TextSensor::add_on_raw_state_callback(std::functionraw_callback_.add(std::move(callback)); } -std::string TextSensor::get_state() const { return this->state; } -std::string TextSensor::get_raw_state() const { +const std::string &TextSensor::get_state() const { return this->state; } +const std::string &TextSensor::get_raw_state() const { // Suppress deprecation warning - get_raw_state() is the replacement API #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index 919bf81c8c..2cd8a65e87 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -37,9 +37,9 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass { #pragma GCC diagnostic pop /// Getter-syntax for .state. - std::string get_state() const; + const std::string &get_state() const; /// Getter-syntax for .raw_state - std::string get_raw_state() const; + const std::string &get_raw_state() const; void publish_state(const std::string &state); diff --git a/esphome/components/udp/udp_component.cpp b/esphome/components/udp/udp_component.cpp index 9105ced21e..daa6c52f98 100644 --- a/esphome/components/udp/udp_component.cpp +++ b/esphome/components/udp/udp_component.cpp @@ -130,7 +130,8 @@ void UDPComponent::dump_config() { for (const auto &address : this->addresses_) ESP_LOGCONFIG(TAG, " Address: %s", address.c_str()); if (this->listen_address_.has_value()) { - ESP_LOGCONFIG(TAG, " Listen address: %s", this->listen_address_.value().str().c_str()); + char addr_buf[network::IP_ADDRESS_BUFFER_SIZE]; + ESP_LOGCONFIG(TAG, " Listen address: %s", this->listen_address_.value().str_to(addr_buf)); } ESP_LOGCONFIG(TAG, " Broadcasting: %s\n" diff --git a/esphome/components/usb_host/__init__.py b/esphome/components/usb_host/__init__.py index cccabcf646..9e058ee20b 100644 --- a/esphome/components/usb_host/__init__.py +++ b/esphome/components/usb_host/__init__.py @@ -53,7 +53,6 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_DEVICES): cv.ensure_list(usb_device_schema()), } ), - cv.only_with_esp_idf, only_on_variant(supported=[VARIANT_ESP32S2, VARIANT_ESP32S3, VARIANT_ESP32P4]), ) diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 551f0370f2..b946e3b38a 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -947,7 +947,7 @@ void VoiceAssistant::on_set_configuration(const std::vector &active } // Enable only active wake words - for (auto ww_id : active_wake_words) { + for (const auto &ww_id : active_wake_words) { for (auto &model : this->micro_wake_word_->get_wake_words()) { if (model->get_id() == ww_id) { model->enable(); diff --git a/esphome/components/web_server/list_entities.cpp b/esphome/components/web_server/list_entities.cpp index 16b1d1e797..55beed812f 100644 --- a/esphome/components/web_server/list_entities.cpp +++ b/esphome/components/web_server/list_entities.cpp @@ -6,8 +6,7 @@ #include "web_server.h" -namespace esphome { -namespace web_server { +namespace esphome::web_server { #ifdef USE_ESP32 ListEntitiesIterator::ListEntitiesIterator(const WebServer *ws, AsyncEventSource *es) : web_server_(ws), events_(es) {} @@ -157,6 +156,5 @@ bool ListEntitiesIterator::on_update(update::UpdateEntity *obj) { } #endif -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif diff --git a/esphome/components/web_server/list_entities.h b/esphome/components/web_server/list_entities.h index 5d9049b082..56fd91a8c6 100644 --- a/esphome/components/web_server/list_entities.h +++ b/esphome/components/web_server/list_entities.h @@ -4,13 +4,13 @@ #ifdef USE_WEBSERVER #include "esphome/core/component.h" #include "esphome/core/component_iterator.h" -namespace esphome { +namespace esphome::web_server_idf { #ifdef USE_ESP32 -namespace web_server_idf { class AsyncEventSource; -} #endif -namespace web_server { +} // namespace esphome::web_server_idf + +namespace esphome::web_server { #if !defined(USE_ESP32) && defined(USE_ARDUINO) class DeferredUpdateEventSource; @@ -99,6 +99,5 @@ class ListEntitiesIterator : public ComponentIterator { #endif }; -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif diff --git a/esphome/components/web_server/ota/ota_web_server.cpp b/esphome/components/web_server/ota/ota_web_server.cpp index f612aa056c..3793f01eb5 100644 --- a/esphome/components/web_server/ota/ota_web_server.cpp +++ b/esphome/components/web_server/ota/ota_web_server.cpp @@ -10,9 +10,7 @@ #endif #ifdef USE_ARDUINO -#ifdef USE_ESP8266 -#include -#elif defined(USE_ESP32) || defined(USE_LIBRETINY) +#if defined(USE_LIBRETINY) #include #endif #endif // USE_ARDUINO @@ -23,8 +21,7 @@ using PlatformString = std::string; using PlatformString = String; #endif -namespace esphome { -namespace web_server { +namespace esphome::web_server { static const char *const TAG = "web_server.ota"; @@ -121,10 +118,7 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf // Platform-specific pre-initialization #ifdef USE_ARDUINO -#ifdef USE_ESP8266 - Update.runAsync(true); -#endif -#if defined(USE_ESP32) || defined(USE_LIBRETINY) +#if defined(USE_LIBRETINY) if (Update.isRunning()) { Update.abort(); } @@ -236,7 +230,6 @@ void WebServerOTAComponent::setup() { void WebServerOTAComponent::dump_config() { ESP_LOGCONFIG(TAG, "Web Server OTA"); } -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif // USE_WEBSERVER_OTA diff --git a/esphome/components/web_server/ota/ota_web_server.h b/esphome/components/web_server/ota/ota_web_server.h index a7170c0e34..53ff99899c 100644 --- a/esphome/components/web_server/ota/ota_web_server.h +++ b/esphome/components/web_server/ota/ota_web_server.h @@ -7,8 +7,7 @@ #include "esphome/components/web_server_base/web_server_base.h" #include "esphome/core/component.h" -namespace esphome { -namespace web_server { +namespace esphome::web_server { class WebServerOTAComponent : public ota::OTAComponent { public: @@ -20,7 +19,6 @@ class WebServerOTAComponent : public ota::OTAComponent { friend class OTARequestHandler; }; -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif // USE_WEBSERVER_OTA diff --git a/esphome/components/web_server/server_index_v2.h b/esphome/components/web_server/server_index_v2.h index e675d81552..b2d204c9e7 100644 --- a/esphome/components/web_server/server_index_v2.h +++ b/esphome/components/web_server/server_index_v2.h @@ -6,8 +6,7 @@ #include "esphome/core/hal.h" -namespace esphome { -namespace web_server { +namespace esphome::web_server { const uint8_t INDEX_GZ[] PROGMEM = { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xcd, 0x7d, 0xdb, 0x72, 0xdb, 0xc6, 0xb6, 0xe0, 0xf3, @@ -644,8 +643,7 @@ const uint8_t INDEX_GZ[] PROGMEM = { 0x2b, 0x4d, 0x17, 0xb8, 0x87, 0x4c, 0xe9, 0x50, 0x19, 0x14, 0xba, 0x92, 0xde, 0x0a, 0xea, 0x97, 0xce, 0xad, 0x80, 0x4f, 0xc7, 0xf5, 0xfe, 0x1f, 0xe7, 0xe0, 0x1c, 0x12, 0xcf, 0x89, 0x00, 0x00}; -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif #endif diff --git a/esphome/components/web_server/server_index_v3.h b/esphome/components/web_server/server_index_v3.h index 39518197a3..8a8ced9153 100644 --- a/esphome/components/web_server/server_index_v3.h +++ b/esphome/components/web_server/server_index_v3.h @@ -6,8 +6,7 @@ #include "esphome/core/hal.h" -namespace esphome { -namespace web_server { +namespace esphome::web_server { const uint8_t INDEX_GZ[] PROGMEM = { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xcc, 0xbd, 0xeb, 0x7a, 0x1b, 0xb7, 0xb2, 0x20, 0xfa, @@ -4048,8 +4047,7 @@ const uint8_t INDEX_GZ[] PROGMEM = { 0x3b, 0x6c, 0x78, 0x02, 0xa6, 0xdc, 0xb4, 0xe8, 0xee, 0x6a, 0xc5, 0x97, 0x94, 0x7e, 0xd1, 0x9b, 0x83, 0x45, 0xb2, 0xf4, 0x87, 0xff, 0x07, 0x52, 0xaf, 0x09, 0x6c, 0x30, 0x6a, 0x03, 0x00}; -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif #endif diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index df8a5364cf..f613d6bc36 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -36,8 +36,7 @@ #endif #endif -namespace esphome { -namespace web_server { +namespace esphome::web_server { static const char *const TAG = "web_server"; @@ -45,13 +44,6 @@ static const char *const TAG = "web_server"; static constexpr size_t PSTR_LOCAL_SIZE = 18; #define PSTR_LOCAL(mode_s) ESPHOME_strncpy_P(buf, (ESPHOME_PGM_P) ((mode_s)), PSTR_LOCAL_SIZE - 1) -#ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS -static const char *const HEADER_PNA_NAME = "Private-Network-Access-Name"; -static const char *const HEADER_PNA_ID = "Private-Network-Access-ID"; -static const char *const HEADER_CORS_REQ_PNA = "Access-Control-Request-Private-Network"; -static const char *const HEADER_CORS_ALLOW_PNA = "Access-Control-Allow-Private-Network"; -#endif - // Parse URL and return match info static UrlMatch match_url(const char *url_ptr, size_t url_len, bool only_domain) { UrlMatch match{}; @@ -348,7 +340,7 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { #else AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ)); #endif - response->addHeader("Content-Encoding", "gzip"); + response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("gzip")); request->send(response); } #elif USE_WEBSERVER_VERSION >= 2 @@ -368,10 +360,10 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS void WebServer::handle_pna_cors_request(AsyncWebServerRequest *request) { AsyncWebServerResponse *response = request->beginResponse(200, ""); - response->addHeader(HEADER_CORS_ALLOW_PNA, "true"); - response->addHeader(HEADER_PNA_NAME, App.get_name().c_str()); + response->addHeader(ESPHOME_F("Access-Control-Allow-Private-Network"), ESPHOME_F("true")); + response->addHeader(ESPHOME_F("Private-Network-Access-Name"), App.get_name().c_str()); char mac_s[18]; - response->addHeader(HEADER_PNA_ID, get_mac_address_pretty_into_buffer(mac_s)); + response->addHeader(ESPHOME_F("Private-Network-Access-ID"), get_mac_address_pretty_into_buffer(mac_s)); request->send(response); } #endif @@ -385,7 +377,7 @@ void WebServer::handle_css_request(AsyncWebServerRequest *request) { AsyncWebServerResponse *response = request->beginResponse_P(200, "text/css", ESPHOME_WEBSERVER_CSS_INCLUDE, ESPHOME_WEBSERVER_CSS_INCLUDE_SIZE); #endif - response->addHeader("Content-Encoding", "gzip"); + response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("gzip")); request->send(response); } #endif @@ -399,7 +391,7 @@ void WebServer::handle_js_request(AsyncWebServerRequest *request) { AsyncWebServerResponse *response = request->beginResponse_P(200, "text/javascript", ESPHOME_WEBSERVER_JS_INCLUDE, ESPHOME_WEBSERVER_JS_INCLUDE_SIZE); #endif - response->addHeader("Content-Encoding", "gzip"); + response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("gzip")); request->send(response); } #endif @@ -1841,7 +1833,7 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) const { } #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS - if (method == HTTP_OPTIONS && request->hasHeader(HEADER_CORS_REQ_PNA)) + if (method == HTTP_OPTIONS && request->hasHeader(ESPHOME_F("Access-Control-Request-Private-Network"))) return true; #endif @@ -1974,7 +1966,7 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { #endif #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS - if (request->method() == HTTP_OPTIONS && request->hasHeader(HEADER_CORS_REQ_PNA)) { + if (request->method() == HTTP_OPTIONS && request->hasHeader(ESPHOME_F("Access-Control-Request-Private-Network"))) { this->handle_pna_cors_request(request); return; } @@ -2112,6 +2104,5 @@ void WebServer::add_sorting_group(uint64_t group_id, const std::string &group_na } #endif -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 0078146284..b9e852c745 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -33,8 +33,7 @@ extern const uint8_t ESPHOME_WEBSERVER_JS_INCLUDE[] PROGMEM; extern const size_t ESPHOME_WEBSERVER_JS_INCLUDE_SIZE; #endif -namespace esphome { -namespace web_server { +namespace esphome::web_server { /// Internal helper struct that is used to parse incoming URLs struct UrlMatch { @@ -616,6 +615,5 @@ class WebServer : public Controller, #endif }; -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif diff --git a/esphome/components/web_server/web_server_v1.cpp b/esphome/components/web_server/web_server_v1.cpp index cbc25b9dec..e27306ad78 100644 --- a/esphome/components/web_server/web_server_v1.cpp +++ b/esphome/components/web_server/web_server_v1.cpp @@ -3,8 +3,7 @@ #if USE_WEBSERVER_VERSION == 1 -namespace esphome { -namespace web_server { +namespace esphome::web_server { void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &klass, const std::string &action, const std::function &action_func = nullptr) { @@ -215,6 +214,5 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { request->send(stream); } -} // namespace web_server -} // namespace esphome +} // namespace esphome::web_server #endif diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index 54ec997671..7e95e00f29 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -100,7 +100,7 @@ class WebServerBase : public Component { } this->server_ = std::make_unique(this->port_); // All content is controlled and created by user - so allowing all origins is fine here. - DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); + DefaultHeaders::Instance().addHeader(ESPHOME_F("Access-Control-Allow-Origin"), ESPHOME_F("*")); this->server_->begin(); for (auto *handler : this->handlers_) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 242265344d..50c0938cf1 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -899,12 +899,20 @@ void WiFiComponent::print_connect_params_() { ESP_LOGCONFIG(TAG, " Disabled"); return; } + // Use stack buffers for IP address formatting to avoid heap allocations + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; for (auto &ip : wifi_sta_ip_addresses()) { if (ip.is_set()) { - ESP_LOGCONFIG(TAG, " IP Address: %s", ip.str().c_str()); + ESP_LOGCONFIG(TAG, " IP Address: %s", ip.str_to(ip_buf)); } } int8_t rssi = wifi_rssi(); + // Use stack buffers for SSID and all IP addresses to avoid heap allocations + char ssid_buf[SSID_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]; ESP_LOGCONFIG(TAG, " SSID: " LOG_SECRET("'%s'") "\n" " BSSID: " LOG_SECRET("%s") "\n" @@ -915,9 +923,9 @@ void WiFiComponent::print_connect_params_() { " Gateway: %s\n" " DNS1: %s\n" " DNS2: %s", - wifi_ssid().c_str(), bssid_s, App.get_name().c_str(), rssi, LOG_STR_ARG(get_signal_bars(rssi)), - get_wifi_channel(), wifi_subnet_mask_().str().c_str(), wifi_gateway_ip_().str().c_str(), - wifi_dns_ip_(0).str().c_str(), wifi_dns_ip_(1).str().c_str()); + wifi_ssid_to(ssid_buf), bssid_s, App.get_name().c_str(), rssi, LOG_STR_ARG(get_signal_bars(rssi)), + get_wifi_channel(), wifi_subnet_mask_().str_to(subnet_buf), wifi_gateway_ip_().str_to(gateway_buf), + wifi_dns_ip_(0).str_to(dns1_buf), wifi_dns_ip_(1).str_to(dns2_buf)); #ifdef ESPHOME_LOG_HAS_VERBOSE if (const WiFiAP *config = this->get_selected_sta_(); config && config->has_bssid()) { ESP_LOGV(TAG, " Priority: %d", this->get_sta_priority(config->get_bssid())); @@ -1523,12 +1531,12 @@ void WiFiComponent::log_and_adjust_priority_for_failed_connect_() { return; // No BSSID to penalize } - // Get SSID for logging - std::string ssid; + // Get SSID for logging (use pointer to avoid copy) + const std::string *ssid = nullptr; if (this->retry_phase_ == WiFiRetryPhase::SCAN_CONNECTING && !this->scan_result_.empty()) { - ssid = this->scan_result_[0].get_ssid(); + ssid = &this->scan_result_[0].get_ssid(); } else if (const WiFiAP *config = this->get_selected_sta_()) { - ssid = config->get_ssid(); + ssid = &config->get_ssid(); } // Only decrease priority on the last attempt for this phase @@ -1548,8 +1556,8 @@ void WiFiComponent::log_and_adjust_priority_for_failed_connect_() { } char bssid_s[18]; format_mac_addr_upper(failed_bssid.value().data(), bssid_s); - ESP_LOGD(TAG, "Failed " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") ", priority %d → %d", ssid.c_str(), bssid_s, - old_priority, new_priority); + ESP_LOGD(TAG, "Failed " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") ", priority %d → %d", + ssid != nullptr ? ssid->c_str() : "", bssid_s, old_priority, new_priority); // After adjusting priority, check if all priorities are now at minimum // If so, clear the vector to save memory and reset for fresh start diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 604efa8a7e..ff2bfe12a4 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -6,7 +6,9 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" +#include "esphome/core/string_ref.h" +#include #include #include @@ -59,6 +61,9 @@ namespace esphome::wifi { /// Sentinel value for RSSI when WiFi is not connected static constexpr int8_t WIFI_RSSI_DISCONNECTED = -127; +/// Buffer size for SSID (IEEE 802.11 max 32 bytes + null terminator) +static constexpr size_t SSID_BUFFER_SIZE = 33; + struct SavedWifiSettings { char ssid[33]; char password[65]; @@ -274,7 +279,7 @@ class WiFiScanResultsListener { */ class WiFiConnectStateListener { public: - virtual void on_wifi_connect_state(const std::string &ssid, const bssid_t &bssid) = 0; + virtual void on_wifi_connect_state(StringRef ssid, std::span bssid) = 0; }; /** Listener interface for WiFi power save mode changes. @@ -406,6 +411,9 @@ class WiFiComponent : public Component { network::IPAddresses wifi_sta_ip_addresses(); std::string wifi_ssid(); + /// Write SSID to buffer without heap allocation. + /// Returns pointer to buffer, or empty string if not connected. + const char *wifi_ssid_to(std::span buffer); bssid_t wifi_bssid(); int8_t wifi_rssi(); diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 550b5579ff..1c744648bb 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -526,7 +526,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { s_sta_connected = true; #ifdef USE_WIFI_LISTENERS for (auto *listener : global_wifi_component->connect_state_listeners_) { - listener->on_wifi_connect_state(global_wifi_component->wifi_ssid(), global_wifi_component->wifi_bssid()); + listener->on_wifi_connect_state(StringRef(buf, it.ssid_len), it.bssid); } // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here #ifdef USE_WIFI_MANUAL_IP @@ -559,8 +559,9 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { s_sta_connected = false; s_sta_connecting = false; #ifdef USE_WIFI_LISTENERS + static constexpr uint8_t EMPTY_BSSID[6] = {}; for (auto *listener : global_wifi_component->connect_state_listeners_) { - listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0})); + listener->on_wifi_connect_state(StringRef(), EMPTY_BSSID); } #endif break; @@ -912,6 +913,18 @@ bssid_t WiFiComponent::wifi_bssid() { return bssid; } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } +const char *WiFiComponent::wifi_ssid_to(std::span buffer) { + struct station_config conf {}; + if (!wifi_station_get_config(&conf)) { + buffer[0] = '\0'; + return buffer.data(); + } + // conf.ssid is uint8[32], not null-terminated if full + size_t len = strnlen(reinterpret_cast(conf.ssid), sizeof(conf.ssid)); + memcpy(buffer.data(), conf.ssid, len); + buffer[len] = '\0'; + return buffer.data(); +} int8_t WiFiComponent::wifi_rssi() { if (WiFi.status() != WL_CONNECTED) return WIFI_RSSI_DISCONNECTED; diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 212514af93..b26ac3d2e2 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -737,7 +737,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { s_sta_connected = true; #ifdef USE_WIFI_LISTENERS for (auto *listener : this->connect_state_listeners_) { - listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid()); + listener->on_wifi_connect_state(StringRef(buf, it.ssid_len), it.bssid); } // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here #ifdef USE_WIFI_MANUAL_IP @@ -772,8 +772,9 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { s_sta_connecting = false; error_from_callback_ = true; #ifdef USE_WIFI_LISTENERS + static constexpr uint8_t EMPTY_BSSID[6] = {}; for (auto *listener : this->connect_state_listeners_) { - listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0})); + listener->on_wifi_connect_state(StringRef(), EMPTY_BSSID); } #endif @@ -1085,6 +1086,19 @@ std::string WiFiComponent::wifi_ssid() { size_t len = strnlen(ssid_s, sizeof(info.ssid)); return {ssid_s, len}; } +const char *WiFiComponent::wifi_ssid_to(std::span buffer) { + wifi_ap_record_t info{}; + esp_err_t err = esp_wifi_sta_get_ap_info(&info); + if (err != ESP_OK) { + buffer[0] = '\0'; + return buffer.data(); + } + // info.ssid is uint8[33], but only 32 bytes are SSID data + size_t len = strnlen(reinterpret_cast(info.ssid), 32); + memcpy(buffer.data(), info.ssid, len); + buffer[len] = '\0'; + return buffer.data(); +} int8_t WiFiComponent::wifi_rssi() { wifi_ap_record_t info; esp_err_t err = esp_wifi_sta_get_ap_info(&info); diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 340537b228..9b8653d0db 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -303,7 +303,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); #ifdef USE_WIFI_LISTENERS for (auto *listener : this->connect_state_listeners_) { - listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid()); + listener->on_wifi_connect_state(StringRef(buf, it.ssid_len), it.bssid); } // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here #ifdef USE_WIFI_MANUAL_IP @@ -357,8 +357,9 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ s_sta_connecting = false; #ifdef USE_WIFI_LISTENERS + static constexpr uint8_t EMPTY_BSSID[6] = {}; for (auto *listener : this->connect_state_listeners_) { - listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0})); + listener->on_wifi_connect_state(StringRef(), EMPTY_BSSID); } #endif break; @@ -553,6 +554,14 @@ bssid_t WiFiComponent::wifi_bssid() { return bssid; } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } +const char *WiFiComponent::wifi_ssid_to(std::span buffer) { + // TODO: Find direct LibreTiny API to avoid Arduino String allocation + String ssid = WiFi.SSID(); + size_t len = std::min(static_cast(ssid.length()), SSID_BUFFER_SIZE - 1); + memcpy(buffer.data(), ssid.c_str(), len); + buffer[len] = '\0'; + return buffer.data(); +} int8_t WiFiComponent::wifi_rssi() { return WiFi.status() == WL_CONNECTED ? WiFi.RSSI() : WIFI_RSSI_DISCONNECTED; } int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; } diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index 61709852ff..1aa737ff4a 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -214,6 +214,14 @@ bssid_t WiFiComponent::wifi_bssid() { return bssid; } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } +const char *WiFiComponent::wifi_ssid_to(std::span buffer) { + // TODO: Find direct CYW43 API to avoid Arduino String allocation + String ssid = WiFi.SSID(); + size_t len = std::min(static_cast(ssid.length()), SSID_BUFFER_SIZE - 1); + memcpy(buffer.data(), ssid.c_str(), len); + buffer[len] = '\0'; + return buffer.data(); +} int8_t WiFiComponent::wifi_rssi() { return WiFi.status() == WL_CONNECTED ? WiFi.RSSI() : WIFI_RSSI_DISCONNECTED; } int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } @@ -256,8 +264,10 @@ void WiFiComponent::wifi_loop_() { s_sta_was_connected = true; ESP_LOGV(TAG, "Connected"); #ifdef USE_WIFI_LISTENERS + String ssid = WiFi.SSID(); + bssid_t bssid = this->wifi_bssid(); for (auto *listener : this->connect_state_listeners_) { - listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid()); + listener->on_wifi_connect_state(StringRef(ssid.c_str(), ssid.length()), bssid); } // For static IP configurations, notify IP listeners immediately as the IP is already configured #ifdef USE_WIFI_MANUAL_IP @@ -275,8 +285,9 @@ void WiFiComponent::wifi_loop_() { s_sta_had_ip = false; ESP_LOGV(TAG, "Disconnected"); #ifdef USE_WIFI_LISTENERS + static constexpr uint8_t EMPTY_BSSID[6] = {}; for (auto *listener : this->connect_state_listeners_) { - listener->on_wifi_connect_state("", bssid_t({0, 0, 0, 0, 0, 0})); + listener->on_wifi_connect_state(StringRef(), EMPTY_BSSID); } #endif } diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.cpp b/esphome/components/wifi_info/wifi_info_text_sensor.cpp index 56cf49028c..0cca3e16ef 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.cpp +++ b/esphome/components/wifi_info/wifi_info_text_sensor.cpp @@ -46,8 +46,13 @@ void DNSAddressWifiInfo::dump_config() { LOG_TEXT_SENSOR("", "DNS Address", this void DNSAddressWifiInfo::on_ip_state(const network::IPAddresses &ips, const network::IPAddress &dns1, const network::IPAddress &dns2) { - std::string dns_results = dns1.str() + " " + dns2.str(); - this->publish_state(dns_results); + // IP_ADDRESS_BUFFER_SIZE (40) = max IP (39) + null; space reuses first null's slot + char buf[network::IP_ADDRESS_BUFFER_SIZE * 2]; + dns1.str_to(buf); + size_t len1 = strlen(buf); + buf[len1] = ' '; + dns2.str_to(buf + len1 + 1); + this->publish_state(buf); } /********************** @@ -58,22 +63,36 @@ void ScanResultsWiFiInfo::setup() { wifi::global_wifi_component->add_scan_result void ScanResultsWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "Scan Results", this); } +// Format: "SSID: -XXdB\n" - caller must ensure ssid_len + 9 bytes available in buffer +static char *format_scan_entry(char *buf, const char *ssid, size_t ssid_len, int8_t rssi) { + memcpy(buf, ssid, ssid_len); + buf += ssid_len; + *buf++ = ':'; + *buf++ = ' '; + buf = int8_to_str(buf, rssi); + *buf++ = 'd'; + *buf++ = 'B'; + *buf++ = '\n'; + return buf; +} + void ScanResultsWiFiInfo::on_wifi_scan_results(const wifi::wifi_scan_vector_t &results) { - std::string scan_results; + char buf[MAX_STATE_LENGTH + 1]; + char *ptr = buf; + const char *end = buf + MAX_STATE_LENGTH; + for (const auto &scan : results) { if (scan.get_is_hidden()) continue; + const std::string &ssid = scan.get_ssid(); + // Max space: ssid + ": " (2) + "-128" (4) + "dB\n" (3) = ssid + 9 + if (ptr + ssid.size() + 9 > end) + break; + ptr = format_scan_entry(ptr, ssid.c_str(), ssid.size(), scan.get_rssi()); + } - scan_results += scan.get_ssid(); - scan_results += ": "; - scan_results += esphome::to_string(scan.get_rssi()); - scan_results += "dB\n"; - } - // There's a limit of 255 characters per state; longer states just don't get sent so we truncate it - if (scan_results.length() > MAX_STATE_LENGTH) { - scan_results.resize(MAX_STATE_LENGTH); - } - this->publish_state(scan_results); + *ptr = '\0'; + this->publish_state(buf); } /*************** @@ -84,8 +103,8 @@ void SSIDWiFiInfo::setup() { wifi::global_wifi_component->add_connect_state_list void SSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "SSID", this); } -void SSIDWiFiInfo::on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) { - this->publish_state(ssid); +void SSIDWiFiInfo::on_wifi_connect_state(StringRef ssid, std::span bssid) { + this->publish_state(ssid.str()); } /**************** @@ -96,7 +115,7 @@ void BSSIDWiFiInfo::setup() { wifi::global_wifi_component->add_connect_state_lis void BSSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "BSSID", this); } -void BSSIDWiFiInfo::on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) { +void BSSIDWiFiInfo::on_wifi_connect_state(StringRef ssid, std::span bssid) { char buf[18] = "unknown"; if (mac_address_is_valid(bssid.data())) { format_mac_addr_upper(bssid.data(), buf); diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.h b/esphome/components/wifi_info/wifi_info_text_sensor.h index b2242372da..6beb1372f5 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.h +++ b/esphome/components/wifi_info/wifi_info_text_sensor.h @@ -2,10 +2,12 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" +#include "esphome/core/string_ref.h" #include "esphome/components/text_sensor/text_sensor.h" #include "esphome/components/wifi/wifi_component.h" #ifdef USE_WIFI #include +#include namespace esphome::wifi_info { @@ -52,7 +54,7 @@ class SSIDWiFiInfo final : public Component, public text_sensor::TextSensor, pub void dump_config() override; // WiFiConnectStateListener interface - void on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) override; + void on_wifi_connect_state(StringRef ssid, std::span bssid) override; }; class BSSIDWiFiInfo final : public Component, public text_sensor::TextSensor, public wifi::WiFiConnectStateListener { @@ -61,7 +63,7 @@ class BSSIDWiFiInfo final : public Component, public text_sensor::TextSensor, pu void dump_config() override; // WiFiConnectStateListener interface - void on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) override; + void on_wifi_connect_state(StringRef ssid, std::span bssid) override; }; class PowerSaveModeWiFiInfo final : public Component, diff --git a/esphome/components/wifi_signal/wifi_signal_sensor.h b/esphome/components/wifi_signal/wifi_signal_sensor.h index 9f581f1eb2..2e1f8cbb2b 100644 --- a/esphome/components/wifi_signal/wifi_signal_sensor.h +++ b/esphome/components/wifi_signal/wifi_signal_sensor.h @@ -2,9 +2,11 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" +#include "esphome/core/string_ref.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/wifi/wifi_component.h" #ifdef USE_WIFI +#include namespace esphome::wifi_signal { #ifdef USE_WIFI_LISTENERS @@ -28,7 +30,7 @@ class WiFiSignalSensor : public sensor::Sensor, public PollingComponent { #ifdef USE_WIFI_LISTENERS // WiFiConnectStateListener interface - update RSSI immediately on connect - void on_wifi_connect_state(const std::string &ssid, const wifi::bssid_t &bssid) override { this->update(); } + void on_wifi_connect_state(StringRef ssid, std::span bssid) override { this->update(); } #endif }; diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 08fffa6cec..d085206ee8 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -697,7 +697,16 @@ only_on_esp32 = only_on(PLATFORM_ESP32) only_on_esp8266 = only_on(PLATFORM_ESP8266) only_on_rp2040 = only_on(PLATFORM_RP2040) only_with_arduino = only_with_framework(Framework.ARDUINO) -only_with_esp_idf = only_with_framework(Framework.ESP_IDF) + + +def only_with_esp_idf(obj): + """Deprecated: use only_on_esp32 instead.""" + _LOGGER.warning( + "cv.only_with_esp_idf was deprecated in 2026.1, will change behavior in 2026.6. " + "ESP32 Arduino builds on top of ESP-IDF, so ESP-IDF features are available in both frameworks. " + "Use cv.only_on_esp32 and/or cv.only_with_arduino instead." + ) + return only_with_framework(Framework.ESP_IDF)(obj) # Adapted from: diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 11c5062140..a269f40479 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -164,13 +164,9 @@ #define USE_I2S_LEGACY #endif -// IDF-specific feature flags -#ifdef USE_ESP_IDF -#define USE_MQTT_IDF_ENQUEUE -#endif - // ESP32-specific feature flags #ifdef USE_ESP32 +#define USE_MQTT_IDF_ENQUEUE #define USE_ESPHOME_TASK_LOG_BUFFER #define USE_OTA_ROLLBACK @@ -231,7 +227,7 @@ #define USE_ETHERNET_MANUAL_IP #endif -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 #define USE_MICRO_WAKE_WORD #define USE_MICRO_WAKE_WORD_VAD #if defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index d7d32ea28f..5e361ecce2 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -297,6 +297,19 @@ std::string format_hex(const uint8_t *data, size_t length) { } std::string format_hex(const std::vector &data) { return format_hex(data.data(), data.size()); } +char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length) { + size_t max_bytes = (buffer_size - 1) / 2; + if (length > max_bytes) { + length = max_bytes; + } + for (size_t i = 0; i < length; i++) { + buffer[2 * i] = format_hex_char(data[i] >> 4); + buffer[2 * i + 1] = format_hex_char(data[i] & 0x0F); + } + buffer[length * 2] = '\0'; + return buffer; +} + // Shared implementation for uint8_t and string hex formatting static std::string format_hex_pretty_uint8(const uint8_t *data, size_t length, char separator, bool show_length) { if (data == nullptr || length == 0) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 3bbda5f8dd..088214f3f2 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -698,6 +698,30 @@ inline char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' + /// This always uses uppercase (A-F) for pretty/human-readable output inline char format_hex_pretty_char(uint8_t v) { return v >= 10 ? 'A' + (v - 10) : '0' + v; } +/// Write int8 value to buffer without modulo operations. +/// Buffer must have at least 4 bytes free. Returns pointer past last char written. +inline char *int8_to_str(char *buf, int8_t val) { + int32_t v = val; + if (v < 0) { + *buf++ = '-'; + v = -v; + } + if (v >= 100) { + *buf++ = '1'; // int8 max is 128, so hundreds digit is always 1 + v -= 100; + // Must write tens digit (even if 0) after hundreds + int32_t tens = v / 10; + *buf++ = '0' + tens; + v -= tens * 10; + } else if (v >= 10) { + int32_t tens = v / 10; + *buf++ = '0' + tens; + v -= tens * 10; + } + *buf++ = '0' + v; + return buf; +} + /// Format MAC address as XX:XX:XX:XX:XX:XX (uppercase) inline void format_mac_addr_upper(const uint8_t *mac, char *output) { for (size_t i = 0; i < 6; i++) { @@ -720,6 +744,24 @@ inline void format_mac_addr_lower_no_sep(const uint8_t *mac, char *output) { output[12] = '\0'; } +/// Format byte array as lowercase hex to buffer (base implementation). +char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length); + +/// Format byte array as lowercase hex to buffer. Automatically deduces buffer size. +/// Truncates output if data exceeds buffer capacity. Returns pointer to buffer. +template inline char *format_hex_to(char (&buffer)[N], const uint8_t *data, size_t length) { + static_assert(N >= 3, "Buffer must hold at least one hex byte (3 chars)"); + return format_hex_to(buffer, N, data, length); +} + +/// Format an unsigned integer in lowercased hex to buffer, starting with the most significant byte. +template::value, int> = 0> +inline char *format_hex_to(char (&buffer)[N], T val) { + static_assert(N >= sizeof(T) * 2 + 1, "Buffer too small for type"); + val = convert_big_endian(val); + return format_hex_to(buffer, reinterpret_cast(&val), sizeof(T)); +} + /// Format the six-byte array \p mac into a MAC address. std::string format_mac_address_pretty(const uint8_t mac[6]); /// Format the byte array \p data of length \p len in lowercased hex. diff --git a/esphome/core/log.cpp b/esphome/core/log.cpp index 909319dd28..8338efbb33 100644 --- a/esphome/core/log.cpp +++ b/esphome/core/log.cpp @@ -46,7 +46,7 @@ void HOT esp_log_vprintf_(int level, const char *tag, int line, const __FlashStr } #endif -#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) +#ifdef USE_ESP32 int HOT esp_idf_log_vprintf_(const char *format, va_list args) { // NOLINT #ifdef USE_LOGGER auto *log = logger::global_logger; diff --git a/esphome/core/log.h b/esphome/core/log.h index cade6a74c1..a2c4b35c6e 100644 --- a/esphome/core/log.h +++ b/esphome/core/log.h @@ -14,13 +14,10 @@ #endif // Include ESP-IDF/Arduino based logging methods here so they don't undefine ours later -#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) +#if defined(USE_ESP32) #include #include #endif -#ifdef USE_ESP32_FRAMEWORK_ARDUINO -#include -#endif #ifdef USE_LIBRETINY #include #endif @@ -66,7 +63,7 @@ void esp_log_vprintf_(int level, const char *tag, int line, const char *format, #ifdef USE_STORE_LOG_STR_IN_FLASH void esp_log_vprintf_(int level, const char *tag, int line, const __FlashStringHelper *format, va_list args); #endif -#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) +#if defined(USE_ESP32) int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT #endif diff --git a/platformio.ini b/platformio.ini index a27fb1f537..e38e1a5f3c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -156,6 +156,7 @@ lib_deps = esphome/ESP32-audioI2S@2.3.0 ; i2s_audio droscy/esp_wireguard@0.4.2 ; wireguard esphome/esp-audio-libs@2.0.1 ; audio + kahrendt/ESPMicroSpeechFeatures@1.1.0 ; micro_wake_word build_flags = ${common:arduino.build_flags} diff --git a/requirements.txt b/requirements.txt index 5718ced617..e741a70f48 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==5.1.0 click==8.1.7 esphome-dashboard==20251013.0 -aioesphomeapi==43.5.0 +aioesphomeapi==43.6.0 zeroconf==0.148.0 puremagic==1.30 ruamel.yaml==0.18.17 # dashboard_import diff --git a/tests/component_tests/mipi_spi/test_init.py b/tests/component_tests/mipi_spi/test_init.py index 0c7dea2286..f752c41d8c 100644 --- a/tests/component_tests/mipi_spi/test_init.py +++ b/tests/component_tests/mipi_spi/test_init.py @@ -227,23 +227,6 @@ def test_esp32s3_specific_errors( run_schema_validation(config) -def test_framework_specific_errors( - set_core_config: SetCoreConfigCallable, -) -> None: - """Test framework-specific configuration errors""" - - set_core_config( - PlatformFramework.ESP32_ARDUINO, - platform_data={KEY_BOARD: "esp32dev", KEY_VARIANT: VARIANT_ESP32}, - ) - - with pytest.raises( - cv.Invalid, - match=r"This feature is only available with framework\(s\) esp-idf", - ): - run_schema_validation({"model": "wt32-sc01-plus"}) - - def test_custom_model_with_all_options( set_core_config: SetCoreConfigCallable, ) -> None: