From cceab26bfb40a32858b2b31b7b6383c32f8e0b49 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 24 Jun 2025 09:43:40 +1000 Subject: [PATCH 01/16] [lvgl] Fix dangling pointer issue with qrcode (#9190) --- esphome/components/lvgl/widgets/qrcode.py | 10 ++++------ tests/components/lvgl/lvgl-package.yaml | 4 +++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/esphome/components/lvgl/widgets/qrcode.py b/esphome/components/lvgl/widgets/qrcode.py index 742b538938..7d8d13d8c4 100644 --- a/esphome/components/lvgl/widgets/qrcode.py +++ b/esphome/components/lvgl/widgets/qrcode.py @@ -3,7 +3,7 @@ import esphome.config_validation as cv from esphome.const import CONF_SIZE, CONF_TEXT from esphome.cpp_generator import MockObjClass -from ..defines import CONF_MAIN, literal +from ..defines import CONF_MAIN from ..lv_validation import color, color_retmapper, lv_text from ..lvcode import LocalVariable, lv, lv_expr from ..schemas import TEXT_SCHEMA @@ -34,7 +34,7 @@ class QrCodeType(WidgetType): ) def get_uses(self): - return ("canvas", "img") + return ("canvas", "img", "label") def obj_creator(self, parent: MockObjClass, config: dict): dark_color = color_retmapper(config[CONF_DARK_COLOR]) @@ -45,10 +45,8 @@ class QrCodeType(WidgetType): async def to_code(self, w: Widget, config): if (value := config.get(CONF_TEXT)) is not None: value = await lv_text.process(value) - with LocalVariable( - "qr_text", cg.const_char_ptr, value, modifier="" - ) as str_obj: - lv.qrcode_update(w.obj, str_obj, literal(f"strlen({str_obj})")) + with LocalVariable("qr_text", cg.std_string, value, modifier="") as str_obj: + lv.qrcode_update(w.obj, str_obj.c_str(), str_obj.size()) qr_code_spec = QrCodeType() diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index d8452bdd2a..e3391a5bac 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -646,7 +646,9 @@ lvgl: on_click: lvgl.qrcode.update: id: lv_qr - text: homeassistant.io + text: + format: "A string with a number %d" + args: ['(int)(random_uint32() % 1000)'] - slider: min_value: 0 From e146c0796abb32ff605ec68f23be74248c579039 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Thu, 26 Jun 2025 20:11:50 +0100 Subject: [PATCH 02/16] [audio] Bugfix: improve timeout handling (#9221) --- esphome/components/audio/audio_reader.cpp | 62 +++++++++++++++-------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/esphome/components/audio/audio_reader.cpp b/esphome/components/audio/audio_reader.cpp index b82c6db9ee..6966c95db7 100644 --- a/esphome/components/audio/audio_reader.cpp +++ b/esphome/components/audio/audio_reader.cpp @@ -5,6 +5,7 @@ #include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" +#include "esphome/core/log.h" #if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE #include "esp_crt_bundle.h" @@ -16,13 +17,13 @@ namespace audio { static const uint32_t READ_WRITE_TIMEOUT_MS = 20; static const uint32_t CONNECTION_TIMEOUT_MS = 5000; - -// The number of times the http read times out with no data before throwing an error -static const uint32_t ERROR_COUNT_NO_DATA_READ_TIMEOUT = 100; +static const uint8_t MAX_FETCHING_HEADER_ATTEMPTS = 6; static const size_t HTTP_STREAM_BUFFER_SIZE = 2048; -static const uint8_t MAX_REDIRECTION = 5; +static const uint8_t MAX_REDIRECTIONS = 5; + +static const char *const TAG = "audio_reader"; // Some common HTTP status codes - borrowed from http_request component accessed 20241224 enum HttpStatus { @@ -94,7 +95,7 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) { client_config.url = uri.c_str(); client_config.cert_pem = nullptr; client_config.disable_auto_redirect = false; - client_config.max_redirection_count = 10; + client_config.max_redirection_count = MAX_REDIRECTIONS; client_config.event_handler = http_event_handler; client_config.user_data = this; client_config.buffer_size = HTTP_STREAM_BUFFER_SIZE; @@ -116,12 +117,29 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) { esp_err_t err = esp_http_client_open(this->client_, 0); if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to open URL"); this->cleanup_connection_(); return err; } int64_t header_length = esp_http_client_fetch_headers(this->client_); + uint8_t reattempt_count = 0; + while ((header_length < 0) && (reattempt_count < MAX_FETCHING_HEADER_ATTEMPTS)) { + this->cleanup_connection_(); + if (header_length != -ESP_ERR_HTTP_EAGAIN) { + // Serious error, no recovery + return ESP_FAIL; + } else { + // Reconnect from a fresh state to avoid a bug where it never reads the headers even if made available + this->client_ = esp_http_client_init(&client_config); + esp_http_client_open(this->client_, 0); + header_length = esp_http_client_fetch_headers(this->client_); + ++reattempt_count; + } + } + if (header_length < 0) { + ESP_LOGE(TAG, "Failed to fetch headers"); this->cleanup_connection_(); return ESP_FAIL; } @@ -135,7 +153,7 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) { ssize_t redirect_count = 0; - while ((esp_http_client_set_redirection(this->client_) == ESP_OK) && (redirect_count < MAX_REDIRECTION)) { + while ((esp_http_client_set_redirection(this->client_) == ESP_OK) && (redirect_count < MAX_REDIRECTIONS)) { err = esp_http_client_open(this->client_, 0); if (err != ESP_OK) { this->cleanup_connection_(); @@ -267,27 +285,29 @@ AudioReaderState AudioReader::http_read_() { return AudioReaderState::FINISHED; } } else if (this->output_transfer_buffer_->free() > 0) { - size_t bytes_to_read = this->output_transfer_buffer_->free(); - int received_len = - esp_http_client_read(this->client_, (char *) this->output_transfer_buffer_->get_buffer_end(), bytes_to_read); + int received_len = esp_http_client_read(this->client_, (char *) this->output_transfer_buffer_->get_buffer_end(), + this->output_transfer_buffer_->free()); if (received_len > 0) { this->output_transfer_buffer_->increase_buffer_length(received_len); this->last_data_read_ms_ = millis(); - } else if (received_len < 0) { + return AudioReaderState::READING; + } else if (received_len <= 0) { // HTTP read error - this->cleanup_connection_(); - return AudioReaderState::FAILED; - } else { - if (bytes_to_read > 0) { - // Read timed out - if ((millis() - this->last_data_read_ms_) > CONNECTION_TIMEOUT_MS) { - this->cleanup_connection_(); - return AudioReaderState::FAILED; - } - - delay(READ_WRITE_TIMEOUT_MS); + if (received_len == -1) { + // A true connection error occured, no chance at recovery + this->cleanup_connection_(); + return AudioReaderState::FAILED; } + + // Read timed out, manually verify if it has been too long since the last successful read + if ((millis() - this->last_data_read_ms_) > MAX_FETCHING_HEADER_ATTEMPTS * CONNECTION_TIMEOUT_MS) { + ESP_LOGE(TAG, "Timed out"); + this->cleanup_connection_(); + return AudioReaderState::FAILED; + } + + delay(READ_WRITE_TIMEOUT_MS); } } From 068aa0ff1eadedf6895ffe31ba1b817e320fcbb0 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Thu, 26 Jun 2025 20:12:58 +0100 Subject: [PATCH 03/16] [speaker] bugfix: continue to block tasks if stop flag is set (#9222) --- .../speaker/media_player/audio_pipeline.cpp | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/esphome/components/speaker/media_player/audio_pipeline.cpp b/esphome/components/speaker/media_player/audio_pipeline.cpp index ac122b6e0c..333a076bec 100644 --- a/esphome/components/speaker/media_player/audio_pipeline.cpp +++ b/esphome/components/speaker/media_player/audio_pipeline.cpp @@ -343,13 +343,12 @@ void AudioPipeline::read_task(void *params) { xEventGroupSetBits(this_pipeline->event_group_, EventGroupBits::READER_MESSAGE_FINISHED); // Wait until the pipeline notifies us the source of the media file - EventBits_t event_bits = - xEventGroupWaitBits(this_pipeline->event_group_, - EventGroupBits::READER_COMMAND_INIT_FILE | EventGroupBits::READER_COMMAND_INIT_HTTP | - EventGroupBits::PIPELINE_COMMAND_STOP, // Bit message to read - pdFALSE, // Clear the bit on exit - pdFALSE, // Wait for all the bits, - portMAX_DELAY); // Block indefinitely until bit is set + EventBits_t event_bits = xEventGroupWaitBits( + this_pipeline->event_group_, + EventGroupBits::READER_COMMAND_INIT_FILE | EventGroupBits::READER_COMMAND_INIT_HTTP, // Bit message to read + pdFALSE, // Clear the bit on exit + pdFALSE, // Wait for all the bits, + portMAX_DELAY); // Block indefinitely until bit is set if (!(event_bits & EventGroupBits::PIPELINE_COMMAND_STOP)) { xEventGroupClearBits(this_pipeline->event_group_, EventGroupBits::READER_MESSAGE_FINISHED | @@ -434,12 +433,12 @@ void AudioPipeline::decode_task(void *params) { xEventGroupSetBits(this_pipeline->event_group_, EventGroupBits::DECODER_MESSAGE_FINISHED); // Wait until the reader notifies us that the media type is available - EventBits_t event_bits = xEventGroupWaitBits(this_pipeline->event_group_, - EventGroupBits::READER_MESSAGE_LOADED_MEDIA_TYPE | - EventGroupBits::PIPELINE_COMMAND_STOP, // Bit message to read - pdFALSE, // Clear the bit on exit - pdFALSE, // Wait for all the bits, - portMAX_DELAY); // Block indefinitely until bit is set + EventBits_t event_bits = + xEventGroupWaitBits(this_pipeline->event_group_, + EventGroupBits::READER_MESSAGE_LOADED_MEDIA_TYPE, // Bit message to read + pdFALSE, // Clear the bit on exit + pdFALSE, // Wait for all the bits, + portMAX_DELAY); // Block indefinitely until bit is set xEventGroupClearBits(this_pipeline->event_group_, EventGroupBits::DECODER_MESSAGE_FINISHED | EventGroupBits::READER_MESSAGE_LOADED_MEDIA_TYPE); From 9f3f4ead4f71a5af9860a7527ac5d96bd3d3d24e Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Thu, 26 Jun 2025 20:18:51 +0100 Subject: [PATCH 04/16] [voice_assistant] Support streaming TTS responses and fixes crash for long responses (#9224) --- CODEOWNERS | 2 +- .../components/voice_assistant/__init__.py | 13 ++++++- .../voice_assistant/voice_assistant.cpp | 38 ++++++++++++++++++- .../voice_assistant/voice_assistant.h | 4 ++ 4 files changed, 53 insertions(+), 4 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 66ea80f8d6..a0812c9cd6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -490,7 +490,7 @@ esphome/components/vbus/* @ssieb esphome/components/veml3235/* @kbx81 esphome/components/veml7700/* @latonita esphome/components/version/* @esphome/core -esphome/components/voice_assistant/* @jesserockz +esphome/components/voice_assistant/* @jesserockz @kahrendt esphome/components/wake_on_lan/* @clydebarrow @willwill2will54 esphome/components/watchdog/* @oarcher esphome/components/waveshare_epaper/* @clydebarrow diff --git a/esphome/components/voice_assistant/__init__.py b/esphome/components/voice_assistant/__init__.py index b9309ab422..59c7ec8383 100644 --- a/esphome/components/voice_assistant/__init__.py +++ b/esphome/components/voice_assistant/__init__.py @@ -17,10 +17,11 @@ from esphome.const import ( AUTO_LOAD = ["socket"] DEPENDENCIES = ["api", "microphone"] -CODEOWNERS = ["@jesserockz"] +CODEOWNERS = ["@jesserockz", "@kahrendt"] CONF_ON_END = "on_end" CONF_ON_INTENT_END = "on_intent_end" +CONF_ON_INTENT_PROGRESS = "on_intent_progress" CONF_ON_INTENT_START = "on_intent_start" CONF_ON_LISTENING = "on_listening" CONF_ON_START = "on_start" @@ -136,6 +137,9 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_ON_INTENT_START): automation.validate_automation( single=True ), + cv.Optional(CONF_ON_INTENT_PROGRESS): automation.validate_automation( + single=True + ), cv.Optional(CONF_ON_INTENT_END): automation.validate_automation( single=True ), @@ -282,6 +286,13 @@ async def to_code(config): config[CONF_ON_INTENT_START], ) + if CONF_ON_INTENT_PROGRESS in config: + await automation.build_automation( + var.get_intent_progress_trigger(), + [(cg.std_string, "x")], + config[CONF_ON_INTENT_PROGRESS], + ) + if CONF_ON_INTENT_END in config: await automation.build_automation( var.get_intent_end_trigger(), diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index a692a7556e..879d9492f0 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -555,7 +555,7 @@ void VoiceAssistant::request_stop() { break; case State::AWAITING_RESPONSE: this->signal_stop_(); - break; + // Fallthrough intended to stop a streaming TTS announcement that has potentially started case State::STREAMING_RESPONSE: #ifdef USE_MEDIA_PLAYER // Stop any ongoing media player announcement @@ -599,6 +599,14 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { switch (msg.event_type) { case api::enums::VOICE_ASSISTANT_RUN_START: ESP_LOGD(TAG, "Assist Pipeline running"); +#ifdef USE_MEDIA_PLAYER + this->started_streaming_tts_ = false; + for (auto arg : msg.data) { + if (arg.name == "url") { + this->tts_response_url_ = std::move(arg.value); + } + } +#endif this->defer([this]() { this->start_trigger_->trigger(); }); break; case api::enums::VOICE_ASSISTANT_WAKE_WORD_START: @@ -622,6 +630,8 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { if (text.empty()) { ESP_LOGW(TAG, "No text in STT_END event"); return; + } else if (text.length() > 500) { + text = text.substr(0, 497) + "..."; } ESP_LOGD(TAG, "Speech recognised as: \"%s\"", text.c_str()); this->defer([this, text]() { this->stt_end_trigger_->trigger(text); }); @@ -631,6 +641,27 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { ESP_LOGD(TAG, "Intent started"); this->defer([this]() { this->intent_start_trigger_->trigger(); }); break; + case api::enums::VOICE_ASSISTANT_INTENT_PROGRESS: { + ESP_LOGD(TAG, "Intent progress"); + std::string tts_url_for_trigger = ""; +#ifdef USE_MEDIA_PLAYER + if (this->media_player_ != nullptr) { + for (const auto &arg : msg.data) { + if ((arg.name == "tts_start_streaming") && (arg.value == "1") && !this->tts_response_url_.empty()) { + this->media_player_->make_call().set_media_url(this->tts_response_url_).set_announcement(true).perform(); + + this->media_player_wait_for_announcement_start_ = true; + this->media_player_wait_for_announcement_end_ = false; + this->started_streaming_tts_ = true; + tts_url_for_trigger = this->tts_response_url_; + this->tts_response_url_.clear(); // Reset streaming URL + } + } + } +#endif + this->defer([this, tts_url_for_trigger]() { this->intent_progress_trigger_->trigger(tts_url_for_trigger); }); + break; + } case api::enums::VOICE_ASSISTANT_INTENT_END: { for (auto arg : msg.data) { if (arg.name == "conversation_id") { @@ -653,6 +684,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { ESP_LOGW(TAG, "No text in TTS_START event"); return; } + if (text.length() > 500) { + text = text.substr(0, 497) + "..."; + } ESP_LOGD(TAG, "Response: \"%s\"", text.c_str()); this->defer([this, text]() { this->tts_start_trigger_->trigger(text); @@ -678,7 +712,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { ESP_LOGD(TAG, "Response URL: \"%s\"", url.c_str()); this->defer([this, url]() { #ifdef USE_MEDIA_PLAYER - if (this->media_player_ != nullptr) { + if ((this->media_player_ != nullptr) && (!this->started_streaming_tts_)) { this->media_player_->make_call().set_media_url(url).set_announcement(true).perform(); this->media_player_wait_for_announcement_start_ = true; diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index 865731522f..2424ea6052 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -177,6 +177,7 @@ class VoiceAssistant : public Component { Trigger<> *get_intent_end_trigger() const { return this->intent_end_trigger_; } Trigger<> *get_intent_start_trigger() const { return this->intent_start_trigger_; } + Trigger *get_intent_progress_trigger() const { return this->intent_progress_trigger_; } Trigger<> *get_listening_trigger() const { return this->listening_trigger_; } Trigger<> *get_end_trigger() const { return this->end_trigger_; } Trigger<> *get_start_trigger() const { return this->start_trigger_; } @@ -233,6 +234,7 @@ class VoiceAssistant : public Component { Trigger<> *tts_stream_start_trigger_ = new Trigger<>(); Trigger<> *tts_stream_end_trigger_ = new Trigger<>(); #endif + Trigger *intent_progress_trigger_ = new Trigger(); Trigger<> *wake_word_detected_trigger_ = new Trigger<>(); Trigger *stt_end_trigger_ = new Trigger(); Trigger *tts_end_trigger_ = new Trigger(); @@ -268,6 +270,8 @@ class VoiceAssistant : public Component { #endif #ifdef USE_MEDIA_PLAYER media_player::MediaPlayer *media_player_{nullptr}; + std::string tts_response_url_{""}; + bool started_streaming_tts_{false}; bool media_player_wait_for_announcement_start_{false}; bool media_player_wait_for_announcement_end_{false}; #endif From 9e993ac603a64b2f48c92e8910ef77eeee9aafc3 Mon Sep 17 00:00:00 2001 From: scaiper Date: Fri, 27 Jun 2025 13:43:18 +0300 Subject: [PATCH 05/16] [esp32] Change ``enable_lwip_mdns_queries`` default to ``True`` (#9188) --- esphome/components/esp32/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 157fd9db11..ba9a4894c2 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -605,7 +605,7 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All( CONF_ENABLE_LWIP_DHCP_SERVER, "wifi", default=False ): cv.boolean, cv.Optional( - CONF_ENABLE_LWIP_MDNS_QUERIES, default=False + CONF_ENABLE_LWIP_MDNS_QUERIES, default=True ): cv.boolean, cv.Optional( CONF_ENABLE_LWIP_BRIDGE_INTERFACE, default=False @@ -760,7 +760,7 @@ async def to_code(config): and not advanced[CONF_ENABLE_LWIP_DHCP_SERVER] ): add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False) - if not advanced.get(CONF_ENABLE_LWIP_MDNS_QUERIES, False): + if not advanced.get(CONF_ENABLE_LWIP_MDNS_QUERIES, True): add_idf_sdkconfig_option("CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES", False) if not advanced.get(CONF_ENABLE_LWIP_BRIDGE_INTERFACE, False): add_idf_sdkconfig_option("CONFIG_LWIP_BRIDGEIF_MAX_PORTS", 0) From 948aa13fb9b0752132eabdcf0adb670585e57cac Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 27 Jun 2025 23:16:13 +1200 Subject: [PATCH 06/16] Bump version to 2025.6.2 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index a4dd65bf45..edbb5b1206 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2025.6.1 +PROJECT_NUMBER = 2025.6.2 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 3ef82aff5b..956edfd6be 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2025.6.1" +__version__ = "2025.6.2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 68d66c873e62be045c55d7dcada54bee3cc3636d Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Fri, 27 Jun 2025 19:31:50 +0200 Subject: [PATCH 07/16] Upgrade to use C++20 (#9135) Co-authored-by: J. Nick Koston --- esphome/components/ac_dimmer/ac_dimmer.cpp | 3 ++- esphome/components/atm90e32/atm90e32.cpp | 3 ++- esphome/components/display/display.cpp | 7 ++++--- esphome/components/display/display.h | 2 -- esphome/components/esp32/__init__.py | 2 +- esphome/components/esp8266/__init__.py | 2 +- esphome/components/esp8266/gpio.cpp | 20 +++++++++---------- esphome/components/host/__init__.py | 2 +- .../http_request/ota/ota_http_request.cpp | 2 +- .../components/hydreon_rgxx/hydreon_rgxx.cpp | 12 +++-------- esphome/components/libretiny/__init__.py | 2 +- .../components/online_image/online_image.cpp | 2 +- .../pulse_meter/pulse_meter_sensor.cpp | 8 ++++---- .../radon_eye_ble/radon_eye_listener.cpp | 2 +- .../remote_receiver_esp8266.cpp | 2 +- esphome/components/rp2040/__init__.py | 2 +- esphome/components/sn74hc595/sn74hc595.cpp | 9 +++++---- esphome/components/sun/sun.cpp | 7 +++---- esphome/components/tx20/tx20.cpp | 4 ++-- esphome/components/wiegand/wiegand.cpp | 4 ++-- esphome/core/application.cpp | 17 ++++++++-------- esphome/cpp_generator.py | 2 +- platformio.ini | 6 +++--- 23 files changed, 59 insertions(+), 63 deletions(-) diff --git a/esphome/components/ac_dimmer/ac_dimmer.cpp b/esphome/components/ac_dimmer/ac_dimmer.cpp index 276adeebb0..e6f7a1214a 100644 --- a/esphome/components/ac_dimmer/ac_dimmer.cpp +++ b/esphome/components/ac_dimmer/ac_dimmer.cpp @@ -4,6 +4,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include +#include #ifdef USE_ESP8266 #include @@ -203,7 +204,7 @@ void AcDimmer::setup() { #endif } void AcDimmer::write_state(float state) { - state = std::acos(1 - (2 * state)) / 3.14159; // RMS power compensation + state = std::acos(1 - (2 * state)) / std::numbers::pi; // RMS power compensation auto new_value = static_cast(roundf(state * 65535)); if (new_value != 0 && this->store_.value == 0) this->store_.init_cycle = this->init_with_half_cycle_; diff --git a/esphome/components/atm90e32/atm90e32.cpp b/esphome/components/atm90e32/atm90e32.cpp index f05d462845..4669a59e39 100644 --- a/esphome/components/atm90e32/atm90e32.cpp +++ b/esphome/components/atm90e32/atm90e32.cpp @@ -1,6 +1,7 @@ #include "atm90e32.h" #include #include +#include #include "esphome/core/log.h" namespace esphome { @@ -848,7 +849,7 @@ uint16_t ATM90E32Component::calculate_voltage_threshold(int line_freq, uint16_t float nominal_voltage = (line_freq == 60) ? 120.0f : 220.0f; float target_voltage = nominal_voltage * multiplier; - float peak_01v = target_voltage * 100.0f * std::sqrt(2.0f); // convert RMS → peak, scale to 0.01V + float peak_01v = target_voltage * 100.0f * std::numbers::sqrt2_v; // convert RMS → peak, scale to 0.01V float divider = (2.0f * ugain) / 32768.0f; float threshold = peak_01v / divider; diff --git a/esphome/components/display/display.cpp b/esphome/components/display/display.cpp index a13464ce1b..c666eee298 100644 --- a/esphome/components/display/display.cpp +++ b/esphome/components/display/display.cpp @@ -1,5 +1,6 @@ #include "display.h" #include +#include #include "display_color_utils.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" @@ -424,15 +425,15 @@ void HOT Display::get_regular_polygon_vertex(int vertex_id, int *vertex_x, int * // hence we rotate the shape by 270° to orient the polygon up. rotation_degrees += ROTATION_270_DEGREES; // Convert the rotation to radians, easier to use in trigonometrical calculations - float rotation_radians = rotation_degrees * PI / 180; + float rotation_radians = rotation_degrees * std::numbers::pi / 180; // A pointy top variation means the first vertex of the polygon is at the top center of the shape, this requires no // additional rotation of the shape. // A flat top variation means the first point of the polygon has to be rotated so that the first edge is horizontal, // this requires to rotate the shape by π/edges radians counter-clockwise so that the first point is located on the // left side of the first horizontal edge. - rotation_radians -= (variation == VARIATION_FLAT_TOP) ? PI / edges : 0.0; + rotation_radians -= (variation == VARIATION_FLAT_TOP) ? std::numbers::pi / edges : 0.0; - float vertex_angle = ((float) vertex_id) / edges * 2 * PI + rotation_radians; + float vertex_angle = ((float) vertex_id) / edges * 2 * std::numbers::pi + rotation_radians; *vertex_x = (int) round(cos(vertex_angle) * radius) + center_x; *vertex_y = (int) round(sin(vertex_angle) * radius) + center_y; } diff --git a/esphome/components/display/display.h b/esphome/components/display/display.h index 68c1184721..f2d79d12d9 100644 --- a/esphome/components/display/display.h +++ b/esphome/components/display/display.h @@ -138,8 +138,6 @@ enum DisplayRotation { DISPLAY_ROTATION_270_DEGREES = 270, }; -#define PI 3.1415926535897932384626433832795 - const int EDGES_TRIGON = 3; const int EDGES_TRIANGLE = 3; const int EDGES_TETRAGON = 4; diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 8319ed5e74..7e1c71a7de 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -704,7 +704,7 @@ FINAL_VALIDATE_SCHEMA = cv.Schema(final_validate) async def to_code(config): cg.add_platformio_option("board", config[CONF_BOARD]) cg.add_platformio_option("board_upload.flash_size", config[CONF_FLASH_SIZE]) - cg.set_cpp_standard("gnu++17") + cg.set_cpp_standard("gnu++20") cg.add_build_flag("-DUSE_ESP32") cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}") diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 4b4862a1d0..81daad8c56 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -183,7 +183,7 @@ async def to_code(config): cg.add_platformio_option("board", config[CONF_BOARD]) cg.add_build_flag("-DUSE_ESP8266") - cg.set_cpp_standard("gnu++17") + cg.set_cpp_standard("gnu++20") cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) cg.add_define("ESPHOME_VARIANT", "ESP8266") diff --git a/esphome/components/esp8266/gpio.cpp b/esphome/components/esp8266/gpio.cpp index 4a997a790c..ee3683c67d 100644 --- a/esphome/components/esp8266/gpio.cpp +++ b/esphome/components/esp8266/gpio.cpp @@ -129,9 +129,9 @@ void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) { } } else { if (value != arg->inverted) { - *arg->out_set_reg |= 1; + *arg->out_set_reg = *arg->out_set_reg | 1; } else { - *arg->out_set_reg &= ~1; + *arg->out_set_reg = *arg->out_set_reg & ~1; } } } @@ -147,7 +147,7 @@ void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) { if (flags & gpio::FLAG_OUTPUT) { *arg->mode_set_reg = arg->mask; if (flags & gpio::FLAG_OPEN_DRAIN) { - *arg->control_reg |= 1 << GPCD; + *arg->control_reg = *arg->control_reg | (1 << GPCD); } else { *arg->control_reg &= ~(1 << GPCD); } @@ -155,21 +155,21 @@ void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) { *arg->mode_clr_reg = arg->mask; } if (flags & gpio::FLAG_PULLUP) { - *arg->func_reg |= 1 << GPFPU; - *arg->control_reg |= 1 << GPCD; + *arg->func_reg = *arg->func_reg | (1 << GPFPU); + *arg->control_reg = *arg->control_reg | (1 << GPCD); } else { - *arg->func_reg &= ~(1 << GPFPU); + *arg->func_reg = *arg->func_reg & ~(1 << GPFPU); } } else { if (flags & gpio::FLAG_OUTPUT) { - *arg->mode_set_reg |= 1; + *arg->mode_set_reg = *arg->mode_set_reg | 1; } else { - *arg->mode_set_reg &= ~1; + *arg->mode_set_reg = *arg->mode_set_reg & ~1; } if (flags & gpio::FLAG_PULLDOWN) { - *arg->func_reg |= 1 << GP16FPD; + *arg->func_reg = *arg->func_reg | (1 << GP16FPD); } else { - *arg->func_reg &= ~(1 << GP16FPD); + *arg->func_reg = *arg->func_reg & ~(1 << GP16FPD); } } } diff --git a/esphome/components/host/__init__.py b/esphome/components/host/__init__.py index b59d8ebd03..da75873eaf 100644 --- a/esphome/components/host/__init__.py +++ b/esphome/components/host/__init__.py @@ -41,6 +41,6 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): cg.add_build_flag("-DUSE_HOST") cg.add_define("USE_ESPHOME_HOST_MAC_ADDRESS", config[CONF_MAC_ADDRESS].parts) - cg.add_build_flag("-std=gnu++17") + cg.add_build_flag("-std=gnu++20") cg.add_define("ESPHOME_BOARD", "host") cg.add_platformio_option("platform", "platformio/native") diff --git a/esphome/components/http_request/ota/ota_http_request.cpp b/esphome/components/http_request/ota/ota_http_request.cpp index 4c8d49dad5..4d9e868c74 100644 --- a/esphome/components/http_request/ota/ota_http_request.cpp +++ b/esphome/components/http_request/ota/ota_http_request.cpp @@ -258,7 +258,7 @@ bool OtaHttpRequestComponent::http_get_md5_() { } bool OtaHttpRequestComponent::validate_url_(const std::string &url) { - if ((url.length() < 8) || (url.find("http") != 0) || (url.find("://") == std::string::npos)) { + if ((url.length() < 8) || !url.starts_with("http") || (url.find("://") == std::string::npos)) { ESP_LOGE(TAG, "URL is invalid and/or must be prefixed with 'http://' or 'https://'"); return false; } diff --git a/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp b/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp index 2d8381b60c..9d4680fdf4 100644 --- a/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp +++ b/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp @@ -159,12 +159,6 @@ void HydreonRGxxComponent::schedule_reboot_() { }); } -bool HydreonRGxxComponent::buffer_starts_with_(const std::string &prefix) { - return this->buffer_starts_with_(prefix.c_str()); -} - -bool HydreonRGxxComponent::buffer_starts_with_(const char *prefix) { return buffer_.rfind(prefix, 0) == 0; } - void HydreonRGxxComponent::process_line_() { ESP_LOGV(TAG, "Read from serial: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); @@ -191,7 +185,7 @@ void HydreonRGxxComponent::process_line_() { ESP_LOGW(TAG, "Received EmSat!"); this->em_sat_ = true; } - if (this->buffer_starts_with_("PwrDays")) { + if (buffer_.starts_with("PwrDays")) { if (this->boot_count_ <= 0) { this->boot_count_ = 1; } else { @@ -220,7 +214,7 @@ void HydreonRGxxComponent::process_line_() { } return; } - if (this->buffer_starts_with_("SW")) { + if (buffer_.starts_with("SW")) { std::string::size_type majend = this->buffer_.find('.'); std::string::size_type endversion = this->buffer_.find(' ', 3); if (majend == std::string::npos || endversion == std::string::npos || majend > endversion) { @@ -282,7 +276,7 @@ void HydreonRGxxComponent::process_line_() { } } else { for (const auto *ignore : IGNORE_STRINGS) { - if (this->buffer_starts_with_(ignore)) { + if (buffer_.starts_with(ignore)) { ESP_LOGI(TAG, "Ignoring %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); return; } diff --git a/esphome/components/libretiny/__init__.py b/esphome/components/libretiny/__init__.py index 28ee1e702f..149e5d1179 100644 --- a/esphome/components/libretiny/__init__.py +++ b/esphome/components/libretiny/__init__.py @@ -264,7 +264,7 @@ async def component_to_code(config): # force using arduino framework cg.add_platformio_option("framework", "arduino") cg.add_build_flag("-DUSE_ARDUINO") - cg.set_cpp_standard("gnu++17") + cg.set_cpp_standard("gnu++20") # disable library compatibility checks cg.add_platformio_option("lib_ldf_mode", "off") diff --git a/esphome/components/online_image/online_image.cpp b/esphome/components/online_image/online_image.cpp index e21b2528d5..d0c743ef93 100644 --- a/esphome/components/online_image/online_image.cpp +++ b/esphome/components/online_image/online_image.cpp @@ -344,7 +344,7 @@ void OnlineImage::end_connection_() { } bool OnlineImage::validate_url_(const std::string &url) { - if ((url.length() < 8) || (url.find("http") != 0) || (url.find("://") == std::string::npos)) { + if ((url.length() < 8) || !url.starts_with("http") || (url.find("://") == std::string::npos)) { ESP_LOGE(TAG, "URL is invalid and/or must be prefixed with 'http://' or 'https://'"); return false; } diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.cpp b/esphome/components/pulse_meter/pulse_meter_sensor.cpp index b82cb7a15c..81ecf22c71 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.cpp +++ b/esphome/components/pulse_meter/pulse_meter_sensor.cpp @@ -63,7 +63,7 @@ void PulseMeterSensor::loop() { // If an edge was peeked, repay the debt if (this->peeked_edge_ && this->get_->count_ > 0) { this->peeked_edge_ = false; - this->get_->count_--; + this->get_->count_--; // NOLINT(clang-diagnostic-deprecated-volatile) } // If there is an unprocessed edge, and filter_us_ has passed since, count this edge early @@ -71,7 +71,7 @@ void PulseMeterSensor::loop() { now - this->get_->last_rising_edge_us_ >= this->filter_us_) { this->peeked_edge_ = true; this->get_->last_detected_edge_us_ = this->get_->last_rising_edge_us_; - this->get_->count_++; + this->get_->count_++; // NOLINT(clang-diagnostic-deprecated-volatile) } // Check if we detected a pulse this loop @@ -146,7 +146,7 @@ void IRAM_ATTR PulseMeterSensor::edge_intr(PulseMeterSensor *sensor) { state.last_sent_edge_us_ = now; set.last_detected_edge_us_ = now; set.last_rising_edge_us_ = now; - set.count_++; + set.count_++; // NOLINT(clang-diagnostic-deprecated-volatile) } // This ISR is bound to rising edges, so the pin is high @@ -169,7 +169,7 @@ void IRAM_ATTR PulseMeterSensor::pulse_intr(PulseMeterSensor *sensor) { } else if (length && !state.latched_ && sensor->last_pin_val_) { // Long enough high edge state.latched_ = true; set.last_detected_edge_us_ = state.last_intr_; - set.count_++; + set.count_++; // NOLINT(clang-diagnostic-deprecated-volatile) } // Due to order of operations this includes diff --git a/esphome/components/radon_eye_ble/radon_eye_listener.cpp b/esphome/components/radon_eye_ble/radon_eye_listener.cpp index a4c79db753..0c6165c691 100644 --- a/esphome/components/radon_eye_ble/radon_eye_listener.cpp +++ b/esphome/components/radon_eye_ble/radon_eye_listener.cpp @@ -17,7 +17,7 @@ bool RadonEyeListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device // Check if the device name starts with any of the prefixes if (std::any_of(prefixes.begin(), prefixes.end(), - [&](const std::string &prefix) { return device.get_name().rfind(prefix, 0) == 0; })) { + [&](const std::string &prefix) { return device.get_name().starts_with(prefix); })) { // Device found ESP_LOGD(TAG, "Found Radon Eye device Name: %s (MAC: %s)", device.get_name().c_str(), device.address_str().c_str()); diff --git a/esphome/components/remote_receiver/remote_receiver_esp8266.cpp b/esphome/components/remote_receiver/remote_receiver_esp8266.cpp index a0fd56bcf4..fe935ba227 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp8266.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp8266.cpp @@ -27,7 +27,7 @@ void IRAM_ATTR HOT RemoteReceiverComponentStore::gpio_intr(RemoteReceiverCompone if (time_since_change <= arg->filter_us) return; - arg->buffer[arg->buffer_write_at = next] = now; + arg->buffer[arg->buffer_write_at = next] = now; // NOLINT(clang-diagnostic-deprecated-volatile) } void RemoteReceiverComponent::setup() { diff --git a/esphome/components/rp2040/__init__.py b/esphome/components/rp2040/__init__.py index 2718e3050f..ecbeb83bb4 100644 --- a/esphome/components/rp2040/__init__.py +++ b/esphome/components/rp2040/__init__.py @@ -167,7 +167,7 @@ async def to_code(config): cg.add_platformio_option("lib_ldf_mode", "chain+") cg.add_platformio_option("board", config[CONF_BOARD]) cg.add_build_flag("-DUSE_RP2040") - cg.set_cpp_standard("gnu++17") + cg.set_cpp_standard("gnu++20") cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) cg.add_define("ESPHOME_VARIANT", "RP2040") diff --git a/esphome/components/sn74hc595/sn74hc595.cpp b/esphome/components/sn74hc595/sn74hc595.cpp index f8d24b898f..d8e33eec22 100644 --- a/esphome/components/sn74hc595/sn74hc595.cpp +++ b/esphome/components/sn74hc595/sn74hc595.cpp @@ -1,5 +1,6 @@ #include "sn74hc595.h" #include "esphome/core/log.h" +#include namespace esphome { namespace sn74hc595 { @@ -55,9 +56,9 @@ void SN74HC595Component::digital_write_(uint16_t pin, bool value) { } void SN74HC595GPIOComponent::write_gpio() { - for (auto byte = this->output_bytes_.rbegin(); byte != this->output_bytes_.rend(); byte++) { + for (uint8_t &output_byte : std::ranges::reverse_view(this->output_bytes_)) { for (int8_t i = 7; i >= 0; i--) { - bool bit = (*byte >> i) & 1; + bool bit = (output_byte >> i) & 1; this->data_pin_->digital_write(bit); this->clock_pin_->digital_write(true); this->clock_pin_->digital_write(false); @@ -68,9 +69,9 @@ void SN74HC595GPIOComponent::write_gpio() { #ifdef USE_SPI void SN74HC595SPIComponent::write_gpio() { - for (auto byte = this->output_bytes_.rbegin(); byte != this->output_bytes_.rend(); byte++) { + for (uint8_t &output_byte : std::ranges::reverse_view(this->output_bytes_)) { this->enable(); - this->transfer_byte(*byte); + this->transfer_byte(output_byte); this->disable(); } SN74HC595Component::write_gpio(); diff --git a/esphome/components/sun/sun.cpp b/esphome/components/sun/sun.cpp index 2fd9394a5e..df7030461b 100644 --- a/esphome/components/sun/sun.cpp +++ b/esphome/components/sun/sun.cpp @@ -1,5 +1,6 @@ #include "sun.h" #include "esphome/core/log.h" +#include /* The formulas/algorithms in this module are based on the book @@ -18,14 +19,12 @@ using namespace esphome::sun::internal; static const char *const TAG = "sun"; -#undef PI #undef degrees #undef radians #undef sq -static const num_t PI = 3.141592653589793; -inline num_t degrees(num_t rad) { return rad * 180 / PI; } -inline num_t radians(num_t deg) { return deg * PI / 180; } +inline num_t degrees(num_t rad) { return rad * 180 / std::numbers::pi; } +inline num_t radians(num_t deg) { return deg * std::numbers::pi / 180; } inline num_t arcdeg(num_t deg, num_t minutes, num_t seconds) { return deg + minutes / 60 + seconds / 3600; } inline num_t sq(num_t x) { return x * x; } inline num_t cb(num_t x) { return x * x * x; } diff --git a/esphome/components/tx20/tx20.cpp b/esphome/components/tx20/tx20.cpp index 73b865e8b8..42e3955fc2 100644 --- a/esphome/components/tx20/tx20.cpp +++ b/esphome/components/tx20/tx20.cpp @@ -152,7 +152,7 @@ void IRAM_ATTR Tx20ComponentStore::gpio_intr(Tx20ComponentStore *arg) { } arg->buffer[arg->buffer_index] = 1; arg->start_time = now; - arg->buffer_index++; + arg->buffer_index++; // NOLINT(clang-diagnostic-deprecated-volatile) return; } const uint32_t delay = now - arg->start_time; @@ -183,7 +183,7 @@ void IRAM_ATTR Tx20ComponentStore::gpio_intr(Tx20ComponentStore *arg) { } arg->spent_time += delay; arg->start_time = now; - arg->buffer_index++; + arg->buffer_index++; // NOLINT(clang-diagnostic-deprecated-volatile) } void IRAM_ATTR Tx20ComponentStore::reset() { tx20_available = false; diff --git a/esphome/components/wiegand/wiegand.cpp b/esphome/components/wiegand/wiegand.cpp index 5a2bb8deee..dd1443d10c 100644 --- a/esphome/components/wiegand/wiegand.cpp +++ b/esphome/components/wiegand/wiegand.cpp @@ -11,7 +11,7 @@ static const char *const KEYS = "0123456789*#"; void IRAM_ATTR HOT WiegandStore::d0_gpio_intr(WiegandStore *arg) { if (arg->d0.digital_read()) return; - arg->count++; + arg->count++; // NOLINT(clang-diagnostic-deprecated-volatile) arg->value <<= 1; arg->last_bit_time = millis(); arg->done = false; @@ -20,7 +20,7 @@ void IRAM_ATTR HOT WiegandStore::d0_gpio_intr(WiegandStore *arg) { void IRAM_ATTR HOT WiegandStore::d1_gpio_intr(WiegandStore *arg) { if (arg->d1.digital_read()) return; - arg->count++; + arg->count++; // NOLINT(clang-diagnostic-deprecated-volatile) arg->value = (arg->value << 1) | 1; arg->last_bit_time = millis(); arg->done = false; diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index f64070fa3d..328de00640 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -3,6 +3,7 @@ #include "esphome/core/version.h" #include "esphome/core/hal.h" #include +#include #ifdef USE_STATUS_LED #include "esphome/components/status_led/status_led.h" @@ -184,8 +185,8 @@ void IRAM_ATTR HOT Application::feed_wdt(uint32_t time) { } void Application::reboot() { ESP_LOGI(TAG, "Forcing a reboot"); - for (auto it = this->components_.rbegin(); it != this->components_.rend(); ++it) { - (*it)->on_shutdown(); + for (auto &component : std::ranges::reverse_view(this->components_)) { + component->on_shutdown(); } arch_restart(); } @@ -198,17 +199,17 @@ void Application::safe_reboot() { } void Application::run_safe_shutdown_hooks() { - for (auto it = this->components_.rbegin(); it != this->components_.rend(); ++it) { - (*it)->on_safe_shutdown(); + for (auto &component : std::ranges::reverse_view(this->components_)) { + component->on_safe_shutdown(); } - for (auto it = this->components_.rbegin(); it != this->components_.rend(); ++it) { - (*it)->on_shutdown(); + for (auto &component : std::ranges::reverse_view(this->components_)) { + component->on_shutdown(); } } void Application::run_powerdown_hooks() { - for (auto it = this->components_.rbegin(); it != this->components_.rend(); ++it) { - (*it)->on_powerdown(); + for (auto &component : std::ranges::reverse_view(this->components_)) { + component->on_powerdown(); } } diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 2a7b7fe057..060dd36f8f 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -617,7 +617,7 @@ def set_cpp_standard(standard: str) -> None: """Set C++ standard with compiler flag `-std={standard}`.""" CORE.add_build_unflag("-std=gnu++11") CORE.add_build_unflag("-std=gnu++14") - CORE.add_build_unflag("-std=gnu++20") + CORE.add_build_unflag("-std=gnu++17") CORE.add_build_unflag("-std=gnu++23") CORE.add_build_unflag("-std=gnu++2a") CORE.add_build_unflag("-std=gnu++2b") diff --git a/platformio.ini b/platformio.ini index 6da9fc1338..be9d7587c2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -47,11 +47,11 @@ lib_deps = lvgl/lvgl@8.4.0 ; lvgl build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE - -std=gnu++17 + -std=gnu++20 build_unflags = -std=gnu++11 -std=gnu++14 - -std=gnu++20 + -std=gnu++17 -std=gnu++23 -std=gnu++2a -std=gnu++2b @@ -560,7 +560,7 @@ lib_deps = build_flags = ${common.build_flags} -DUSE_HOST - -std=c++17 + -std=c++20 build_unflags = ${common.build_unflags} From 156a9160ba9ac191543ae7c6c844ba259d0cd59b Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Fri, 27 Jun 2025 18:31:23 -0700 Subject: [PATCH 08/16] [mcp23xxx_base] fix pin interrupts (#9244) Co-authored-by: Samuel Sieb --- esphome/components/mcp23xxx_base/mcp23xxx_base.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp b/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp index 14a703fb9f..fc49f216ee 100644 --- a/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp +++ b/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp @@ -6,7 +6,11 @@ namespace mcp23xxx_base { float MCP23XXXBase::get_setup_priority() const { return setup_priority::IO; } -void MCP23XXXGPIOPin::setup() { pin_mode(flags_); } +void MCP23XXXGPIOPin::setup() { + pin_mode(flags_); + this->parent_->pin_interrupt_mode(this->pin_, this->interrupt_mode_); +} + void MCP23XXXGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool MCP23XXXGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void MCP23XXXGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } From 52ca8deb1099eca9be1ef9a4fa5c30f2edf67343 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 27 Jun 2025 21:32:18 -0400 Subject: [PATCH 09/16] [i2c] Disable i2c scan on certain idf versions (#9237) --- esphome/components/i2c/__init__.py | 33 ++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index d56bb2d07c..fae1fa1d22 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -1,5 +1,8 @@ +import logging + from esphome import pins import esphome.codegen as cg +from esphome.components import esp32 import esphome.config_validation as cv from esphome.const import ( CONF_ADDRESS, @@ -12,6 +15,8 @@ from esphome.const import ( CONF_SCL, CONF_SDA, CONF_TIMEOUT, + KEY_CORE, + KEY_FRAMEWORK_VERSION, PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040, @@ -19,6 +24,7 @@ from esphome.const import ( from esphome.core import CORE, coroutine_with_priority import esphome.final_validate as fv +LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@esphome/core"] i2c_ns = cg.esphome_ns.namespace("i2c") I2CBus = i2c_ns.class_("I2CBus") @@ -41,6 +47,32 @@ def _bus_declare_type(value): raise NotImplementedError +def validate_config(config): + if ( + config[CONF_SCAN] + and CORE.is_esp32 + and CORE.using_esp_idf + and esp32.get_esp32_variant() + in [ + esp32.const.VARIANT_ESP32C5, + esp32.const.VARIANT_ESP32C6, + esp32.const.VARIANT_ESP32P4, + ] + ): + version: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] + if version.major == 5 and ( + (version.minor == 3 and version.patch <= 3) + or (version.minor == 4 and version.patch <= 1) + ): + LOGGER.warning( + "There is a bug in esp-idf version %s that breaks I2C scan, I2C scan " + "has been disabled, see https://github.com/esphome/issues/issues/7128", + str(version), + ) + config[CONF_SCAN] = False + return config + + pin_with_input_and_output_support = pins.internal_gpio_pin_number( {CONF_OUTPUT: True, CONF_INPUT: True} ) @@ -66,6 +98,7 @@ CONFIG_SCHEMA = cv.All( } ).extend(cv.COMPONENT_SCHEMA), cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]), + validate_config, ) From 5010a0f5e706d1067e13bb6a76661d1b78ce29c4 Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Fri, 27 Jun 2025 18:31:23 -0700 Subject: [PATCH 10/16] [mcp23xxx_base] fix pin interrupts (#9244) Co-authored-by: Samuel Sieb --- esphome/components/mcp23xxx_base/mcp23xxx_base.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp b/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp index 14a703fb9f..fc49f216ee 100644 --- a/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp +++ b/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp @@ -6,7 +6,11 @@ namespace mcp23xxx_base { float MCP23XXXBase::get_setup_priority() const { return setup_priority::IO; } -void MCP23XXXGPIOPin::setup() { pin_mode(flags_); } +void MCP23XXXGPIOPin::setup() { + pin_mode(flags_); + this->parent_->pin_interrupt_mode(this->pin_, this->interrupt_mode_); +} + void MCP23XXXGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } bool MCP23XXXGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } void MCP23XXXGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } From 61b3379f48b2e4895da96ab0c72239e9516a3299 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 27 Jun 2025 21:32:18 -0400 Subject: [PATCH 11/16] [i2c] Disable i2c scan on certain idf versions (#9237) --- esphome/components/i2c/__init__.py | 33 ++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index e47dec650d..e20b7febeb 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -1,5 +1,8 @@ +import logging + from esphome import pins import esphome.codegen as cg +from esphome.components import esp32 import esphome.config_validation as cv from esphome.const import ( CONF_ADDRESS, @@ -12,6 +15,8 @@ from esphome.const import ( CONF_SCL, CONF_SDA, CONF_TIMEOUT, + KEY_CORE, + KEY_FRAMEWORK_VERSION, PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040, @@ -19,6 +24,7 @@ from esphome.const import ( from esphome.core import CORE, coroutine_with_priority import esphome.final_validate as fv +LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@esphome/core"] i2c_ns = cg.esphome_ns.namespace("i2c") I2CBus = i2c_ns.class_("I2CBus") @@ -40,6 +46,32 @@ def _bus_declare_type(value): raise NotImplementedError +def validate_config(config): + if ( + config[CONF_SCAN] + and CORE.is_esp32 + and CORE.using_esp_idf + and esp32.get_esp32_variant() + in [ + esp32.const.VARIANT_ESP32C5, + esp32.const.VARIANT_ESP32C6, + esp32.const.VARIANT_ESP32P4, + ] + ): + version: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] + if version.major == 5 and ( + (version.minor == 3 and version.patch <= 3) + or (version.minor == 4 and version.patch <= 1) + ): + LOGGER.warning( + "There is a bug in esp-idf version %s that breaks I2C scan, I2C scan " + "has been disabled, see https://github.com/esphome/issues/issues/7128", + str(version), + ) + config[CONF_SCAN] = False + return config + + pin_with_input_and_output_support = pins.internal_gpio_pin_number( {CONF_OUTPUT: True, CONF_INPUT: True} ) @@ -65,6 +97,7 @@ CONFIG_SCHEMA = cv.All( } ).extend(cv.COMPONENT_SCHEMA), cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]), + validate_config, ) From f8d59b5aeb1268d52a47fe674b115e7245ceb143 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Jun 2025 22:53:40 -0500 Subject: [PATCH 12/16] Reduce libretiny logconfig messages (#9239) --- esphome/components/libretiny/lt_component.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/esphome/components/libretiny/lt_component.cpp b/esphome/components/libretiny/lt_component.cpp index ec4b60eaeb..ffccd0ad7a 100644 --- a/esphome/components/libretiny/lt_component.cpp +++ b/esphome/components/libretiny/lt_component.cpp @@ -10,9 +10,11 @@ namespace libretiny { static const char *const TAG = "lt.component"; void LTComponent::dump_config() { - ESP_LOGCONFIG(TAG, "LibreTiny:"); - ESP_LOGCONFIG(TAG, " Version: %s", LT_BANNER_STR + 10); - ESP_LOGCONFIG(TAG, " Loglevel: %u", LT_LOGLEVEL); + ESP_LOGCONFIG(TAG, + "LibreTiny:\n" + " Version: %s\n" + " Loglevel: %u", + LT_BANNER_STR + 10, LT_LOGLEVEL); #ifdef USE_TEXT_SENSOR if (this->version_ != nullptr) { From 094bf19ec4d86de89d57cc60a3870bdcc6cc4567 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Jun 2025 23:58:53 -0500 Subject: [PATCH 13/16] Disable dynamic log level control for ESP32 ESP-IDF builds (#9233) --- esphome/components/esp32/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 7e1c71a7de..6425e57b13 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -758,6 +758,9 @@ async def to_code(config): add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0", False) add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1", False) + # Disable dynamic log level control to save memory + add_idf_sdkconfig_option("CONFIG_LOG_DYNAMIC_LEVEL_CONTROL", False) + # Set default CPU frequency add_idf_sdkconfig_option(f"CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_{freq}", True) From 3f65cee17cb135895921f0309d01af404d96d61a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Jun 2025 23:59:52 -0500 Subject: [PATCH 14/16] Silence protobuf compatibility warnings when importing aioesphomeapi (#9236) --- esphome/components/api/client.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py index 20136ef7b8..7f8e2529a5 100644 --- a/esphome/components/api/client.py +++ b/esphome/components/api/client.py @@ -4,9 +4,15 @@ import asyncio from datetime import datetime import logging from typing import TYPE_CHECKING, Any +import warnings -from aioesphomeapi import APIClient, parse_log_message -from aioesphomeapi.log_runner import async_run +# Suppress protobuf version warnings +with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", category=UserWarning, message=".*Protobuf gencode version.*" + ) + from aioesphomeapi import APIClient, parse_log_message + from aioesphomeapi.log_runner import async_run from esphome.const import CONF_KEY, CONF_PASSWORD, CONF_PORT, __version__ from esphome.core import CORE From d37f5b87bd024a14eb4828076b0a32f166d6f903 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Sat, 28 Jun 2025 02:30:59 -0400 Subject: [PATCH 15/16] [esp32] Allow 5.4.2 (#9243) --- esphome/components/esp32/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 6425e57b13..32323b7504 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -341,6 +341,7 @@ SUPPORTED_PLATFORMIO_ESP_IDF_5X = [ # List based on https://github.com/pioarduino/esp-idf/releases SUPPORTED_PIOARDUINO_ESP_IDF_5X = [ cv.Version(5, 5, 0), + cv.Version(5, 4, 2), cv.Version(5, 4, 1), cv.Version(5, 4, 0), cv.Version(5, 3, 3), From 58b7d0b412187aa4fb0c8ec03444b5e23454d39b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 28 Jun 2025 10:21:53 +0000 Subject: [PATCH 16/16] Bump ruff from 0.12.0 to 0.12.1 (#9241) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston --- .pre-commit-config.yaml | 2 +- requirements_test.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 96efee7020..1a0289a3ca 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.12.0 + rev: v0.12.1 hooks: # Run the linter. - id: ruff diff --git a/requirements_test.txt b/requirements_test.txt index 89aba702b9..66b71c2225 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==3.3.7 flake8==7.3.0 # also change in .pre-commit-config.yaml when updating -ruff==0.12.0 # also change in .pre-commit-config.yaml when updating +ruff==0.12.1 # also change in .pre-commit-config.yaml when updating pyupgrade==3.20.0 # also change in .pre-commit-config.yaml when updating pre-commit