From 6f74decd7997c159ffecdd8c556a70e30c0f3647 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Fri, 18 Jul 2025 21:52:46 +0100 Subject: [PATCH 01/38] [i2s_audio] Bugfix: cast adc_channel_t to adc1_channel_t (#9688) --- .../components/i2s_audio/microphone/i2s_audio_microphone.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h index 5f66f2e962..633bd0e7dd 100644 --- a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h +++ b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h @@ -36,8 +36,8 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub #ifdef USE_I2S_LEGACY #if SOC_I2S_SUPPORTS_ADC - void set_adc_channel(adc1_channel_t channel) { - this->adc_channel_ = channel; + void set_adc_channel(adc_channel_t channel) { + this->adc_channel_ = (adc1_channel_t) channel; this->adc_ = true; } #endif From 6cefe943e9dc80fbffe3eff99c2f552b6ef25842 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 11:32:20 -1000 Subject: [PATCH 02/38] [gpio] Disable interrupt mode by default for LibreTiny platforms (#9687) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../components/gpio/binary_sensor/__init__.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/esphome/components/gpio/binary_sensor/__init__.py b/esphome/components/gpio/binary_sensor/__init__.py index 867a8efe49..59f54520fa 100644 --- a/esphome/components/gpio/binary_sensor/__init__.py +++ b/esphome/components/gpio/binary_sensor/__init__.py @@ -29,7 +29,21 @@ CONFIG_SCHEMA = ( .extend( { cv.Required(CONF_PIN): pins.gpio_input_pin_schema, - cv.Optional(CONF_USE_INTERRUPT, default=True): cv.boolean, + # Interrupts are disabled by default for bk72xx, ln882x, and rtl87xx platforms + # due to hardware limitations or lack of reliable interrupt support. This ensures + # stable operation on these platforms. Future maintainers should verify platform + # capabilities before changing this default behavior. + cv.SplitDefault( + CONF_USE_INTERRUPT, + bk72xx=False, + esp32=True, + esp8266=True, + host=True, + ln882x=False, + nrf52=True, + rp2040=True, + rtl87xx=False, + ): cv.boolean, cv.Optional(CONF_INTERRUPT_TYPE, default="ANY"): cv.enum( INTERRUPT_TYPES, upper=True ), From cdeed7afa72dc62a974ddf102ff1b8b8bf5512d8 Mon Sep 17 00:00:00 2001 From: Flo Date: Thu, 17 Jul 2025 23:45:07 +0200 Subject: [PATCH 03/38] Fix template event web_server crash (#9618) --- esphome/components/web_server/web_server.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index fe5c197329..bb2640b539 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1620,7 +1620,9 @@ void WebServer::handle_event_request(AsyncWebServerRequest *request, const UrlMa request->send(404); } -static std::string get_event_type(event::Event *event) { return event->last_event_type ? *event->last_event_type : ""; } +static std::string get_event_type(event::Event *event) { + return (event && event->last_event_type) ? *event->last_event_type : ""; +} std::string WebServer::event_state_json_generator(WebServer *web_server, void *source) { auto *event = static_cast(source); From 21e66b76e4fe514014fe7c7edfc8441d59fb5cb8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 17 Jul 2025 12:55:39 -1000 Subject: [PATCH 04/38] [api] Fix compilation error with char* lambdas in HomeAssistant services (#9638) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../components/api/homeassistant_service.h | 3 +++ .../fixtures/api_string_lambda.yaml | 23 +++++++++++++++++++ tests/integration/test_api_string_lambda.py | 21 ++++++++++++++--- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/esphome/components/api/homeassistant_service.h b/esphome/components/api/homeassistant_service.h index 223af132db..f765f1f806 100644 --- a/esphome/components/api/homeassistant_service.h +++ b/esphome/components/api/homeassistant_service.h @@ -16,6 +16,9 @@ template class TemplatableStringValue : public TemplatableValue static std::string value_to_string(T &&val) { return to_string(std::forward(val)); } // Overloads for string types - needed because std::to_string doesn't support them + static std::string value_to_string(char *val) { + return val ? std::string(val) : std::string(); + } // For lambdas returning char* (e.g., itoa) static std::string value_to_string(const char *val) { return std::string(val); } // For lambdas returning .c_str() static std::string value_to_string(const std::string &val) { return val; } static std::string value_to_string(std::string &&val) { return std::move(val); } diff --git a/tests/integration/fixtures/api_string_lambda.yaml b/tests/integration/fixtures/api_string_lambda.yaml index 18440b9984..e2da4683c0 100644 --- a/tests/integration/fixtures/api_string_lambda.yaml +++ b/tests/integration/fixtures/api_string_lambda.yaml @@ -60,5 +60,28 @@ api: data: value: !lambda 'return input_float;' + # Service that tests char* lambda functionality (e.g., from itoa or sprintf) + - action: test_char_ptr_lambda + variables: + input_number: int + input_string: string + then: + # Log the input to verify service was called + - logger.log: + format: "Service called with number for char* test: %d" + args: [input_number] + + # Test that char* lambdas work correctly + # This would fail in issue #9628 with "invalid conversion from 'char*' to 'long long unsigned int'" + - homeassistant.event: + event: esphome.test_char_ptr_lambda + data: + # Test snprintf returning char* + decimal_value: !lambda 'static char buffer[20]; snprintf(buffer, sizeof(buffer), "%d", input_number); return buffer;' + # Test strdup returning char* (dynamically allocated) + string_copy: !lambda 'return strdup(input_string.c_str());' + # Test string literal (const char*) + literal: !lambda 'return "test literal";' + logger: level: DEBUG diff --git a/tests/integration/test_api_string_lambda.py b/tests/integration/test_api_string_lambda.py index 3bef2d86e2..f4ef77bad8 100644 --- a/tests/integration/test_api_string_lambda.py +++ b/tests/integration/test_api_string_lambda.py @@ -19,15 +19,17 @@ async def test_api_string_lambda( """Test TemplatableStringValue works with lambdas that return different types.""" loop = asyncio.get_running_loop() - # Track log messages for all three service calls + # Track log messages for all four service calls string_called_future = loop.create_future() int_called_future = loop.create_future() float_called_future = loop.create_future() + char_ptr_called_future = loop.create_future() # Patterns to match in logs - confirms the lambdas compiled and executed string_pattern = re.compile(r"Service called with string: STRING_FROM_LAMBDA") int_pattern = re.compile(r"Service called with int: 42") float_pattern = re.compile(r"Service called with float: 3\.14") + char_ptr_pattern = re.compile(r"Service called with number for char\* test: 123") def check_output(line: str) -> None: """Check log output for expected messages.""" @@ -37,6 +39,8 @@ async def test_api_string_lambda( int_called_future.set_result(True) if not float_called_future.done() and float_pattern.search(line): float_called_future.set_result(True) + if not char_ptr_called_future.done() and char_ptr_pattern.search(line): + char_ptr_called_future.set_result(True) # Run with log monitoring async with ( @@ -65,17 +69,28 @@ async def test_api_string_lambda( ) assert float_service is not None, "test_float_lambda service not found" - # Execute all three services to test different lambda return types + char_ptr_service = next( + (s for s in services if s.name == "test_char_ptr_lambda"), None + ) + assert char_ptr_service is not None, "test_char_ptr_lambda service not found" + + # Execute all four services to test different lambda return types client.execute_service(string_service, {"input_string": "STRING_FROM_LAMBDA"}) client.execute_service(int_service, {"input_number": 42}) client.execute_service(float_service, {"input_float": 3.14}) + client.execute_service( + char_ptr_service, {"input_number": 123, "input_string": "test_string"} + ) # Wait for all service log messages # This confirms the lambdas compiled successfully and executed try: await asyncio.wait_for( asyncio.gather( - string_called_future, int_called_future, float_called_future + string_called_future, + int_called_future, + float_called_future, + char_ptr_called_future, ), timeout=5.0, ) From 4a43f922c65bb6a7c03f85cdda3daf932450b70a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 17 Jul 2025 13:00:56 -1000 Subject: [PATCH 05/38] [wireguard] Fix boot loop when CONFIG_LWIP_TCPIP_CORE_LOCKING is enabled (#9637) --- esphome/components/wireguard/wireguard.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/esphome/components/wireguard/wireguard.cpp b/esphome/components/wireguard/wireguard.cpp index 1f61e2dda3..4efcf13e08 100644 --- a/esphome/components/wireguard/wireguard.cpp +++ b/esphome/components/wireguard/wireguard.cpp @@ -8,6 +8,7 @@ #include "esphome/core/log.h" #include "esphome/core/time.h" #include "esphome/components/network/util.h" +#include "esphome/core/helpers.h" #include #include @@ -42,7 +43,10 @@ void Wireguard::setup() { this->publish_enabled_state(); - this->wg_initialized_ = esp_wireguard_init(&(this->wg_config_), &(this->wg_ctx_)); + { + LwIPLock lock; + this->wg_initialized_ = esp_wireguard_init(&(this->wg_config_), &(this->wg_ctx_)); + } if (this->wg_initialized_ == ESP_OK) { ESP_LOGI(TAG, "Initialized"); @@ -249,7 +253,10 @@ void Wireguard::start_connection_() { } ESP_LOGD(TAG, "Starting connection"); - this->wg_connected_ = esp_wireguard_connect(&(this->wg_ctx_)); + { + LwIPLock lock; + this->wg_connected_ = esp_wireguard_connect(&(this->wg_ctx_)); + } if (this->wg_connected_ == ESP_OK) { ESP_LOGI(TAG, "Connection started"); @@ -280,7 +287,10 @@ void Wireguard::start_connection_() { void Wireguard::stop_connection_() { if (this->wg_initialized_ == ESP_OK && this->wg_connected_ == ESP_OK) { ESP_LOGD(TAG, "Stopping connection"); - esp_wireguard_disconnect(&(this->wg_ctx_)); + { + LwIPLock lock; + esp_wireguard_disconnect(&(this->wg_ctx_)); + } this->wg_connected_ = ESP_FAIL; } } From c602f3082eb92baa419eb16716561184e2b7de3d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 17 Jul 2025 16:20:35 -1000 Subject: [PATCH 06/38] [scheduler] Fix cancellation of timers with empty string names (#9641) --- esphome/core/component.cpp | 4 +- esphome/core/scheduler.cpp | 2 +- esphome/core/scheduler.h | 7 +- .../fixtures/scheduler_string_test.yaml | 117 +++++++++++++++++- .../integration/test_scheduler_heap_stress.py | 5 +- 5 files changed, 123 insertions(+), 12 deletions(-) diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index c47f16b5f7..800fbcaa28 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -252,10 +252,10 @@ void Component::defer(const char *name, std::function &&f) { // NOLINT App.scheduler.set_timeout(this, name, 0, std::move(f)); } void Component::set_timeout(uint32_t timeout, std::function &&f) { // NOLINT - App.scheduler.set_timeout(this, "", timeout, std::move(f)); + App.scheduler.set_timeout(this, static_cast(nullptr), timeout, std::move(f)); } void Component::set_interval(uint32_t interval, std::function &&f) { // NOLINT - App.scheduler.set_interval(this, "", interval, std::move(f)); + App.scheduler.set_interval(this, static_cast(nullptr), interval, std::move(f)); } void Component::set_retry(uint32_t initial_wait_time, uint8_t max_attempts, std::function &&f, float backoff_increase_factor) { // NOLINT diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index c6893b128f..8a31e4f42e 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -446,7 +446,7 @@ bool HOT Scheduler::cancel_item_(Component *component, bool is_static_string, co // Helper to cancel items by name - must be called with lock held bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_cstr, SchedulerItem::Type type) { // Early return if name is invalid - no items to cancel - if (name_cstr == nullptr || name_cstr[0] == '\0') { + if (name_cstr == nullptr) { return false; } diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index 39cee5a876..a3da2c20f6 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -114,16 +114,17 @@ class Scheduler { name_is_dynamic = false; } - if (!name || !name[0]) { + if (!name) { + // nullptr case - no name provided name_.static_name = nullptr; } else if (make_copy) { - // Make a copy for dynamic strings + // Make a copy for dynamic strings (including empty strings) size_t len = strlen(name); name_.dynamic_name = new char[len + 1]; memcpy(name_.dynamic_name, name, len + 1); name_is_dynamic = true; } else { - // Use static string directly + // Use static string directly (including empty strings) name_.static_name = name; } } diff --git a/tests/integration/fixtures/scheduler_string_test.yaml b/tests/integration/fixtures/scheduler_string_test.yaml index 3dfe891370..c53ec392df 100644 --- a/tests/integration/fixtures/scheduler_string_test.yaml +++ b/tests/integration/fixtures/scheduler_string_test.yaml @@ -4,9 +4,7 @@ esphome: priority: -100 then: - logger.log: "Starting scheduler string tests" - platformio_options: - build_flags: - - "-DESPHOME_DEBUG_SCHEDULER" # Enable scheduler debug logging + debug_scheduler: true # Enable scheduler debug logging host: api: @@ -32,6 +30,12 @@ globals: - id: results_reported type: bool initial_value: 'false' + - id: edge_tests_done + type: bool + initial_value: 'false' + - id: empty_cancel_failed + type: bool + initial_value: 'false' script: - id: test_static_strings @@ -147,12 +151,106 @@ script: static TestDynamicDeferComponent test_dynamic_defer_component; test_dynamic_defer_component.test_dynamic_defer(); + - id: test_cancellation_edge_cases + then: + - logger.log: "Testing cancellation edge cases" + - lambda: |- + auto *component1 = id(test_sensor1); + // Use a different component for empty string tests to avoid interference + auto *component2 = id(test_sensor2); + + // Test 12: Cancel with empty string - regression test for issue #9599 + // First create a timeout with empty name on component2 to avoid interference + App.scheduler.set_timeout(component2, "", 500, []() { + ESP_LOGE("test", "ERROR: Empty name timeout fired - it should have been cancelled!"); + id(empty_cancel_failed) = true; + }); + + // Now cancel it - this should work after our fix + bool cancelled_empty = App.scheduler.cancel_timeout(component2, ""); + ESP_LOGI("test", "Cancel empty string result: %s (should be true)", cancelled_empty ? "true" : "false"); + if (!cancelled_empty) { + ESP_LOGE("test", "ERROR: Failed to cancel empty string timeout!"); + id(empty_cancel_failed) = true; + } + + // Test 13: Cancel non-existent timeout + bool cancelled_nonexistent = App.scheduler.cancel_timeout(component1, "does_not_exist"); + ESP_LOGI("test", "Cancel non-existent timeout result: %s", + cancelled_nonexistent ? "true (unexpected!)" : "false (expected)"); + + // Test 14: Multiple timeouts with same name - only last should execute + for (int i = 0; i < 5; i++) { + App.scheduler.set_timeout(component1, "duplicate_timeout", 200 + i*10, [i]() { + ESP_LOGI("test", "Duplicate timeout %d fired", i); + id(timeout_counter) += 1; + }); + } + ESP_LOGI("test", "Created 5 timeouts with same name 'duplicate_timeout'"); + + // Test 15: Multiple intervals with same name - only last should run + for (int i = 0; i < 3; i++) { + App.scheduler.set_interval(component1, "duplicate_interval", 300, [i]() { + ESP_LOGI("test", "Duplicate interval %d fired", i); + id(interval_counter) += 10; // Large increment to detect multiple + // Cancel after first execution + App.scheduler.cancel_interval(id(test_sensor1), "duplicate_interval"); + }); + } + ESP_LOGI("test", "Created 3 intervals with same name 'duplicate_interval'"); + + // Test 16: Cancel with nullptr protection (via empty const char*) + const char* null_name = ""; + App.scheduler.set_timeout(component2, null_name, 600, []() { + ESP_LOGE("test", "ERROR: Const char* empty timeout fired - should have been cancelled!"); + id(empty_cancel_failed) = true; + }); + bool cancelled_const_empty = App.scheduler.cancel_timeout(component2, null_name); + ESP_LOGI("test", "Cancel const char* empty result: %s (should be true)", + cancelled_const_empty ? "true" : "false"); + if (!cancelled_const_empty) { + ESP_LOGE("test", "ERROR: Failed to cancel const char* empty timeout!"); + id(empty_cancel_failed) = true; + } + + // Test 17: Rapid create/cancel/create with same name + App.scheduler.set_timeout(component1, "rapid_test", 5000, []() { + ESP_LOGI("test", "First rapid timeout - should not fire"); + id(timeout_counter) += 100; + }); + App.scheduler.cancel_timeout(component1, "rapid_test"); + App.scheduler.set_timeout(component1, "rapid_test", 250, []() { + ESP_LOGI("test", "Second rapid timeout - should fire"); + id(timeout_counter) += 1; + }); + + // Test 18: Cancel all with a specific name (multiple instances) + // Create multiple with same name + App.scheduler.set_timeout(component1, "multi_cancel", 300, []() { + ESP_LOGI("test", "Multi-cancel timeout 1"); + }); + App.scheduler.set_timeout(component1, "multi_cancel", 350, []() { + ESP_LOGI("test", "Multi-cancel timeout 2"); + }); + App.scheduler.set_timeout(component1, "multi_cancel", 400, []() { + ESP_LOGI("test", "Multi-cancel timeout 3 - only this should fire"); + id(timeout_counter) += 1; + }); + // Note: Each set_timeout with same name cancels the previous one automatically + - id: report_results then: - lambda: |- ESP_LOGI("test", "Final results - Timeouts: %d, Intervals: %d", id(timeout_counter), id(interval_counter)); + // Check if empty string cancellation test passed + if (id(empty_cancel_failed)) { + ESP_LOGE("test", "ERROR: Empty string cancellation test FAILED!"); + } else { + ESP_LOGI("test", "Empty string cancellation test PASSED"); + } + sensor: - platform: template name: Test Sensor 1 @@ -189,12 +287,23 @@ interval: - delay: 0.2s - script.execute: test_dynamic_strings + # Run cancellation edge case tests after dynamic tests + - interval: 0.2s + then: + - if: + condition: + lambda: 'return id(dynamic_tests_done) && !id(edge_tests_done);' + then: + - lambda: 'id(edge_tests_done) = true;' + - delay: 0.5s + - script.execute: test_cancellation_edge_cases + # Report results after all tests - interval: 0.2s then: - if: condition: - lambda: 'return id(dynamic_tests_done) && !id(results_reported);' + lambda: 'return id(edge_tests_done) && !id(results_reported);' then: - lambda: 'id(results_reported) = true;' - delay: 1s diff --git a/tests/integration/test_scheduler_heap_stress.py b/tests/integration/test_scheduler_heap_stress.py index 3c757bfc9d..229b5b98df 100644 --- a/tests/integration/test_scheduler_heap_stress.py +++ b/tests/integration/test_scheduler_heap_stress.py @@ -103,13 +103,14 @@ async def test_scheduler_heap_stress( # Wait for all callbacks to execute (should be quick, but give more time for scheduling) try: - await asyncio.wait_for(test_complete_future, timeout=60.0) + await asyncio.wait_for(test_complete_future, timeout=10.0) except asyncio.TimeoutError: # Report how many we got + missing_ids = sorted(set(range(1000)) - executed_callbacks) pytest.fail( f"Stress test timed out. Only {len(executed_callbacks)} of " f"1000 callbacks executed. Missing IDs: " - f"{sorted(set(range(1000)) - executed_callbacks)[:10]}..." + f"{missing_ids[:20]}... (total missing: {len(missing_ids)})" ) # Verify all callbacks executed From 121ed687f383e30b2824b8a2157b8d1f758d43dc Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Thu, 17 Jul 2025 20:08:18 -0700 Subject: [PATCH 07/38] [logger] fix on_message (#9642) Co-authored-by: Samuel Sieb Co-authored-by: J. Nick Koston Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/logger/__init__.py | 4 ++-- .../logger/test-on_message.host.yaml | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 tests/components/logger/test-on_message.host.yaml diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 9ac2999696..cf2af17677 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -183,7 +183,7 @@ def validate_local_no_higher_than_global(value): Logger = logger_ns.class_("Logger", cg.Component) LoggerMessageTrigger = logger_ns.class_( "LoggerMessageTrigger", - automation.Trigger.template(cg.int_, cg.const_char_ptr, cg.const_char_ptr), + automation.Trigger.template(cg.uint8, cg.const_char_ptr, cg.const_char_ptr), ) CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH = "esp8266_store_log_strings_in_flash" @@ -368,7 +368,7 @@ async def to_code(config): await automation.build_automation( trigger, [ - (cg.int_, "level"), + (cg.uint8, "level"), (cg.const_char_ptr, "tag"), (cg.const_char_ptr, "message"), ], diff --git a/tests/components/logger/test-on_message.host.yaml b/tests/components/logger/test-on_message.host.yaml new file mode 100644 index 0000000000..12211a257b --- /dev/null +++ b/tests/components/logger/test-on_message.host.yaml @@ -0,0 +1,18 @@ +logger: + id: logger_id + level: DEBUG + on_message: + - level: DEBUG + then: + - lambda: |- + ESP_LOGD("test", "Got message level %d: %s - %s", level, tag, message); + - level: WARN + then: + - lambda: |- + ESP_LOGW("test", "Warning level %d from %s", level, tag); + - level: ERROR + then: + - lambda: |- + // Test that level is uint8_t by using it in calculations + uint8_t adjusted_level = level + 1; + ESP_LOGE("test", "Error with adjusted level %d", adjusted_level); From 11a4115e30ead221f7886e64b2c565a6b3fd644b Mon Sep 17 00:00:00 2001 From: "@RubenKelevra" Date: Fri, 18 Jul 2025 05:09:24 +0200 Subject: [PATCH 08/38] esp32_camera: deprecate i2c_pins; throw error if combined with i2c: block (#9615) --- esphome/components/esp32_camera/__init__.py | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 6e36f7d5a7..a99ec34087 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -1,3 +1,5 @@ +import logging + from esphome import automation, pins import esphome.codegen as cg from esphome.components import i2c @@ -8,6 +10,7 @@ from esphome.const import ( CONF_CONTRAST, CONF_DATA_PINS, CONF_FREQUENCY, + CONF_I2C, CONF_I2C_ID, CONF_ID, CONF_PIN, @@ -20,6 +23,9 @@ from esphome.const import ( ) from esphome.core import CORE from esphome.core.entity_helpers import setup_entity +import esphome.final_validate as fv + +_LOGGER = logging.getLogger(__name__) DEPENDENCIES = ["esp32"] @@ -250,6 +256,22 @@ CONFIG_SCHEMA = cv.All( cv.has_exactly_one_key(CONF_I2C_PINS, CONF_I2C_ID), ) + +def _final_validate(config): + if CONF_I2C_PINS not in config: + return + fconf = fv.full_config.get() + if fconf.get(CONF_I2C): + raise cv.Invalid( + "The `i2c_pins:` config option is incompatible with an dedicated `i2c:` block, use `i2c_id` instead" + ) + _LOGGER.warning( + "The `i2c_pins:` config option is deprecated. Use `i2c_id:` with a dedicated `i2c:` definition instead." + ) + + +FINAL_VALIDATE_SCHEMA = _final_validate + SETTERS = { # pin assignment CONF_DATA_PINS: "set_data_pins", From 84a77ee427b391bd7f498d0a82c5034c748c473c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 17 Jul 2025 18:07:59 -1000 Subject: [PATCH 09/38] [scheduler] Fix DelayAction cancellation in restart mode scripts (#9646) --- esphome/core/base_automation.h | 4 +- .../fixtures/delay_action_cancellation.yaml | 24 +++++ tests/integration/test_automations.py | 91 +++++++++++++++++++ 3 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 tests/integration/fixtures/delay_action_cancellation.yaml create mode 100644 tests/integration/test_automations.py diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index 13179b90bb..740e10700b 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -158,14 +158,14 @@ template class DelayAction : public Action, public Compon void play_complex(Ts... x) override { auto f = std::bind(&DelayAction::play_next_, this, x...); this->num_running_++; - this->set_timeout(this->delay_.value(x...), f); + this->set_timeout("delay", this->delay_.value(x...), f); } float get_setup_priority() const override { return setup_priority::HARDWARE; } void play(Ts... x) override { /* ignore - see play_complex */ } - void stop() override { this->cancel_timeout(""); } + void stop() override { this->cancel_timeout("delay"); } }; template class LambdaAction : public Action { diff --git a/tests/integration/fixtures/delay_action_cancellation.yaml b/tests/integration/fixtures/delay_action_cancellation.yaml new file mode 100644 index 0000000000..e0dd427c2d --- /dev/null +++ b/tests/integration/fixtures/delay_action_cancellation.yaml @@ -0,0 +1,24 @@ +esphome: + name: test-delay-action + +host: +api: + actions: + - action: start_delay_then_restart + then: + - logger.log: "Starting first script execution" + - script.execute: test_delay_script + - delay: 250ms # Give first script time to start delay + - logger.log: "Restarting script (should cancel first delay)" + - script.execute: test_delay_script + +logger: + level: DEBUG + +script: + - id: test_delay_script + mode: restart + then: + - logger.log: "Script started, beginning delay" + - delay: 500ms # Long enough that it won't complete before restart + - logger.log: "Delay completed successfully" diff --git a/tests/integration/test_automations.py b/tests/integration/test_automations.py new file mode 100644 index 0000000000..bd2082e86b --- /dev/null +++ b/tests/integration/test_automations.py @@ -0,0 +1,91 @@ +"""Test ESPHome automations functionality.""" + +from __future__ import annotations + +import asyncio +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_delay_action_cancellation( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Test that delay actions can be properly cancelled when script restarts.""" + loop = asyncio.get_running_loop() + + # Track log messages with timestamps + log_entries: list[tuple[float, str]] = [] + script_starts: list[float] = [] + delay_completions: list[float] = [] + script_restart_logged = False + test_started_time = None + + # Patterns to match + test_start_pattern = re.compile(r"Starting first script execution") + script_start_pattern = re.compile(r"Script started, beginning delay") + restart_pattern = re.compile(r"Restarting script \(should cancel first delay\)") + delay_complete_pattern = re.compile(r"Delay completed successfully") + + # Future to track when we can check results + second_script_started = loop.create_future() + + def check_output(line: str) -> None: + """Check log output for expected messages.""" + nonlocal script_restart_logged, test_started_time + + current_time = loop.time() + log_entries.append((current_time, line)) + + if test_start_pattern.search(line): + test_started_time = current_time + elif script_start_pattern.search(line) and test_started_time: + script_starts.append(current_time) + if len(script_starts) == 2 and not second_script_started.done(): + second_script_started.set_result(True) + elif restart_pattern.search(line): + script_restart_logged = True + elif delay_complete_pattern.search(line): + delay_completions.append(current_time) + + async with ( + run_compiled(yaml_config, line_callback=check_output), + api_client_connected() as client, + ): + # Get services + entities, services = await client.list_entities_services() + + # Find our test service + test_service = next( + (s for s in services if s.name == "start_delay_then_restart"), None + ) + assert test_service is not None, "start_delay_then_restart service not found" + + # Execute the test sequence + client.execute_service(test_service, {}) + + # Wait for the second script to start + await asyncio.wait_for(second_script_started, timeout=5.0) + + # Wait for potential delay completion + await asyncio.sleep(0.75) # Original delay was 500ms + + # Check results + assert len(script_starts) == 2, ( + f"Script should have started twice, but started {len(script_starts)} times" + ) + assert script_restart_logged, "Script restart was not logged" + + # Verify we got exactly one completion and it happened ~500ms after the second start + assert len(delay_completions) == 1, ( + f"Expected 1 delay completion, got {len(delay_completions)}" + ) + time_from_second_start = delay_completions[0] - script_starts[1] + assert 0.4 < time_from_second_start < 0.6, ( + f"Delay completed {time_from_second_start:.3f}s after second start, expected ~0.5s" + ) From 85495d38b736b8a4bdd094c8f9129bbf35cafaa9 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 18 Jul 2025 14:14:21 +1000 Subject: [PATCH 10/38] [lvgl] Fix meter rotation (#9605) Co-authored-by: clydeps --- esphome/components/lvgl/widgets/meter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/lvgl/widgets/meter.py b/esphome/components/lvgl/widgets/meter.py index 04de195e3c..acec986f99 100644 --- a/esphome/components/lvgl/widgets/meter.py +++ b/esphome/components/lvgl/widgets/meter.py @@ -14,6 +14,7 @@ from esphome.const import ( CONF_VALUE, CONF_WIDTH, ) +from esphome.cpp_generator import IntLiteral from ..automation import action_to_code from ..defines import ( @@ -188,6 +189,8 @@ class MeterType(WidgetType): rotation = 90 + (360 - scale_conf[CONF_ANGLE_RANGE]) / 2 if CONF_ROTATION in scale_conf: rotation = await lv_angle.process(scale_conf[CONF_ROTATION]) + if isinstance(rotation, IntLiteral): + rotation = int(str(rotation)) // 10 with LocalVariable( "meter_var", "lv_meter_scale_t", lv_expr.meter_add_scale(var) ) as meter_var: From cc2c1b1d89216254b872f820f0a7aa6361b43cd7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 01:40:14 -1000 Subject: [PATCH 11/38] [libretiny] Remove unsupported lock-free queue and event pool implementations (#9653) --- esphome/core/event_pool.h | 4 ++-- esphome/core/lock_free_queue.h | 9 ++------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/esphome/core/event_pool.h b/esphome/core/event_pool.h index 69e03bafac..928a4e7dee 100644 --- a/esphome/core/event_pool.h +++ b/esphome/core/event_pool.h @@ -1,6 +1,6 @@ #pragma once -#if defined(USE_ESP32) || defined(USE_LIBRETINY) +#if defined(USE_ESP32) #include #include @@ -78,4 +78,4 @@ template class EventPool { } // namespace esphome -#endif // defined(USE_ESP32) || defined(USE_LIBRETINY) +#endif // defined(USE_ESP32) diff --git a/esphome/core/lock_free_queue.h b/esphome/core/lock_free_queue.h index f35cfa5af9..de07b0ebba 100644 --- a/esphome/core/lock_free_queue.h +++ b/esphome/core/lock_free_queue.h @@ -1,17 +1,12 @@ #pragma once -#if defined(USE_ESP32) || defined(USE_LIBRETINY) +#if defined(USE_ESP32) #include #include -#if defined(USE_ESP32) #include #include -#elif defined(USE_LIBRETINY) -#include -#include -#endif /* * Lock-free queue for single-producer single-consumer scenarios. @@ -148,4 +143,4 @@ template class NotifyingLockFreeQueue : public LockFreeQu } // namespace esphome -#endif // defined(USE_ESP32) || defined(USE_LIBRETINY) +#endif // defined(USE_ESP32) From 976a1e27b4f0e9618c46a77a9ea56d36931289b8 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 18 Jul 2025 23:49:34 +1200 Subject: [PATCH 12/38] [lvgl] Prevent keyerror on min/max value widgets with no default (#9660) --- esphome/components/lvgl/types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/lvgl/types.py b/esphome/components/lvgl/types.py index 40e69119f0..10b6f63528 100644 --- a/esphome/components/lvgl/types.py +++ b/esphome/components/lvgl/types.py @@ -192,7 +192,7 @@ class WidgetType: class NumberType(WidgetType): def get_max(self, config: dict): - return int(config[CONF_MAX_VALUE] or 100) + return int(config.get(CONF_MAX_VALUE, 100)) def get_min(self, config: dict): - return int(config[CONF_MIN_VALUE] or 0) + return int(config.get(CONF_MIN_VALUE, 0)) From 32d8c60a0b4a6d45f5b031b83545ec503e0dd4a1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 09:20:08 -1000 Subject: [PATCH 13/38] Fix AsyncTCP version mismatch between platformio.ini and async_tcp component (#9676) --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 8fcc578103..7fb301c08b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -138,7 +138,7 @@ lib_deps = WiFi ; wifi,web_server_base,ethernet (Arduino built-in) Update ; ota,web_server_base (Arduino built-in) ${common:arduino.lib_deps} - ESP32Async/AsyncTCP@3.4.4 ; async_tcp + ESP32Async/AsyncTCP@3.4.5 ; async_tcp NetworkClientSecure ; http_request,nextion (Arduino built-in) HTTPClient ; http_request,nextion (Arduino built-in) ESPmDNS ; mdns (Arduino built-in) From 8664ec0a3b42366c1823261430c297910d59914a Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Fri, 18 Jul 2025 20:21:36 +0100 Subject: [PATCH 14/38] [speaker] Media player's pipeline properly returns playing state near end of file (#9668) --- .../speaker/media_player/audio_pipeline.cpp | 16 ++++++++++++++-- .../speaker/media_player/audio_pipeline.h | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/esphome/components/speaker/media_player/audio_pipeline.cpp b/esphome/components/speaker/media_player/audio_pipeline.cpp index 333a076bec..8811ea1644 100644 --- a/esphome/components/speaker/media_player/audio_pipeline.cpp +++ b/esphome/components/speaker/media_player/audio_pipeline.cpp @@ -200,7 +200,7 @@ AudioPipelineState AudioPipeline::process_state() { if ((this->read_task_handle_ != nullptr) || (this->decode_task_handle_ != nullptr)) { this->delete_tasks_(); if (this->hard_stop_) { - // Stop command was sent, so immediately end of the playback + // Stop command was sent, so immediately end the playback this->speaker_->stop(); this->hard_stop_ = false; } else { @@ -210,13 +210,25 @@ AudioPipelineState AudioPipeline::process_state() { } } this->is_playing_ = false; - return AudioPipelineState::STOPPED; + if (!this->speaker_->is_running()) { + return AudioPipelineState::STOPPED; + } else { + this->is_finishing_ = true; + } } if (this->pause_state_) { return AudioPipelineState::PAUSED; } + if (this->is_finishing_) { + if (!this->speaker_->is_running()) { + this->is_finishing_ = false; + } else { + return AudioPipelineState::PLAYING; + } + } + if ((this->read_task_handle_ == nullptr) && (this->decode_task_handle_ == nullptr)) { // No tasks are running, so the pipeline is stopped. xEventGroupClearBits(this->event_group_, EventGroupBits::PIPELINE_COMMAND_STOP); diff --git a/esphome/components/speaker/media_player/audio_pipeline.h b/esphome/components/speaker/media_player/audio_pipeline.h index 722d9cbb2a..98f43fda6e 100644 --- a/esphome/components/speaker/media_player/audio_pipeline.h +++ b/esphome/components/speaker/media_player/audio_pipeline.h @@ -114,6 +114,7 @@ class AudioPipeline { bool hard_stop_{false}; bool is_playing_{false}; + bool is_finishing_{false}; bool pause_state_{false}; bool task_stack_in_psram_; From 84607c1255a40935a843db511a126c42a46e1644 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Fri, 18 Jul 2025 20:24:55 +0100 Subject: [PATCH 15/38] [voice_assistant] Use media player callbacks to track TTS response status (#9670) --- .../voice_assistant/voice_assistant.cpp | 72 +++++++++++++------ .../voice_assistant/voice_assistant.h | 13 +++- 2 files changed, 63 insertions(+), 22 deletions(-) diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 9cf7d10936..647bbc7653 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -35,6 +35,27 @@ void VoiceAssistant::setup() { temp_ring_buffer->write((void *) data.data(), data.size()); } }); + +#ifdef USE_MEDIA_PLAYER + if (this->media_player_ != nullptr) { + this->media_player_->add_on_state_callback([this]() { + switch (this->media_player_->state) { + case media_player::MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING: + if (this->media_player_response_state_ == MediaPlayerResponseState::URL_SENT) { + // State changed to announcing after receiving the url + this->media_player_response_state_ = MediaPlayerResponseState::PLAYING; + } + break; + default: + if (this->media_player_response_state_ == MediaPlayerResponseState::PLAYING) { + // No longer announcing the TTS response + this->media_player_response_state_ = MediaPlayerResponseState::FINISHED; + } + break; + } + }); + } +#endif } float VoiceAssistant::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } @@ -223,6 +244,13 @@ void VoiceAssistant::loop() { msg.wake_word_phrase = this->wake_word_; this->wake_word_ = ""; + // Reset media player state tracking +#ifdef USE_MEDIA_PLAYER + if (this->media_player_ != nullptr) { + this->media_player_response_state_ = MediaPlayerResponseState::IDLE; + } +#endif + if (this->api_client_ == nullptr || !this->api_client_->send_message(msg)) { ESP_LOGW(TAG, "Could not request start"); this->error_trigger_->trigger("not-connected", "Could not request start"); @@ -314,17 +342,10 @@ void VoiceAssistant::loop() { #endif #ifdef USE_MEDIA_PLAYER if (this->media_player_ != nullptr) { - playing = (this->media_player_->state == media_player::MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING); + playing = (this->media_player_response_state_ == MediaPlayerResponseState::PLAYING); - if (playing && this->media_player_wait_for_announcement_start_) { - // Announcement has started playing, wait for it to finish - this->media_player_wait_for_announcement_start_ = false; - this->media_player_wait_for_announcement_end_ = true; - } - - if (!playing && this->media_player_wait_for_announcement_end_) { - // Announcement has finished playing - this->media_player_wait_for_announcement_end_ = false; + if (this->media_player_response_state_ == MediaPlayerResponseState::FINISHED) { + this->media_player_response_state_ = MediaPlayerResponseState::IDLE; this->cancel_timeout("playing"); ESP_LOGD(TAG, "Announcement finished playing"); this->set_state_(State::RESPONSE_FINISHED, State::RESPONSE_FINISHED); @@ -555,7 +576,7 @@ void VoiceAssistant::request_stop() { break; case State::AWAITING_RESPONSE: this->signal_stop_(); - // Fallthrough intended to stop a streaming TTS announcement that has potentially started + break; case State::STREAMING_RESPONSE: #ifdef USE_MEDIA_PLAYER // Stop any ongoing media player announcement @@ -565,6 +586,10 @@ void VoiceAssistant::request_stop() { .set_announcement(true) .perform(); } + if (this->started_streaming_tts_) { + // Haven't reached the TTS_END stage, so send the stop signal to HA. + this->signal_stop_(); + } #endif break; case State::RESPONSE_FINISHED: @@ -648,13 +673,16 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { 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_response_state_ = MediaPlayerResponseState::URL_SENT; + 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; + this->start_playback_timeout_(); + tts_url_for_trigger = this->tts_response_url_; this->tts_response_url_.clear(); // Reset streaming URL + this->set_state_(State::STREAMING_RESPONSE, State::STREAMING_RESPONSE); } } } @@ -713,18 +741,22 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { this->defer([this, url]() { #ifdef USE_MEDIA_PLAYER if ((this->media_player_ != nullptr) && (!this->started_streaming_tts_)) { + this->media_player_response_state_ = MediaPlayerResponseState::URL_SENT; + this->media_player_->make_call().set_media_url(url).set_announcement(true).perform(); - this->media_player_wait_for_announcement_start_ = true; - this->media_player_wait_for_announcement_end_ = false; - // Start the playback timeout, as the media player state isn't immediately updated this->start_playback_timeout_(); } + this->started_streaming_tts_ = false; // Helps indicate reaching the TTS_END stage #endif this->tts_end_trigger_->trigger(url); }); State new_state = this->local_output_ ? State::STREAMING_RESPONSE : State::IDLE; - this->set_state_(new_state, new_state); + if (new_state != this->state_) { + // Don't needlessly change the state. The intent progress stage may have already changed the state to streaming + // response. + this->set_state_(new_state, new_state); + } break; } case api::enums::VOICE_ASSISTANT_RUN_END: { @@ -875,6 +907,9 @@ void VoiceAssistant::on_announce(const api::VoiceAssistantAnnounceRequest &msg) #ifdef USE_MEDIA_PLAYER if (this->media_player_ != nullptr) { this->tts_start_trigger_->trigger(msg.text); + + this->media_player_response_state_ = MediaPlayerResponseState::URL_SENT; + if (!msg.preannounce_media_id.empty()) { this->media_player_->make_call().set_media_url(msg.preannounce_media_id).set_announcement(true).perform(); } @@ -886,9 +921,6 @@ void VoiceAssistant::on_announce(const api::VoiceAssistantAnnounceRequest &msg) .perform(); this->continue_conversation_ = msg.start_conversation; - this->media_player_wait_for_announcement_start_ = true; - this->media_player_wait_for_announcement_end_ = false; - // Start the playback timeout, as the media player state isn't immediately updated this->start_playback_timeout_(); if (this->continuous_) { diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index 2424ea6052..95f77dbf09 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -90,6 +90,15 @@ struct Configuration { uint32_t max_active_wake_words; }; +#ifdef USE_MEDIA_PLAYER +enum class MediaPlayerResponseState { + IDLE, + URL_SENT, + PLAYING, + FINISHED, +}; +#endif + class VoiceAssistant : public Component { public: VoiceAssistant(); @@ -272,8 +281,8 @@ class VoiceAssistant : public Component { 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}; + + MediaPlayerResponseState media_player_response_state_{MediaPlayerResponseState::IDLE}; #endif bool local_output_{false}; From 8a45e877bbf4fbc62c669a3423ec371386d5afbe Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 11:32:20 -1000 Subject: [PATCH 16/38] [gpio] Disable interrupt mode by default for LibreTiny platforms (#9687) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../components/gpio/binary_sensor/__init__.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/esphome/components/gpio/binary_sensor/__init__.py b/esphome/components/gpio/binary_sensor/__init__.py index 867a8efe49..59f54520fa 100644 --- a/esphome/components/gpio/binary_sensor/__init__.py +++ b/esphome/components/gpio/binary_sensor/__init__.py @@ -29,7 +29,21 @@ CONFIG_SCHEMA = ( .extend( { cv.Required(CONF_PIN): pins.gpio_input_pin_schema, - cv.Optional(CONF_USE_INTERRUPT, default=True): cv.boolean, + # Interrupts are disabled by default for bk72xx, ln882x, and rtl87xx platforms + # due to hardware limitations or lack of reliable interrupt support. This ensures + # stable operation on these platforms. Future maintainers should verify platform + # capabilities before changing this default behavior. + cv.SplitDefault( + CONF_USE_INTERRUPT, + bk72xx=False, + esp32=True, + esp8266=True, + host=True, + ln882x=False, + nrf52=True, + rp2040=True, + rtl87xx=False, + ): cv.boolean, cv.Optional(CONF_INTERRUPT_TYPE, default="ANY"): cv.enum( INTERRUPT_TYPES, upper=True ), From 576ce7ee351c448e49ba290ed7a65225bcda8f60 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 19 Jul 2025 09:56:08 +1200 Subject: [PATCH 17/38] Bump version to 2025.7.2 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index 9c14041478..a601fcc8eb 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.7.1 +PROJECT_NUMBER = 2025.7.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 1eb6db118a..93b509f72a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2025.7.1" +__version__ = "2025.7.2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 19a68dc6507fe4df81fec221d63129eb69232d1e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 19 Jul 2025 10:55:22 +1200 Subject: [PATCH 18/38] Add core team as codeowner of .github folder (#9663) --- CODEOWNERS | 1 + script/build_codeowners.py | 1 + 2 files changed, 2 insertions(+) diff --git a/CODEOWNERS b/CODEOWNERS index 257f927fd9..cd11c7796f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -9,6 +9,7 @@ pyproject.toml @esphome/core esphome/*.py @esphome/core esphome/core/* @esphome/core +.github/** @esphome/core # Integrations esphome/components/a01nyub/* @MrSuicideParrot diff --git a/script/build_codeowners.py b/script/build_codeowners.py index 523fe8ac7f..4581620095 100755 --- a/script/build_codeowners.py +++ b/script/build_codeowners.py @@ -31,6 +31,7 @@ BASE = """ pyproject.toml @esphome/core esphome/*.py @esphome/core esphome/core/* @esphome/core +.github/** @esphome/core # Integrations """.strip() From 1a62b75ec349c4b1663c6e8a1171d80fd7f8b76c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 14:19:06 -1000 Subject: [PATCH 19/38] [bluetooth_proxy] Fix performance issue and service discovery on disconnect --- .../bluetooth_proxy/bluetooth_connection.cpp | 172 +++++++++++++++++- .../bluetooth_proxy/bluetooth_connection.h | 4 + .../bluetooth_proxy/bluetooth_proxy.cpp | 135 +------------- .../bluetooth_proxy/bluetooth_proxy.h | 6 +- 4 files changed, 177 insertions(+), 140 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index 2bfccdb438..3bf8711714 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -13,11 +13,174 @@ namespace bluetooth_proxy { static const char *const TAG = "bluetooth_proxy.connection"; +// Helper function from bluetooth_proxy.cpp +static std::vector get_128bit_uuid_vec(esp_bt_uuid_t uuid_source) { + esp_bt_uuid_t uuid = espbt::ESPBTUUID::from_uuid(uuid_source).as_128bit().get_uuid(); + return std::vector{((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) | + ((uint64_t) uuid.uuid.uuid128[13] << 40) | ((uint64_t) uuid.uuid.uuid128[12] << 32) | + ((uint64_t) uuid.uuid.uuid128[11] << 24) | ((uint64_t) uuid.uuid.uuid128[10] << 16) | + ((uint64_t) uuid.uuid.uuid128[9] << 8) | ((uint64_t) uuid.uuid.uuid128[8]), + ((uint64_t) uuid.uuid.uuid128[7] << 56) | ((uint64_t) uuid.uuid.uuid128[6] << 48) | + ((uint64_t) uuid.uuid.uuid128[5] << 40) | ((uint64_t) uuid.uuid.uuid128[4] << 32) | + ((uint64_t) uuid.uuid.uuid128[3] << 24) | ((uint64_t) uuid.uuid.uuid128[2] << 16) | + ((uint64_t) uuid.uuid.uuid128[1] << 8) | ((uint64_t) uuid.uuid.uuid128[0])}; +} + void BluetoothConnection::dump_config() { ESP_LOGCONFIG(TAG, "BLE Connection:"); BLEClientBase::dump_config(); } +void BluetoothConnection::loop() { + BLEClientBase::loop(); + + // Early return if no active connection or not in service discovery phase + if (this->address_ == 0 || this->send_service_ < 0 || this->send_service_ > this->service_count_) { + return; + } + + // Handle service discovery + this->send_service_for_discovery_(); +} + +void BluetoothConnection::reset_connection_() { + // Important: If we were in the middle of sending services, we do NOT send + // send_gatt_services_done() here. This ensures the client knows that + // the service discovery was interrupted and can retry. The client + // (aioesphomeapi) implements a 30-second timeout (DEFAULT_BLE_TIMEOUT) + // to detect incomplete service discovery rather than relying on us to + // tell them about a partial list. + this->set_address(0); + this->send_service_ = DONE_SENDING_SERVICES; + this->proxy_->send_connections_free(); +} + +void BluetoothConnection::send_service_for_discovery_() { + if (this->send_service_ == this->service_count_) { + this->send_service_ = DONE_SENDING_SERVICES; + this->proxy_->send_gatt_services_done(this->get_address()); + if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE || + this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) { + this->release_services(); + } + return; + } + + // Send next service + esp_gattc_service_elem_t service_result; + uint16_t service_count = 1; + esp_gatt_status_t service_status = esp_ble_gattc_get_service(this->get_gattc_if(), this->get_conn_id(), nullptr, + &service_result, &service_count, this->send_service_); + this->send_service_++; + + if (service_status != ESP_GATT_OK) { + ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service error at offset=%d, status=%d", this->get_connection_index(), + this->address_str().c_str(), this->send_service_ - 1, service_status); + return; + } + + if (service_count == 0) { + ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service missing, service_count=%d", this->get_connection_index(), + this->address_str().c_str(), service_count); + return; + } + + api::BluetoothGATTGetServicesResponse resp; + resp.address = this->get_address(); + resp.services.reserve(1); // Always one service per response in this implementation + api::BluetoothGATTService service_resp; + service_resp.uuid = get_128bit_uuid_vec(service_result.uuid); + service_resp.handle = service_result.start_handle; + + // Get the number of characteristics directly with one call + uint16_t total_char_count = 0; + esp_gatt_status_t char_count_status = + esp_ble_gattc_get_attr_count(this->get_gattc_if(), this->get_conn_id(), ESP_GATT_DB_CHARACTERISTIC, + service_result.start_handle, service_result.end_handle, 0, &total_char_count); + + if (char_count_status == ESP_GATT_OK && total_char_count > 0) { + // Only reserve if we successfully got a count + service_resp.characteristics.reserve(total_char_count); + } else if (char_count_status != ESP_GATT_OK) { + ESP_LOGW(TAG, "[%d] [%s] Error getting characteristic count, status=%d", this->get_connection_index(), + this->address_str().c_str(), char_count_status); + } + + // Now process characteristics + uint16_t char_offset = 0; + esp_gattc_char_elem_t char_result; + while (true) { // characteristics + uint16_t char_count = 1; + esp_gatt_status_t char_status = + esp_ble_gattc_get_all_char(this->get_gattc_if(), this->get_conn_id(), service_result.start_handle, + service_result.end_handle, &char_result, &char_count, char_offset); + if (char_status == ESP_GATT_INVALID_OFFSET || char_status == ESP_GATT_NOT_FOUND) { + break; + } + if (char_status != ESP_GATT_OK) { + ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->get_connection_index(), + this->address_str().c_str(), char_status); + break; + } + if (char_count == 0) { + break; + } + + api::BluetoothGATTCharacteristic characteristic_resp; + characteristic_resp.uuid = get_128bit_uuid_vec(char_result.uuid); + characteristic_resp.handle = char_result.char_handle; + characteristic_resp.properties = char_result.properties; + char_offset++; + + // Get the number of descriptors directly with one call + uint16_t total_desc_count = 0; + esp_gatt_status_t desc_count_status = + esp_ble_gattc_get_attr_count(this->get_gattc_if(), this->get_conn_id(), ESP_GATT_DB_DESCRIPTOR, + char_result.char_handle, service_result.end_handle, 0, &total_desc_count); + + if (desc_count_status == ESP_GATT_OK && total_desc_count > 0) { + // Only reserve if we successfully got a count + characteristic_resp.descriptors.reserve(total_desc_count); + } else if (desc_count_status != ESP_GATT_OK) { + ESP_LOGW(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d", + this->get_connection_index(), this->address_str().c_str(), char_result.char_handle, desc_count_status); + } + + // Now process descriptors + uint16_t desc_offset = 0; + esp_gattc_descr_elem_t desc_result; + while (true) { // descriptors + uint16_t desc_count = 1; + esp_gatt_status_t desc_status = esp_ble_gattc_get_all_descr( + this->get_gattc_if(), this->get_conn_id(), char_result.char_handle, &desc_result, &desc_count, desc_offset); + if (desc_status == ESP_GATT_INVALID_OFFSET || desc_status == ESP_GATT_NOT_FOUND) { + break; + } + if (desc_status != ESP_GATT_OK) { + ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d", this->get_connection_index(), + this->address_str().c_str(), desc_status); + break; + } + if (desc_count == 0) { + break; + } + + api::BluetoothGATTDescriptor descriptor_resp; + descriptor_resp.uuid = get_128bit_uuid_vec(desc_result.uuid); + descriptor_resp.handle = desc_result.handle; + characteristic_resp.descriptors.push_back(std::move(descriptor_resp)); + desc_offset++; + } + service_resp.characteristics.push_back(std::move(characteristic_resp)); + } + resp.services.push_back(std::move(service_resp)); + + auto *api_conn = this->proxy_->get_api_connection(); + if (api_conn != nullptr) { + api_conn->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE); + } +} + bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { if (!BLEClientBase::gattc_event_handler(event, gattc_if, param)) @@ -26,21 +189,18 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga switch (event) { case ESP_GATTC_DISCONNECT_EVT: { this->proxy_->send_device_connection(this->address_, false, 0, param->disconnect.reason); - this->set_address(0); - this->proxy_->send_connections_free(); + this->reset_connection_(); break; } case ESP_GATTC_CLOSE_EVT: { this->proxy_->send_device_connection(this->address_, false, 0, param->close.reason); - this->set_address(0); - this->proxy_->send_connections_free(); + this->reset_connection_(); break; } case ESP_GATTC_OPEN_EVT: { if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) { this->proxy_->send_device_connection(this->address_, false, 0, param->open.status); - this->set_address(0); - this->proxy_->send_connections_free(); + this->reset_connection_(); } else if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) { this->proxy_->send_device_connection(this->address_, true, this->mtu_); this->proxy_->send_connections_free(); diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.h b/esphome/components/bluetooth_proxy/bluetooth_connection.h index 73c034d93b..4c4eff0919 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.h +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.h @@ -12,6 +12,7 @@ class BluetoothProxy; class BluetoothConnection : public esp32_ble_client::BLEClientBase { public: void dump_config() override; + void loop() override; bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override; void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; @@ -27,6 +28,9 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase { protected: friend class BluetoothProxy; + void send_service_for_discovery_(); + void reset_connection_(); + // Memory optimized layout for 32-bit systems // Group 1: Pointers (4 bytes each, naturally aligned) BluetoothProxy *proxy_; diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index 7d12842a24..f4b63f3a5d 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -11,19 +11,6 @@ namespace esphome { namespace bluetooth_proxy { static const char *const TAG = "bluetooth_proxy"; -static const int DONE_SENDING_SERVICES = -2; - -std::vector get_128bit_uuid_vec(esp_bt_uuid_t uuid_source) { - esp_bt_uuid_t uuid = espbt::ESPBTUUID::from_uuid(uuid_source).as_128bit().get_uuid(); - return std::vector{((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) | - ((uint64_t) uuid.uuid.uuid128[13] << 40) | ((uint64_t) uuid.uuid.uuid128[12] << 32) | - ((uint64_t) uuid.uuid.uuid128[11] << 24) | ((uint64_t) uuid.uuid.uuid128[10] << 16) | - ((uint64_t) uuid.uuid.uuid128[9] << 8) | ((uint64_t) uuid.uuid.uuid128[8]), - ((uint64_t) uuid.uuid.uuid128[7] << 56) | ((uint64_t) uuid.uuid.uuid128[6] << 48) | - ((uint64_t) uuid.uuid.uuid128[5] << 40) | ((uint64_t) uuid.uuid.uuid128[4] << 32) | - ((uint64_t) uuid.uuid.uuid128[3] << 24) | ((uint64_t) uuid.uuid.uuid128[2] << 16) | - ((uint64_t) uuid.uuid.uuid128[1] << 8) | ((uint64_t) uuid.uuid.uuid128[0])}; -} // Batch size for BLE advertisements to maximize WiFi efficiency // Each advertisement is up to 80 bytes when packaged (including protocol overhead) @@ -213,130 +200,12 @@ void BluetoothProxy::loop() { } // Flush any pending BLE advertisements that have been accumulated but not yet sent - static uint32_t last_flush_time = 0; uint32_t now = App.get_loop_component_start_time(); // Flush accumulated advertisements every 100ms - if (now - last_flush_time >= 100) { + if (now - this->last_advertisement_flush_time_ >= 100) { this->flush_pending_advertisements(); - last_flush_time = now; - } - for (auto *connection : this->connections_) { - if (connection->send_service_ == connection->service_count_) { - connection->send_service_ = DONE_SENDING_SERVICES; - this->send_gatt_services_done(connection->get_address()); - if (connection->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE || - connection->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) { - connection->release_services(); - } - } else if (connection->send_service_ >= 0) { - esp_gattc_service_elem_t service_result; - uint16_t service_count = 1; - esp_gatt_status_t service_status = - esp_ble_gattc_get_service(connection->get_gattc_if(), connection->get_conn_id(), nullptr, &service_result, - &service_count, connection->send_service_); - connection->send_service_++; - if (service_status != ESP_GATT_OK) { - ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service error at offset=%d, status=%d", - connection->get_connection_index(), connection->address_str().c_str(), connection->send_service_ - 1, - service_status); - continue; - } - if (service_count == 0) { - ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service missing, service_count=%d", - connection->get_connection_index(), connection->address_str().c_str(), service_count); - continue; - } - api::BluetoothGATTGetServicesResponse resp; - resp.address = connection->get_address(); - resp.services.reserve(1); // Always one service per response in this implementation - api::BluetoothGATTService service_resp; - service_resp.uuid = get_128bit_uuid_vec(service_result.uuid); - service_resp.handle = service_result.start_handle; - uint16_t char_offset = 0; - esp_gattc_char_elem_t char_result; - // Get the number of characteristics directly with one call - uint16_t total_char_count = 0; - esp_gatt_status_t char_count_status = esp_ble_gattc_get_attr_count( - connection->get_gattc_if(), connection->get_conn_id(), ESP_GATT_DB_CHARACTERISTIC, - service_result.start_handle, service_result.end_handle, 0, &total_char_count); - - if (char_count_status == ESP_GATT_OK && total_char_count > 0) { - // Only reserve if we successfully got a count - service_resp.characteristics.reserve(total_char_count); - } else if (char_count_status != ESP_GATT_OK) { - ESP_LOGW(TAG, "[%d] [%s] Error getting characteristic count, status=%d", connection->get_connection_index(), - connection->address_str().c_str(), char_count_status); - } - - // Now process characteristics - while (true) { // characteristics - uint16_t char_count = 1; - esp_gatt_status_t char_status = esp_ble_gattc_get_all_char( - connection->get_gattc_if(), connection->get_conn_id(), service_result.start_handle, - service_result.end_handle, &char_result, &char_count, char_offset); - if (char_status == ESP_GATT_INVALID_OFFSET || char_status == ESP_GATT_NOT_FOUND) { - break; - } - if (char_status != ESP_GATT_OK) { - ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", connection->get_connection_index(), - connection->address_str().c_str(), char_status); - break; - } - if (char_count == 0) { - break; - } - api::BluetoothGATTCharacteristic characteristic_resp; - characteristic_resp.uuid = get_128bit_uuid_vec(char_result.uuid); - characteristic_resp.handle = char_result.char_handle; - characteristic_resp.properties = char_result.properties; - char_offset++; - - // Get the number of descriptors directly with one call - uint16_t total_desc_count = 0; - esp_gatt_status_t desc_count_status = - esp_ble_gattc_get_attr_count(connection->get_gattc_if(), connection->get_conn_id(), ESP_GATT_DB_DESCRIPTOR, - char_result.char_handle, service_result.end_handle, 0, &total_desc_count); - - if (desc_count_status == ESP_GATT_OK && total_desc_count > 0) { - // Only reserve if we successfully got a count - characteristic_resp.descriptors.reserve(total_desc_count); - } else if (desc_count_status != ESP_GATT_OK) { - ESP_LOGW(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d", - connection->get_connection_index(), connection->address_str().c_str(), char_result.char_handle, - desc_count_status); - } - - // Now process descriptors - uint16_t desc_offset = 0; - esp_gattc_descr_elem_t desc_result; - while (true) { // descriptors - uint16_t desc_count = 1; - esp_gatt_status_t desc_status = - esp_ble_gattc_get_all_descr(connection->get_gattc_if(), connection->get_conn_id(), - char_result.char_handle, &desc_result, &desc_count, desc_offset); - if (desc_status == ESP_GATT_INVALID_OFFSET || desc_status == ESP_GATT_NOT_FOUND) { - break; - } - if (desc_status != ESP_GATT_OK) { - ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d", connection->get_connection_index(), - connection->address_str().c_str(), desc_status); - break; - } - if (desc_count == 0) { - break; - } - api::BluetoothGATTDescriptor descriptor_resp; - descriptor_resp.uuid = get_128bit_uuid_vec(desc_result.uuid); - descriptor_resp.handle = desc_result.handle; - characteristic_resp.descriptors.push_back(std::move(descriptor_resp)); - desc_offset++; - } - service_resp.characteristics.push_back(std::move(characteristic_resp)); - } - resp.services.push_back(std::move(service_resp)); - this->api_connection_->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE); - } + this->last_advertisement_flush_time_ = now; } } diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index 52f1d0f88a..9d84a9dbf2 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -22,6 +22,7 @@ namespace esphome { namespace bluetooth_proxy { static const esp_err_t ESP_GATT_NOT_CONNECTED = -1; +static const int DONE_SENDING_SERVICES = -2; using namespace esp32_ble_client; @@ -149,7 +150,10 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com std::vector advertisement_pool_; std::unique_ptr response_; - // Group 3: 1-byte types grouped together + // Group 3: 4-byte types + uint32_t last_advertisement_flush_time_{0}; + + // Group 4: 1-byte types grouped together bool active_; uint8_t advertisement_count_{0}; // 2 bytes used, 2 bytes padding From b9afa119a030ab935d2a460d6ebf5e76932c4b90 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 14:22:29 -1000 Subject: [PATCH 20/38] preen --- .../bluetooth_proxy/bluetooth_connection.cpp | 1 - .../bluetooth_proxy/bluetooth_connection.h | 12 +++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index 3bf8711714..d88eb5d632 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -13,7 +13,6 @@ namespace bluetooth_proxy { static const char *const TAG = "bluetooth_proxy.connection"; -// Helper function from bluetooth_proxy.cpp static std::vector get_128bit_uuid_vec(esp_bt_uuid_t uuid_source) { esp_bt_uuid_t uuid = espbt::ESPBTUUID::from_uuid(uuid_source).as_128bit().get_uuid(); return std::vector{((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) | diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.h b/esphome/components/bluetooth_proxy/bluetooth_connection.h index 4c4eff0919..18e32f4402 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.h +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.h @@ -29,7 +29,17 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase { friend class BluetoothProxy; void send_service_for_discovery_(); - void reset_connection_(); + void reset_connection_() { + // Important: If we were in the middle of sending services, we do NOT send + // send_gatt_services_done() here. This ensures the client knows that + // the service discovery was interrupted and can retry. The client + // (aioesphomeapi) implements a 30-second timeout (DEFAULT_BLE_TIMEOUT) + // to detect incomplete service discovery rather than relying on us to + // tell them about a partial list. + this->set_address(0); + this->send_service_ = DONE_SENDING_SERVICES; + this->proxy_->send_connections_free(); + } // Memory optimized layout for 32-bit systems // Group 1: Pointers (4 bytes each, naturally aligned) From 9aa53fd140e4b7ef7eb0b7a4a0f904a415b02520 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 14:22:43 -1000 Subject: [PATCH 21/38] preen --- .../bluetooth_proxy/bluetooth_connection.h | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.h b/esphome/components/bluetooth_proxy/bluetooth_connection.h index 18e32f4402..4c4eff0919 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.h +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.h @@ -29,17 +29,7 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase { friend class BluetoothProxy; void send_service_for_discovery_(); - void reset_connection_() { - // Important: If we were in the middle of sending services, we do NOT send - // send_gatt_services_done() here. This ensures the client knows that - // the service discovery was interrupted and can retry. The client - // (aioesphomeapi) implements a 30-second timeout (DEFAULT_BLE_TIMEOUT) - // to detect incomplete service discovery rather than relying on us to - // tell them about a partial list. - this->set_address(0); - this->send_service_ = DONE_SENDING_SERVICES; - this->proxy_->send_connections_free(); - } + void reset_connection_(); // Memory optimized layout for 32-bit systems // Group 1: Pointers (4 bytes each, naturally aligned) From 5f13aa162dfb6f87e50e96a3f258838377fb9d49 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 14:23:38 -1000 Subject: [PATCH 22/38] preen --- .../bluetooth_proxy/bluetooth_connection.cpp | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index d88eb5d632..ff10bd00bf 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -224,7 +224,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga case ESP_GATTC_READ_CHAR_EVT: { if (param->read.status != ESP_GATT_OK) { ESP_LOGW(TAG, "[%d] [%s] Error reading char/descriptor at handle 0x%2X, status=%d", this->connection_index_, - this->address_str_.c_str(), param->read.handle, param->read.status); + this->address_str().c_str(), param->read.handle, param->read.status); this->proxy_->send_gatt_error(this->address_, param->read.handle, param->read.status); break; } @@ -241,7 +241,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga case ESP_GATTC_WRITE_DESCR_EVT: { if (param->write.status != ESP_GATT_OK) { ESP_LOGW(TAG, "[%d] [%s] Error writing char/descriptor at handle 0x%2X, status=%d", this->connection_index_, - this->address_str_.c_str(), param->write.handle, param->write.status); + this->address_str().c_str(), param->write.handle, param->write.status); this->proxy_->send_gatt_error(this->address_, param->write.handle, param->write.status); break; } @@ -254,7 +254,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: { if (param->unreg_for_notify.status != ESP_GATT_OK) { ESP_LOGW(TAG, "[%d] [%s] Error unregistering notifications for handle 0x%2X, status=%d", - this->connection_index_, this->address_str_.c_str(), param->unreg_for_notify.handle, + this->connection_index_, this->address_str().c_str(), param->unreg_for_notify.handle, param->unreg_for_notify.status); this->proxy_->send_gatt_error(this->address_, param->unreg_for_notify.handle, param->unreg_for_notify.status); break; @@ -268,7 +268,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga case ESP_GATTC_REG_FOR_NOTIFY_EVT: { if (param->reg_for_notify.status != ESP_GATT_OK) { ESP_LOGW(TAG, "[%d] [%s] Error registering notifications for handle 0x%2X, status=%d", this->connection_index_, - this->address_str_.c_str(), param->reg_for_notify.handle, param->reg_for_notify.status); + this->address_str().c_str(), param->reg_for_notify.handle, param->reg_for_notify.status); this->proxy_->send_gatt_error(this->address_, param->reg_for_notify.handle, param->reg_for_notify.status); break; } @@ -279,8 +279,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga break; } case ESP_GATTC_NOTIFY_EVT: { - ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_NOTIFY_EVT: handle=0x%2X", this->connection_index_, this->address_str_.c_str(), - param->notify.handle); + ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_NOTIFY_EVT: handle=0x%2X", this->connection_index_, + this->address_str().c_str(), param->notify.handle); api::BluetoothGATTNotifyDataResponse resp; resp.address = this->address_; resp.handle = param->notify.handle; @@ -317,7 +317,7 @@ void BluetoothConnection::gap_event_handler(esp_gap_ble_cb_event_t event, esp_bl esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) { if (!this->connected()) { ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT characteristic, not connected.", this->connection_index_, - this->address_str_.c_str()); + this->address_str().c_str()); return ESP_GATT_NOT_CONNECTED; } @@ -336,7 +336,7 @@ esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) { esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std::string &data, bool response) { if (!this->connected()) { ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT characteristic, not connected.", this->connection_index_, - this->address_str_.c_str()); + this->address_str().c_str()); return ESP_GATT_NOT_CONNECTED; } ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(), @@ -356,7 +356,7 @@ esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std:: esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) { if (!this->connected()) { ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT descriptor, not connected.", this->connection_index_, - this->address_str_.c_str()); + this->address_str().c_str()); return ESP_GATT_NOT_CONNECTED; } ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(), @@ -374,7 +374,7 @@ esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) { esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::string &data, bool response) { if (!this->connected()) { ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT descriptor, not connected.", this->connection_index_, - this->address_str_.c_str()); + this->address_str().c_str()); return ESP_GATT_NOT_CONNECTED; } ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(), @@ -394,7 +394,7 @@ esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::stri esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enable) { if (!this->connected()) { ESP_LOGW(TAG, "[%d] [%s] Cannot notify GATT characteristic, not connected.", this->connection_index_, - this->address_str_.c_str()); + this->address_str().c_str()); return ESP_GATT_NOT_CONNECTED; } From 27db5352ac6546ff4e51da3a0bfebf756b9b5f1a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 14:24:05 -1000 Subject: [PATCH 23/38] preen --- .../bluetooth_proxy/bluetooth_connection.cpp | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index ff10bd00bf..6af3c71873 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -223,8 +223,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga case ESP_GATTC_READ_DESCR_EVT: case ESP_GATTC_READ_CHAR_EVT: { if (param->read.status != ESP_GATT_OK) { - ESP_LOGW(TAG, "[%d] [%s] Error reading char/descriptor at handle 0x%2X, status=%d", this->connection_index_, - this->address_str().c_str(), param->read.handle, param->read.status); + ESP_LOGW(TAG, "[%d] [%s] Error reading char/descriptor at handle 0x%2X, status=%d", + this->get_connection_index(), this->address_str().c_str(), param->read.handle, param->read.status); this->proxy_->send_gatt_error(this->address_, param->read.handle, param->read.status); break; } @@ -240,8 +240,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga case ESP_GATTC_WRITE_CHAR_EVT: case ESP_GATTC_WRITE_DESCR_EVT: { if (param->write.status != ESP_GATT_OK) { - ESP_LOGW(TAG, "[%d] [%s] Error writing char/descriptor at handle 0x%2X, status=%d", this->connection_index_, - this->address_str().c_str(), param->write.handle, param->write.status); + ESP_LOGW(TAG, "[%d] [%s] Error writing char/descriptor at handle 0x%2X, status=%d", + this->get_connection_index(), this->address_str().c_str(), param->write.handle, param->write.status); this->proxy_->send_gatt_error(this->address_, param->write.handle, param->write.status); break; } @@ -254,7 +254,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: { if (param->unreg_for_notify.status != ESP_GATT_OK) { ESP_LOGW(TAG, "[%d] [%s] Error unregistering notifications for handle 0x%2X, status=%d", - this->connection_index_, this->address_str().c_str(), param->unreg_for_notify.handle, + this->get_connection_index(), this->address_str().c_str(), param->unreg_for_notify.handle, param->unreg_for_notify.status); this->proxy_->send_gatt_error(this->address_, param->unreg_for_notify.handle, param->unreg_for_notify.status); break; @@ -267,8 +267,9 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga } case ESP_GATTC_REG_FOR_NOTIFY_EVT: { if (param->reg_for_notify.status != ESP_GATT_OK) { - ESP_LOGW(TAG, "[%d] [%s] Error registering notifications for handle 0x%2X, status=%d", this->connection_index_, - this->address_str().c_str(), param->reg_for_notify.handle, param->reg_for_notify.status); + ESP_LOGW(TAG, "[%d] [%s] Error registering notifications for handle 0x%2X, status=%d", + this->get_connection_index(), this->address_str().c_str(), param->reg_for_notify.handle, + param->reg_for_notify.status); this->proxy_->send_gatt_error(this->address_, param->reg_for_notify.handle, param->reg_for_notify.status); break; } @@ -279,7 +280,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga break; } case ESP_GATTC_NOTIFY_EVT: { - ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_NOTIFY_EVT: handle=0x%2X", this->connection_index_, + ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_NOTIFY_EVT: handle=0x%2X", this->get_connection_index(), this->address_str().c_str(), param->notify.handle); api::BluetoothGATTNotifyDataResponse resp; resp.address = this->address_; @@ -316,17 +317,17 @@ void BluetoothConnection::gap_event_handler(esp_gap_ble_cb_event_t event, esp_bl esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) { if (!this->connected()) { - ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT characteristic, not connected.", this->connection_index_, + ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT characteristic, not connected.", this->get_connection_index(), this->address_str().c_str()); return ESP_GATT_NOT_CONNECTED; } - ESP_LOGV(TAG, "[%d] [%s] Reading GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(), - handle); + ESP_LOGV(TAG, "[%d] [%s] Reading GATT characteristic handle %d", this->get_connection_index(), + this->address_str_.c_str(), handle); esp_err_t err = esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE); if (err != ERR_OK) { - ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char error, err=%d", this->connection_index_, + ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char error, err=%d", this->get_connection_index(), this->address_str_.c_str(), err); return err; } @@ -335,18 +336,18 @@ esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) { esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std::string &data, bool response) { if (!this->connected()) { - ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT characteristic, not connected.", this->connection_index_, + ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT characteristic, not connected.", this->get_connection_index(), this->address_str().c_str()); return ESP_GATT_NOT_CONNECTED; } - ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(), - handle); + ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->get_connection_index(), + this->address_str_.c_str(), handle); esp_err_t err = esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(), response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (err != ERR_OK) { - ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char error, err=%d", this->connection_index_, + ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char error, err=%d", this->get_connection_index(), this->address_str_.c_str(), err); return err; } @@ -355,16 +356,16 @@ esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std:: esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) { if (!this->connected()) { - ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT descriptor, not connected.", this->connection_index_, + ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT descriptor, not connected.", this->get_connection_index(), this->address_str().c_str()); return ESP_GATT_NOT_CONNECTED; } - ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(), + ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor handle %d", this->get_connection_index(), this->address_str_.c_str(), handle); esp_err_t err = esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE); if (err != ERR_OK) { - ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char_descr error, err=%d", this->connection_index_, + ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char_descr error, err=%d", this->get_connection_index(), this->address_str_.c_str(), err); return err; } @@ -373,18 +374,18 @@ esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) { esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::string &data, bool response) { if (!this->connected()) { - ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT descriptor, not connected.", this->connection_index_, + ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT descriptor, not connected.", this->get_connection_index(), this->address_str().c_str()); return ESP_GATT_NOT_CONNECTED; } - ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(), + ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->get_connection_index(), this->address_str_.c_str(), handle); esp_err_t err = esp_ble_gattc_write_char_descr( this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(), response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (err != ERR_OK) { - ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, err=%d", this->connection_index_, + ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, err=%d", this->get_connection_index(), this->address_str_.c_str(), err); return err; } @@ -393,26 +394,26 @@ esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::stri esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enable) { if (!this->connected()) { - ESP_LOGW(TAG, "[%d] [%s] Cannot notify GATT characteristic, not connected.", this->connection_index_, + ESP_LOGW(TAG, "[%d] [%s] Cannot notify GATT characteristic, not connected.", this->get_connection_index(), this->address_str().c_str()); return ESP_GATT_NOT_CONNECTED; } if (enable) { - ESP_LOGV(TAG, "[%d] [%s] Registering for GATT characteristic notifications handle %d", this->connection_index_, + ESP_LOGV(TAG, "[%d] [%s] Registering for GATT characteristic notifications handle %d", this->get_connection_index(), this->address_str_.c_str(), handle); esp_err_t err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, handle); if (err != ESP_OK) { - ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_register_for_notify failed, err=%d", this->connection_index_, + ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_register_for_notify failed, err=%d", this->get_connection_index(), this->address_str_.c_str(), err); return err; } } else { - ESP_LOGV(TAG, "[%d] [%s] Unregistering for GATT characteristic notifications handle %d", this->connection_index_, - this->address_str_.c_str(), handle); + ESP_LOGV(TAG, "[%d] [%s] Unregistering for GATT characteristic notifications handle %d", + this->get_connection_index(), this->address_str_.c_str(), handle); esp_err_t err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, handle); if (err != ESP_OK) { - ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_unregister_for_notify failed, err=%d", this->connection_index_, + ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_unregister_for_notify failed, err=%d", this->get_connection_index(), this->address_str_.c_str(), err); return err; } From 9f5584ac6216f14f65a84e7b13a9f6453e543fae Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 14:24:20 -1000 Subject: [PATCH 24/38] preen --- esphome/components/bluetooth_proxy/bluetooth_proxy.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index f4b63f3a5d..89a857f94e 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -426,7 +426,8 @@ void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetSer return; } if (!connection->service_count_) { - ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->connection_index_, connection->address_str().c_str()); + ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->get_connection_index(), + connection->address_str().c_str()); this->send_gatt_services_done(msg.address); return; } From 1c4a50ad3afb0e42360c8754b65b33851c84c94f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 14:24:32 -1000 Subject: [PATCH 25/38] preen --- esphome/components/bluetooth_proxy/bluetooth_connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index 6af3c71873..4f974d9f30 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -323,12 +323,12 @@ esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) { } ESP_LOGV(TAG, "[%d] [%s] Reading GATT characteristic handle %d", this->get_connection_index(), - this->address_str_.c_str(), handle); + this->address_str().c_str(), handle); esp_err_t err = esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE); if (err != ERR_OK) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char error, err=%d", this->get_connection_index(), - this->address_str_.c_str(), err); + this->address_str().c_str(), err); return err; } return ESP_OK; From 4c9fa2f753561a82480a19f23e9bf357c96b5a25 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 14:24:49 -1000 Subject: [PATCH 26/38] preen --- esphome/components/bluetooth_proxy/bluetooth_connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index 4f974d9f30..bf39790712 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -341,14 +341,14 @@ esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std:: return ESP_GATT_NOT_CONNECTED; } ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->get_connection_index(), - this->address_str_.c_str(), handle); + this->address_str().c_str(), handle); esp_err_t err = esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(), response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (err != ERR_OK) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char error, err=%d", this->get_connection_index(), - this->address_str_.c_str(), err); + this->address_str().c_str(), err); return err; } return ESP_OK; From a8dd0b474a2e50932abad4ced93691f954000565 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 14:25:06 -1000 Subject: [PATCH 27/38] preen --- .../bluetooth_proxy/bluetooth_connection.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index bf39790712..cb07c41c22 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -360,13 +360,13 @@ esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) { this->address_str().c_str()); return ESP_GATT_NOT_CONNECTED; } - ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor handle %d", this->get_connection_index(), this->address_str_.c_str(), - handle); + ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor handle %d", this->get_connection_index(), + this->address_str().c_str(), handle); esp_err_t err = esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE); if (err != ERR_OK) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char_descr error, err=%d", this->get_connection_index(), - this->address_str_.c_str(), err); + this->address_str().c_str(), err); return err; } return ESP_OK; @@ -378,15 +378,15 @@ esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::stri this->address_str().c_str()); return ESP_GATT_NOT_CONNECTED; } - ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->get_connection_index(), this->address_str_.c_str(), - handle); + ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->get_connection_index(), + this->address_str().c_str(), handle); esp_err_t err = esp_ble_gattc_write_char_descr( this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(), response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (err != ERR_OK) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, err=%d", this->get_connection_index(), - this->address_str_.c_str(), err); + this->address_str().c_str(), err); return err; } return ESP_OK; @@ -405,7 +405,7 @@ esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enabl esp_err_t err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, handle); if (err != ESP_OK) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_register_for_notify failed, err=%d", this->get_connection_index(), - this->address_str_.c_str(), err); + this->address_str().c_str(), err); return err; } } else { @@ -414,7 +414,7 @@ esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enabl esp_err_t err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, handle); if (err != ESP_OK) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_unregister_for_notify failed, err=%d", this->get_connection_index(), - this->address_str_.c_str(), err); + this->address_str().c_str(), err); return err; } } From 56fdc1d1150a5909f90ea25071e4668f5ffd7f8e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 14:25:22 -1000 Subject: [PATCH 28/38] preen --- esphome/components/bluetooth_proxy/bluetooth_connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index cb07c41c22..fa17e5df76 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -401,7 +401,7 @@ esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enabl if (enable) { ESP_LOGV(TAG, "[%d] [%s] Registering for GATT characteristic notifications handle %d", this->get_connection_index(), - this->address_str_.c_str(), handle); + this->address_str().c_str(), handle); esp_err_t err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, handle); if (err != ESP_OK) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_register_for_notify failed, err=%d", this->get_connection_index(), From d9fe52a5fb5b4c49f634c0b70acd1f43ad7f750f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 14:28:40 -1000 Subject: [PATCH 29/38] Revert "preen" This reverts commit 56fdc1d1150a5909f90ea25071e4668f5ffd7f8e. --- esphome/components/bluetooth_proxy/bluetooth_connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index fa17e5df76..cb07c41c22 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -401,7 +401,7 @@ esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enabl if (enable) { ESP_LOGV(TAG, "[%d] [%s] Registering for GATT characteristic notifications handle %d", this->get_connection_index(), - this->address_str().c_str(), handle); + this->address_str_.c_str(), handle); esp_err_t err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, handle); if (err != ESP_OK) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_register_for_notify failed, err=%d", this->get_connection_index(), From 3087ccface9b7e54106c6da9ca5c264b2ba842b3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 14:28:41 -1000 Subject: [PATCH 30/38] Revert "preen" This reverts commit a8dd0b474a2e50932abad4ced93691f954000565. --- .../bluetooth_proxy/bluetooth_connection.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index cb07c41c22..bf39790712 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -360,13 +360,13 @@ esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) { this->address_str().c_str()); return ESP_GATT_NOT_CONNECTED; } - ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor handle %d", this->get_connection_index(), - this->address_str().c_str(), handle); + ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor handle %d", this->get_connection_index(), this->address_str_.c_str(), + handle); esp_err_t err = esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE); if (err != ERR_OK) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char_descr error, err=%d", this->get_connection_index(), - this->address_str().c_str(), err); + this->address_str_.c_str(), err); return err; } return ESP_OK; @@ -378,15 +378,15 @@ esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::stri this->address_str().c_str()); return ESP_GATT_NOT_CONNECTED; } - ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->get_connection_index(), - this->address_str().c_str(), handle); + ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->get_connection_index(), this->address_str_.c_str(), + handle); esp_err_t err = esp_ble_gattc_write_char_descr( this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(), response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (err != ERR_OK) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, err=%d", this->get_connection_index(), - this->address_str().c_str(), err); + this->address_str_.c_str(), err); return err; } return ESP_OK; @@ -405,7 +405,7 @@ esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enabl esp_err_t err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, handle); if (err != ESP_OK) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_register_for_notify failed, err=%d", this->get_connection_index(), - this->address_str().c_str(), err); + this->address_str_.c_str(), err); return err; } } else { @@ -414,7 +414,7 @@ esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enabl esp_err_t err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, handle); if (err != ESP_OK) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_unregister_for_notify failed, err=%d", this->get_connection_index(), - this->address_str().c_str(), err); + this->address_str_.c_str(), err); return err; } } From 1ca1ceb08d9d8632a92fa22cce6ad0ace7762a5b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 14:28:42 -1000 Subject: [PATCH 31/38] Revert "preen" This reverts commit 4c9fa2f753561a82480a19f23e9bf357c96b5a25. --- esphome/components/bluetooth_proxy/bluetooth_connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index bf39790712..4f974d9f30 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -341,14 +341,14 @@ esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std:: return ESP_GATT_NOT_CONNECTED; } ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->get_connection_index(), - this->address_str().c_str(), handle); + this->address_str_.c_str(), handle); esp_err_t err = esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(), response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (err != ERR_OK) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char error, err=%d", this->get_connection_index(), - this->address_str().c_str(), err); + this->address_str_.c_str(), err); return err; } return ESP_OK; From 8acd7548c6e8c0200cfa7a0309e00f51a8d14617 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 14:28:43 -1000 Subject: [PATCH 32/38] Revert "preen" This reverts commit 1c4a50ad3afb0e42360c8754b65b33851c84c94f. --- esphome/components/bluetooth_proxy/bluetooth_connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index 4f974d9f30..6af3c71873 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -323,12 +323,12 @@ esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) { } ESP_LOGV(TAG, "[%d] [%s] Reading GATT characteristic handle %d", this->get_connection_index(), - this->address_str().c_str(), handle); + this->address_str_.c_str(), handle); esp_err_t err = esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE); if (err != ERR_OK) { ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char error, err=%d", this->get_connection_index(), - this->address_str().c_str(), err); + this->address_str_.c_str(), err); return err; } return ESP_OK; From e4736e9aa7bbc622d689617b065985d9f0bf7ecd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 14:28:44 -1000 Subject: [PATCH 33/38] Revert "preen" This reverts commit 9f5584ac6216f14f65a84e7b13a9f6453e543fae. --- esphome/components/bluetooth_proxy/bluetooth_proxy.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index 89a857f94e..f4b63f3a5d 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -426,8 +426,7 @@ void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetSer return; } if (!connection->service_count_) { - ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->get_connection_index(), - connection->address_str().c_str()); + ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->connection_index_, connection->address_str().c_str()); this->send_gatt_services_done(msg.address); return; } From 2ce0753ec6364a3edeba8cfa4846d963f0c54cad Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 14:28:45 -1000 Subject: [PATCH 34/38] Revert "preen" This reverts commit 27db5352ac6546ff4e51da3a0bfebf756b9b5f1a. --- .../bluetooth_proxy/bluetooth_connection.cpp | 57 +++++++++---------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index 6af3c71873..ff10bd00bf 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -223,8 +223,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga case ESP_GATTC_READ_DESCR_EVT: case ESP_GATTC_READ_CHAR_EVT: { if (param->read.status != ESP_GATT_OK) { - ESP_LOGW(TAG, "[%d] [%s] Error reading char/descriptor at handle 0x%2X, status=%d", - this->get_connection_index(), this->address_str().c_str(), param->read.handle, param->read.status); + ESP_LOGW(TAG, "[%d] [%s] Error reading char/descriptor at handle 0x%2X, status=%d", this->connection_index_, + this->address_str().c_str(), param->read.handle, param->read.status); this->proxy_->send_gatt_error(this->address_, param->read.handle, param->read.status); break; } @@ -240,8 +240,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga case ESP_GATTC_WRITE_CHAR_EVT: case ESP_GATTC_WRITE_DESCR_EVT: { if (param->write.status != ESP_GATT_OK) { - ESP_LOGW(TAG, "[%d] [%s] Error writing char/descriptor at handle 0x%2X, status=%d", - this->get_connection_index(), this->address_str().c_str(), param->write.handle, param->write.status); + ESP_LOGW(TAG, "[%d] [%s] Error writing char/descriptor at handle 0x%2X, status=%d", this->connection_index_, + this->address_str().c_str(), param->write.handle, param->write.status); this->proxy_->send_gatt_error(this->address_, param->write.handle, param->write.status); break; } @@ -254,7 +254,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: { if (param->unreg_for_notify.status != ESP_GATT_OK) { ESP_LOGW(TAG, "[%d] [%s] Error unregistering notifications for handle 0x%2X, status=%d", - this->get_connection_index(), this->address_str().c_str(), param->unreg_for_notify.handle, + this->connection_index_, this->address_str().c_str(), param->unreg_for_notify.handle, param->unreg_for_notify.status); this->proxy_->send_gatt_error(this->address_, param->unreg_for_notify.handle, param->unreg_for_notify.status); break; @@ -267,9 +267,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga } case ESP_GATTC_REG_FOR_NOTIFY_EVT: { if (param->reg_for_notify.status != ESP_GATT_OK) { - ESP_LOGW(TAG, "[%d] [%s] Error registering notifications for handle 0x%2X, status=%d", - this->get_connection_index(), this->address_str().c_str(), param->reg_for_notify.handle, - param->reg_for_notify.status); + ESP_LOGW(TAG, "[%d] [%s] Error registering notifications for handle 0x%2X, status=%d", this->connection_index_, + this->address_str().c_str(), param->reg_for_notify.handle, param->reg_for_notify.status); this->proxy_->send_gatt_error(this->address_, param->reg_for_notify.handle, param->reg_for_notify.status); break; } @@ -280,7 +279,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga break; } case ESP_GATTC_NOTIFY_EVT: { - ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_NOTIFY_EVT: handle=0x%2X", this->get_connection_index(), + ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_NOTIFY_EVT: handle=0x%2X", this->connection_index_, this->address_str().c_str(), param->notify.handle); api::BluetoothGATTNotifyDataResponse resp; resp.address = this->address_; @@ -317,17 +316,17 @@ void BluetoothConnection::gap_event_handler(esp_gap_ble_cb_event_t event, esp_bl esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) { if (!this->connected()) { - ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT characteristic, not connected.", this->get_connection_index(), + ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT characteristic, not connected.", this->connection_index_, this->address_str().c_str()); return ESP_GATT_NOT_CONNECTED; } - ESP_LOGV(TAG, "[%d] [%s] Reading GATT characteristic handle %d", this->get_connection_index(), - this->address_str_.c_str(), handle); + ESP_LOGV(TAG, "[%d] [%s] Reading GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(), + handle); esp_err_t err = esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE); if (err != ERR_OK) { - ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char error, err=%d", this->get_connection_index(), + ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char error, err=%d", this->connection_index_, this->address_str_.c_str(), err); return err; } @@ -336,18 +335,18 @@ esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) { esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std::string &data, bool response) { if (!this->connected()) { - ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT characteristic, not connected.", this->get_connection_index(), + ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT characteristic, not connected.", this->connection_index_, this->address_str().c_str()); return ESP_GATT_NOT_CONNECTED; } - ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->get_connection_index(), - this->address_str_.c_str(), handle); + ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(), + handle); esp_err_t err = esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(), response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (err != ERR_OK) { - ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char error, err=%d", this->get_connection_index(), + ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char error, err=%d", this->connection_index_, this->address_str_.c_str(), err); return err; } @@ -356,16 +355,16 @@ esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std:: esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) { if (!this->connected()) { - ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT descriptor, not connected.", this->get_connection_index(), + ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT descriptor, not connected.", this->connection_index_, this->address_str().c_str()); return ESP_GATT_NOT_CONNECTED; } - ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor handle %d", this->get_connection_index(), this->address_str_.c_str(), + ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(), handle); esp_err_t err = esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE); if (err != ERR_OK) { - ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char_descr error, err=%d", this->get_connection_index(), + ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char_descr error, err=%d", this->connection_index_, this->address_str_.c_str(), err); return err; } @@ -374,18 +373,18 @@ esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) { esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::string &data, bool response) { if (!this->connected()) { - ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT descriptor, not connected.", this->get_connection_index(), + ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT descriptor, not connected.", this->connection_index_, this->address_str().c_str()); return ESP_GATT_NOT_CONNECTED; } - ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->get_connection_index(), this->address_str_.c_str(), + ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(), handle); esp_err_t err = esp_ble_gattc_write_char_descr( this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(), response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (err != ERR_OK) { - ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, err=%d", this->get_connection_index(), + ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, err=%d", this->connection_index_, this->address_str_.c_str(), err); return err; } @@ -394,26 +393,26 @@ esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::stri esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enable) { if (!this->connected()) { - ESP_LOGW(TAG, "[%d] [%s] Cannot notify GATT characteristic, not connected.", this->get_connection_index(), + ESP_LOGW(TAG, "[%d] [%s] Cannot notify GATT characteristic, not connected.", this->connection_index_, this->address_str().c_str()); return ESP_GATT_NOT_CONNECTED; } if (enable) { - ESP_LOGV(TAG, "[%d] [%s] Registering for GATT characteristic notifications handle %d", this->get_connection_index(), + ESP_LOGV(TAG, "[%d] [%s] Registering for GATT characteristic notifications handle %d", this->connection_index_, this->address_str_.c_str(), handle); esp_err_t err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, handle); if (err != ESP_OK) { - ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_register_for_notify failed, err=%d", this->get_connection_index(), + ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_register_for_notify failed, err=%d", this->connection_index_, this->address_str_.c_str(), err); return err; } } else { - ESP_LOGV(TAG, "[%d] [%s] Unregistering for GATT characteristic notifications handle %d", - this->get_connection_index(), this->address_str_.c_str(), handle); + ESP_LOGV(TAG, "[%d] [%s] Unregistering for GATT characteristic notifications handle %d", this->connection_index_, + this->address_str_.c_str(), handle); esp_err_t err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, handle); if (err != ESP_OK) { - ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_unregister_for_notify failed, err=%d", this->get_connection_index(), + ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_unregister_for_notify failed, err=%d", this->connection_index_, this->address_str_.c_str(), err); return err; } From 9902a4ee9ca750466c38d0e4016f8be775fae6c6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 14:28:45 -1000 Subject: [PATCH 35/38] Revert "preen" This reverts commit 5f13aa162dfb6f87e50e96a3f258838377fb9d49. --- .../bluetooth_proxy/bluetooth_connection.cpp | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index ff10bd00bf..d88eb5d632 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -224,7 +224,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga case ESP_GATTC_READ_CHAR_EVT: { if (param->read.status != ESP_GATT_OK) { ESP_LOGW(TAG, "[%d] [%s] Error reading char/descriptor at handle 0x%2X, status=%d", this->connection_index_, - this->address_str().c_str(), param->read.handle, param->read.status); + this->address_str_.c_str(), param->read.handle, param->read.status); this->proxy_->send_gatt_error(this->address_, param->read.handle, param->read.status); break; } @@ -241,7 +241,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga case ESP_GATTC_WRITE_DESCR_EVT: { if (param->write.status != ESP_GATT_OK) { ESP_LOGW(TAG, "[%d] [%s] Error writing char/descriptor at handle 0x%2X, status=%d", this->connection_index_, - this->address_str().c_str(), param->write.handle, param->write.status); + this->address_str_.c_str(), param->write.handle, param->write.status); this->proxy_->send_gatt_error(this->address_, param->write.handle, param->write.status); break; } @@ -254,7 +254,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: { if (param->unreg_for_notify.status != ESP_GATT_OK) { ESP_LOGW(TAG, "[%d] [%s] Error unregistering notifications for handle 0x%2X, status=%d", - this->connection_index_, this->address_str().c_str(), param->unreg_for_notify.handle, + this->connection_index_, this->address_str_.c_str(), param->unreg_for_notify.handle, param->unreg_for_notify.status); this->proxy_->send_gatt_error(this->address_, param->unreg_for_notify.handle, param->unreg_for_notify.status); break; @@ -268,7 +268,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga case ESP_GATTC_REG_FOR_NOTIFY_EVT: { if (param->reg_for_notify.status != ESP_GATT_OK) { ESP_LOGW(TAG, "[%d] [%s] Error registering notifications for handle 0x%2X, status=%d", this->connection_index_, - this->address_str().c_str(), param->reg_for_notify.handle, param->reg_for_notify.status); + this->address_str_.c_str(), param->reg_for_notify.handle, param->reg_for_notify.status); this->proxy_->send_gatt_error(this->address_, param->reg_for_notify.handle, param->reg_for_notify.status); break; } @@ -279,8 +279,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga break; } case ESP_GATTC_NOTIFY_EVT: { - ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_NOTIFY_EVT: handle=0x%2X", this->connection_index_, - this->address_str().c_str(), param->notify.handle); + ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_NOTIFY_EVT: handle=0x%2X", this->connection_index_, this->address_str_.c_str(), + param->notify.handle); api::BluetoothGATTNotifyDataResponse resp; resp.address = this->address_; resp.handle = param->notify.handle; @@ -317,7 +317,7 @@ void BluetoothConnection::gap_event_handler(esp_gap_ble_cb_event_t event, esp_bl esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) { if (!this->connected()) { ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT characteristic, not connected.", this->connection_index_, - this->address_str().c_str()); + this->address_str_.c_str()); return ESP_GATT_NOT_CONNECTED; } @@ -336,7 +336,7 @@ esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) { esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std::string &data, bool response) { if (!this->connected()) { ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT characteristic, not connected.", this->connection_index_, - this->address_str().c_str()); + this->address_str_.c_str()); return ESP_GATT_NOT_CONNECTED; } ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(), @@ -356,7 +356,7 @@ esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std:: esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) { if (!this->connected()) { ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT descriptor, not connected.", this->connection_index_, - this->address_str().c_str()); + this->address_str_.c_str()); return ESP_GATT_NOT_CONNECTED; } ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(), @@ -374,7 +374,7 @@ esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) { esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::string &data, bool response) { if (!this->connected()) { ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT descriptor, not connected.", this->connection_index_, - this->address_str().c_str()); + this->address_str_.c_str()); return ESP_GATT_NOT_CONNECTED; } ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(), @@ -394,7 +394,7 @@ esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::stri esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enable) { if (!this->connected()) { ESP_LOGW(TAG, "[%d] [%s] Cannot notify GATT characteristic, not connected.", this->connection_index_, - this->address_str().c_str()); + this->address_str_.c_str()); return ESP_GATT_NOT_CONNECTED; } From da1e1ce9ce6368a3b65db898a016582cac8dd78a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 14:30:46 -1000 Subject: [PATCH 36/38] other way --- .../bluetooth_proxy/bluetooth_connection.cpp | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index d88eb5d632..e5991c0100 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -68,18 +68,18 @@ void BluetoothConnection::send_service_for_discovery_() { // Send next service esp_gattc_service_elem_t service_result; uint16_t service_count = 1; - esp_gatt_status_t service_status = esp_ble_gattc_get_service(this->get_gattc_if(), this->get_conn_id(), nullptr, + esp_gatt_status_t service_status = esp_ble_gattc_get_service(this->get_gattc_if(), this->conn_id_, nullptr, &service_result, &service_count, this->send_service_); this->send_service_++; if (service_status != ESP_GATT_OK) { - ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service error at offset=%d, status=%d", this->get_connection_index(), + ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service error at offset=%d, status=%d", this->connection_index_, this->address_str().c_str(), this->send_service_ - 1, service_status); return; } if (service_count == 0) { - ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service missing, service_count=%d", this->get_connection_index(), + ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service missing, service_count=%d", this->connection_index_, this->address_str().c_str(), service_count); return; } @@ -94,14 +94,14 @@ void BluetoothConnection::send_service_for_discovery_() { // Get the number of characteristics directly with one call uint16_t total_char_count = 0; esp_gatt_status_t char_count_status = - esp_ble_gattc_get_attr_count(this->get_gattc_if(), this->get_conn_id(), ESP_GATT_DB_CHARACTERISTIC, + esp_ble_gattc_get_attr_count(this->get_gattc_if(), this->conn_id_, ESP_GATT_DB_CHARACTERISTIC, service_result.start_handle, service_result.end_handle, 0, &total_char_count); if (char_count_status == ESP_GATT_OK && total_char_count > 0) { // Only reserve if we successfully got a count service_resp.characteristics.reserve(total_char_count); } else if (char_count_status != ESP_GATT_OK) { - ESP_LOGW(TAG, "[%d] [%s] Error getting characteristic count, status=%d", this->get_connection_index(), + ESP_LOGW(TAG, "[%d] [%s] Error getting characteristic count, status=%d", this->connection_index_, this->address_str().c_str(), char_count_status); } @@ -111,13 +111,13 @@ void BluetoothConnection::send_service_for_discovery_() { while (true) { // characteristics uint16_t char_count = 1; esp_gatt_status_t char_status = - esp_ble_gattc_get_all_char(this->get_gattc_if(), this->get_conn_id(), service_result.start_handle, + esp_ble_gattc_get_all_char(this->get_gattc_if(), this->conn_id_, service_result.start_handle, service_result.end_handle, &char_result, &char_count, char_offset); if (char_status == ESP_GATT_INVALID_OFFSET || char_status == ESP_GATT_NOT_FOUND) { break; } if (char_status != ESP_GATT_OK) { - ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->get_connection_index(), + ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->connection_index_, this->address_str().c_str(), char_status); break; } @@ -134,15 +134,15 @@ void BluetoothConnection::send_service_for_discovery_() { // Get the number of descriptors directly with one call uint16_t total_desc_count = 0; esp_gatt_status_t desc_count_status = - esp_ble_gattc_get_attr_count(this->get_gattc_if(), this->get_conn_id(), ESP_GATT_DB_DESCRIPTOR, + esp_ble_gattc_get_attr_count(this->get_gattc_if(), this->conn_id_, ESP_GATT_DB_DESCRIPTOR, char_result.char_handle, service_result.end_handle, 0, &total_desc_count); if (desc_count_status == ESP_GATT_OK && total_desc_count > 0) { // Only reserve if we successfully got a count characteristic_resp.descriptors.reserve(total_desc_count); } else if (desc_count_status != ESP_GATT_OK) { - ESP_LOGW(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d", - this->get_connection_index(), this->address_str().c_str(), char_result.char_handle, desc_count_status); + ESP_LOGW(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d", this->connection_index_, + this->address_str().c_str(), char_result.char_handle, desc_count_status); } // Now process descriptors @@ -151,12 +151,12 @@ void BluetoothConnection::send_service_for_discovery_() { while (true) { // descriptors uint16_t desc_count = 1; esp_gatt_status_t desc_status = esp_ble_gattc_get_all_descr( - this->get_gattc_if(), this->get_conn_id(), char_result.char_handle, &desc_result, &desc_count, desc_offset); + this->get_gattc_if(), this->conn_id_, char_result.char_handle, &desc_result, &desc_count, desc_offset); if (desc_status == ESP_GATT_INVALID_OFFSET || desc_status == ESP_GATT_NOT_FOUND) { break; } if (desc_status != ESP_GATT_OK) { - ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d", this->get_connection_index(), + ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d", this->connection_index_, this->address_str().c_str(), desc_status); break; } From 6a566c6305d93c5e4398917a99eee93f292e8135 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 14:31:27 -1000 Subject: [PATCH 37/38] other way --- .../bluetooth_proxy/bluetooth_connection.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index e5991c0100..6495cb4922 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -68,7 +68,7 @@ void BluetoothConnection::send_service_for_discovery_() { // Send next service esp_gattc_service_elem_t service_result; uint16_t service_count = 1; - esp_gatt_status_t service_status = esp_ble_gattc_get_service(this->get_gattc_if(), this->conn_id_, nullptr, + esp_gatt_status_t service_status = esp_ble_gattc_get_service(this->gattc_if_, this->conn_id_, nullptr, &service_result, &service_count, this->send_service_); this->send_service_++; @@ -94,7 +94,7 @@ void BluetoothConnection::send_service_for_discovery_() { // Get the number of characteristics directly with one call uint16_t total_char_count = 0; esp_gatt_status_t char_count_status = - esp_ble_gattc_get_attr_count(this->get_gattc_if(), this->conn_id_, ESP_GATT_DB_CHARACTERISTIC, + esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_CHARACTERISTIC, service_result.start_handle, service_result.end_handle, 0, &total_char_count); if (char_count_status == ESP_GATT_OK && total_char_count > 0) { @@ -111,7 +111,7 @@ void BluetoothConnection::send_service_for_discovery_() { while (true) { // characteristics uint16_t char_count = 1; esp_gatt_status_t char_status = - esp_ble_gattc_get_all_char(this->get_gattc_if(), this->conn_id_, service_result.start_handle, + esp_ble_gattc_get_all_char(this->gattc_if_, this->conn_id_, service_result.start_handle, service_result.end_handle, &char_result, &char_count, char_offset); if (char_status == ESP_GATT_INVALID_OFFSET || char_status == ESP_GATT_NOT_FOUND) { break; @@ -134,8 +134,8 @@ void BluetoothConnection::send_service_for_discovery_() { // Get the number of descriptors directly with one call uint16_t total_desc_count = 0; esp_gatt_status_t desc_count_status = - esp_ble_gattc_get_attr_count(this->get_gattc_if(), this->conn_id_, ESP_GATT_DB_DESCRIPTOR, - char_result.char_handle, service_result.end_handle, 0, &total_desc_count); + esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_DESCRIPTOR, char_result.char_handle, + service_result.end_handle, 0, &total_desc_count); if (desc_count_status == ESP_GATT_OK && total_desc_count > 0) { // Only reserve if we successfully got a count @@ -151,7 +151,7 @@ void BluetoothConnection::send_service_for_discovery_() { while (true) { // descriptors uint16_t desc_count = 1; esp_gatt_status_t desc_status = esp_ble_gattc_get_all_descr( - this->get_gattc_if(), this->conn_id_, char_result.char_handle, &desc_result, &desc_count, desc_offset); + this->gattc_if_, this->conn_id_, char_result.char_handle, &desc_result, &desc_count, desc_offset); if (desc_status == ESP_GATT_INVALID_OFFSET || desc_status == ESP_GATT_NOT_FOUND) { break; } From b2ec2615bbc3eabbb67577701a845b729bf42505 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Jul 2025 14:33:10 -1000 Subject: [PATCH 38/38] other way --- .../bluetooth_proxy/bluetooth_connection.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index 6495cb4922..ea41396c5c 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -65,6 +65,12 @@ void BluetoothConnection::send_service_for_discovery_() { return; } + // Early return if no API connection + auto *api_conn = this->proxy_->get_api_connection(); + if (api_conn == nullptr) { + return; + } + // Send next service esp_gattc_service_elem_t service_result; uint16_t service_count = 1; @@ -174,10 +180,8 @@ void BluetoothConnection::send_service_for_discovery_() { } resp.services.push_back(std::move(service_resp)); - auto *api_conn = this->proxy_->get_api_connection(); - if (api_conn != nullptr) { - api_conn->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE); - } + // Send the message (we already checked api_conn is not null at the beginning) + api_conn->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE); } bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,