From dac738a916689808aea642c9662b8fcda707d771 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 11 Jun 2025 22:27:10 -0500 Subject: [PATCH 01/51] Always perform select() when loop duration exceeds interval (#9058) --- esphome/core/application.cpp | 18 ++++++++++++------ esphome/core/application.h | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 75a7052c63..87e6f33e04 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -117,7 +117,9 @@ void Application::loop() { // Use the last component's end time instead of calling millis() again auto elapsed = last_op_end_time - this->last_loop_; if (elapsed >= this->loop_interval_ || HighFrequencyLoopRequester::is_high_frequency()) { - yield(); + // Even if we overran the loop interval, we still need to select() + // to know if any sockets have data ready + this->yield_with_select_(0); } else { uint32_t delay_time = this->loop_interval_ - elapsed; uint32_t next_schedule = this->scheduler.next_schedule_in().value_or(delay_time); @@ -126,7 +128,7 @@ void Application::loop() { next_schedule = std::max(next_schedule, delay_time / 2); delay_time = std::min(next_schedule, delay_time); - this->delay_with_select_(delay_time); + this->yield_with_select_(delay_time); } this->last_loop_ = last_op_end_time; @@ -215,7 +217,7 @@ void Application::teardown_components(uint32_t timeout_ms) { // Give some time for I/O operations if components are still pending if (!pending_components.empty()) { - this->delay_with_select_(1); + this->yield_with_select_(1); } // Update time for next iteration @@ -293,8 +295,6 @@ bool Application::is_socket_ready(int fd) const { // This function is thread-safe for reading the result of select() // However, it should only be called after select() has been executed in the main loop // The read_fds_ is only modified by select() in the main loop - if (HighFrequencyLoopRequester::is_high_frequency()) - return true; // fd sets via select are not updated in high frequency looping - so force true fallback behavior if (fd < 0 || fd >= FD_SETSIZE) return false; @@ -302,7 +302,9 @@ bool Application::is_socket_ready(int fd) const { } #endif -void Application::delay_with_select_(uint32_t delay_ms) { +void Application::yield_with_select_(uint32_t delay_ms) { + // Delay while monitoring sockets. When delay_ms is 0, always yield() to ensure other tasks run + // since select() with 0 timeout only polls without yielding. #ifdef USE_SOCKET_SELECT_SUPPORT if (!this->socket_fds_.empty()) { // Update fd_set if socket list has changed @@ -340,6 +342,10 @@ void Application::delay_with_select_(uint32_t delay_ms) { ESP_LOGW(TAG, "select() failed with errno %d", errno); delay(delay_ms); } + // When delay_ms is 0, we need to yield since select(0) doesn't yield + if (delay_ms == 0) { + yield(); + } } else { // No sockets registered, use regular delay delay(delay_ms); diff --git a/esphome/core/application.h b/esphome/core/application.h index d95f45e757..6c09b25590 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -575,7 +575,7 @@ class Application { void feed_wdt_arch_(); /// Perform a delay while also monitoring socket file descriptors for readiness - void delay_with_select_(uint32_t delay_ms); + void yield_with_select_(uint32_t delay_ms); std::vector components_{}; std::vector looping_components_{}; From 8b6aa319bfa43ab3aa7f2f4d3d5e6c73a54b1ceb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 10:57:46 -0500 Subject: [PATCH 02/51] s3 fixes --- .../components/esp32_touch/esp32_touch.cpp | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 76532704ad..7746721c59 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -79,7 +79,11 @@ void ESP32TouchComponent::setup() { } #endif -#if ESP_IDF_VERSION_MAJOR >= 5 && defined(USE_ESP32_VARIANT_ESP32) +#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + // For ESP32-S2/S3, use the new API + touch_pad_set_charge_discharge_times(this->meas_cycle_); + touch_pad_set_measurement_interval(this->sleep_cycle_); +#elif ESP_IDF_VERSION_MAJOR >= 5 && defined(USE_ESP32_VARIANT_ESP32) touch_pad_set_measurement_clock_cycles(this->meas_cycle_); touch_pad_set_measurement_interval(this->sleep_cycle_); #else @@ -88,12 +92,31 @@ void ESP32TouchComponent::setup() { touch_pad_set_voltage(this->high_voltage_reference_, this->low_voltage_reference_, this->voltage_attenuation_); for (auto *child : this->children_) { - // Set interrupt threshold + // Configure touch pad +#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + // For ESP32-S2/S3, config and threshold are separate + touch_pad_config(child->get_touch_pad()); + if (child->get_threshold() != 0) { + // Only set threshold if it's non-zero + touch_pad_set_thresh(child->get_touch_pad(), child->get_threshold()); + } +#else + // For original ESP32, config includes threshold touch_pad_config(child->get_touch_pad(), child->get_threshold()); +#endif } // Register ISR handler +#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + // For ESP32-S2/S3, we need to specify which interrupts to enable + // We want active/inactive interrupts to detect touch state changes + esp_err_t err = touch_pad_isr_register( + touch_isr_handler, this, + static_cast(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE)); +#else + // For original ESP32 esp_err_t err = touch_pad_isr_register(touch_isr_handler, this); +#endif if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to register touch ISR: %s", esp_err_to_name(err)); vQueueDelete(this->touch_queue_); @@ -117,7 +140,13 @@ void ESP32TouchComponent::setup() { this->release_check_interval_ms_ = std::min(this->release_timeout_ms_ / 4, (uint32_t) 50); // Enable touch pad interrupt +#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + // For ESP32-S2/S3, enable the interrupts we registered for + touch_pad_intr_enable(static_cast(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE)); +#else + // For original ESP32 touch_pad_intr_enable(); +#endif } void ESP32TouchComponent::dump_config() { @@ -354,10 +383,15 @@ void ESP32TouchComponent::loop() { if (new_state != child->last_state_) { child->last_state_ = new_state; child->publish_state(new_state); - // Note: In practice, this will always show ON because the ISR only fires when a pad is touched - // OFF events are detected by the timeout logic, not the ISR +#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + // ESP32-S2/S3: ISR fires for both touch (ACTIVE) and release (INACTIVE) events + ESP_LOGV(TAG, "Touch Pad '%s' state: %s (value: %" PRIu32 ", threshold: %" PRIu32 ")", + child->get_name().c_str(), new_state ? "ON" : "OFF", event.value, child->get_threshold()); +#else + // Original ESP32: ISR only fires when touched, release is detected by timeout ESP_LOGV(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 ", threshold: %" PRIu32 ")", child->get_name().c_str(), event.value, child->get_threshold()); +#endif } break; } @@ -397,7 +431,13 @@ void ESP32TouchComponent::loop() { } void ESP32TouchComponent::on_shutdown() { +#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + // For ESP32-S2/S3, disable the interrupts we enabled + touch_pad_intr_disable(static_cast(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE)); +#else + // For original ESP32 touch_pad_intr_disable(); +#endif touch_pad_isr_deregister(touch_isr_handler, this); if (this->touch_queue_) { vQueueDelete(this->touch_queue_); From a36af1bfac6a5d9e4a460dbf44fe99db4ffc19b1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 10:59:40 -0500 Subject: [PATCH 03/51] s3 fixes --- esphome/components/esp32_touch/esp32_touch.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 7746721c59..17e5ed3641 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -91,6 +91,15 @@ void ESP32TouchComponent::setup() { #endif touch_pad_set_voltage(this->high_voltage_reference_, this->low_voltage_reference_, this->voltage_attenuation_); +#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + // For ESP32-S2/S3, we need to set up the channel mask + uint16_t channel_mask = 0; + for (auto *child : this->children_) { + channel_mask |= BIT(child->get_touch_pad()); + } + touch_pad_set_channel_mask(channel_mask); +#endif + for (auto *child : this->children_) { // Configure touch pad #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) @@ -475,8 +484,15 @@ void ESP32TouchComponent::on_shutdown() { void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { ESP32TouchComponent *component = static_cast(arg); +#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + // For S2/S3, read the interrupt status mask to see what type of interrupt occurred + uint32_t intr_mask = touch_pad_read_intr_status_mask(); + touch_pad_intr_clear(static_cast(intr_mask)); +#else + // For original ESP32 uint32_t pad_status = touch_pad_get_status(); touch_pad_clear_status(); +#endif // Process all configured pads to check their current state // Send events for ALL pads with valid readings so we catch both touches and releases From 99cbe53a8e5e2f8561f795e15c0acd98c0bcf2f2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 11:43:47 -0500 Subject: [PATCH 04/51] split it --- .../components/esp32_touch/esp32_touch.cpp | 559 +----------------- esphome/components/esp32_touch/esp32_touch.h | 53 +- .../components/esp32_touch/esp32_touch_v1.cpp | 270 +++++++++ .../components/esp32_touch/esp32_touch_v2.cpp | 378 ++++++++++++ 4 files changed, 704 insertions(+), 556 deletions(-) create mode 100644 esphome/components/esp32_touch/esp32_touch_v1.cpp create mode 100644 esphome/components/esp32_touch/esp32_touch_v2.cpp diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 17e5ed3641..4b2635e685 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -1,555 +1,4 @@ -#ifdef USE_ESP32 - -#include "esp32_touch.h" -#include "esphome/core/application.h" -#include "esphome/core/log.h" -#include "esphome/core/hal.h" - -#include -#include - -// Include HAL for ISR-safe touch reading on all variants -#include "hal/touch_sensor_ll.h" -// Include for RTC clock frequency -#include "soc/rtc.h" - -namespace esphome { -namespace esp32_touch { - -static const char *const TAG = "esp32_touch"; - -void ESP32TouchComponent::setup() { - ESP_LOGCONFIG(TAG, "Running setup"); - - touch_pad_init(); - - touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); - -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - touch_pad_fsm_start(); -#endif - - // Create queue for touch events - size based on number of touch pads - // Each pad can have at most a few press events queued - // Use 4x the number of pads to handle burst events - size_t queue_size = this->children_.size() * 4; - if (queue_size < 8) - queue_size = 8; // Minimum queue size - - this->touch_queue_ = xQueueCreate(queue_size, sizeof(TouchPadEvent)); - if (this->touch_queue_ == nullptr) { - ESP_LOGE(TAG, "Failed to create touch event queue of size %d", queue_size); - this->mark_failed(); - return; - } -// set up and enable/start filtering based on ESP32 variant -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - if (this->filter_configured_()) { - touch_filter_config_t filter_info = { - .mode = this->filter_mode_, - .debounce_cnt = this->debounce_count_, - .noise_thr = this->noise_threshold_, - .jitter_step = this->jitter_step_, - .smh_lvl = this->smooth_level_, - }; - touch_pad_filter_set_config(&filter_info); - touch_pad_filter_enable(); - } - - if (this->denoise_configured_()) { - touch_pad_denoise_t denoise = { - .grade = this->grade_, - .cap_level = this->cap_level_, - }; - touch_pad_denoise_set_config(&denoise); - touch_pad_denoise_enable(); - } - - if (this->waterproof_configured_()) { - touch_pad_waterproof_t waterproof = { - .guard_ring_pad = this->waterproof_guard_ring_pad_, - .shield_driver = this->waterproof_shield_driver_, - }; - touch_pad_waterproof_set_config(&waterproof); - touch_pad_waterproof_enable(); - } -#else - if (this->iir_filter_enabled_()) { - touch_pad_filter_start(this->iir_filter_); - } -#endif - -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - // For ESP32-S2/S3, use the new API - touch_pad_set_charge_discharge_times(this->meas_cycle_); - touch_pad_set_measurement_interval(this->sleep_cycle_); -#elif ESP_IDF_VERSION_MAJOR >= 5 && defined(USE_ESP32_VARIANT_ESP32) - touch_pad_set_measurement_clock_cycles(this->meas_cycle_); - touch_pad_set_measurement_interval(this->sleep_cycle_); -#else - touch_pad_set_meas_time(this->sleep_cycle_, this->meas_cycle_); -#endif - touch_pad_set_voltage(this->high_voltage_reference_, this->low_voltage_reference_, this->voltage_attenuation_); - -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - // For ESP32-S2/S3, we need to set up the channel mask - uint16_t channel_mask = 0; - for (auto *child : this->children_) { - channel_mask |= BIT(child->get_touch_pad()); - } - touch_pad_set_channel_mask(channel_mask); -#endif - - for (auto *child : this->children_) { - // Configure touch pad -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - // For ESP32-S2/S3, config and threshold are separate - touch_pad_config(child->get_touch_pad()); - if (child->get_threshold() != 0) { - // Only set threshold if it's non-zero - touch_pad_set_thresh(child->get_touch_pad(), child->get_threshold()); - } -#else - // For original ESP32, config includes threshold - touch_pad_config(child->get_touch_pad(), child->get_threshold()); -#endif - } - - // Register ISR handler -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - // For ESP32-S2/S3, we need to specify which interrupts to enable - // We want active/inactive interrupts to detect touch state changes - esp_err_t err = touch_pad_isr_register( - touch_isr_handler, this, - static_cast(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE)); -#else - // For original ESP32 - esp_err_t err = touch_pad_isr_register(touch_isr_handler, this); -#endif - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to register touch ISR: %s", esp_err_to_name(err)); - vQueueDelete(this->touch_queue_); - this->touch_queue_ = nullptr; - this->mark_failed(); - return; - } - - // Calculate release timeout based on sleep cycle - // Sleep cycle is in RTC_SLOW_CLK cycles (typically 150kHz, but can be 32kHz) - // Get actual RTC clock frequency - uint32_t rtc_freq = rtc_clk_slow_freq_get_hz(); - - // Calculate based on actual sleep cycle since they use timer mode - this->release_timeout_ms_ = (this->sleep_cycle_ * 1000 * 3) / (rtc_freq * 2); - if (this->release_timeout_ms_ < 100) { - this->release_timeout_ms_ = 100; // Minimum 100ms - } - - // Calculate check interval - this->release_check_interval_ms_ = std::min(this->release_timeout_ms_ / 4, (uint32_t) 50); - - // Enable touch pad interrupt -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - // For ESP32-S2/S3, enable the interrupts we registered for - touch_pad_intr_enable(static_cast(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE)); -#else - // For original ESP32 - touch_pad_intr_enable(); -#endif -} - -void ESP32TouchComponent::dump_config() { - const char *lv_s; - switch (this->low_voltage_reference_) { - case TOUCH_LVOLT_0V5: - lv_s = "0.5V"; - break; - case TOUCH_LVOLT_0V6: - lv_s = "0.6V"; - break; - case TOUCH_LVOLT_0V7: - lv_s = "0.7V"; - break; - case TOUCH_LVOLT_0V8: - lv_s = "0.8V"; - break; - default: - lv_s = "UNKNOWN"; - break; - } - - const char *hv_s; - switch (this->high_voltage_reference_) { - case TOUCH_HVOLT_2V4: - hv_s = "2.4V"; - break; - case TOUCH_HVOLT_2V5: - hv_s = "2.5V"; - break; - case TOUCH_HVOLT_2V6: - hv_s = "2.6V"; - break; - case TOUCH_HVOLT_2V7: - hv_s = "2.7V"; - break; - default: - hv_s = "UNKNOWN"; - break; - } - - const char *atten_s; - switch (this->voltage_attenuation_) { - case TOUCH_HVOLT_ATTEN_1V5: - atten_s = "1.5V"; - break; - case TOUCH_HVOLT_ATTEN_1V: - atten_s = "1V"; - break; - case TOUCH_HVOLT_ATTEN_0V5: - atten_s = "0.5V"; - break; - case TOUCH_HVOLT_ATTEN_0V: - atten_s = "0V"; - break; - default: - atten_s = "UNKNOWN"; - break; - } - ESP_LOGCONFIG(TAG, - "Config for ESP32 Touch Hub:\n" - " Meas cycle: %.2fms\n" - " Sleep cycle: %.2fms\n" - " Low Voltage Reference: %s\n" - " High Voltage Reference: %s\n" - " Voltage Attenuation: %s\n" - " ISR Configuration:\n" - " Release timeout: %" PRIu32 "ms\n" - " Release check interval: %" PRIu32 "ms", - this->meas_cycle_ / (8000000.0f / 1000.0f), this->sleep_cycle_ / (150000.0f / 1000.0f), lv_s, hv_s, - atten_s, this->release_timeout_ms_, this->release_check_interval_ms_); - -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - if (this->filter_configured_()) { - const char *filter_mode_s; - switch (this->filter_mode_) { - case TOUCH_PAD_FILTER_IIR_4: - filter_mode_s = "IIR_4"; - break; - case TOUCH_PAD_FILTER_IIR_8: - filter_mode_s = "IIR_8"; - break; - case TOUCH_PAD_FILTER_IIR_16: - filter_mode_s = "IIR_16"; - break; - case TOUCH_PAD_FILTER_IIR_32: - filter_mode_s = "IIR_32"; - break; - case TOUCH_PAD_FILTER_IIR_64: - filter_mode_s = "IIR_64"; - break; - case TOUCH_PAD_FILTER_IIR_128: - filter_mode_s = "IIR_128"; - break; - case TOUCH_PAD_FILTER_IIR_256: - filter_mode_s = "IIR_256"; - break; - case TOUCH_PAD_FILTER_JITTER: - filter_mode_s = "JITTER"; - break; - default: - filter_mode_s = "UNKNOWN"; - break; - } - ESP_LOGCONFIG(TAG, - " Filter mode: %s\n" - " Debounce count: %" PRIu32 "\n" - " Noise threshold coefficient: %" PRIu32 "\n" - " Jitter filter step size: %" PRIu32, - filter_mode_s, this->debounce_count_, this->noise_threshold_, this->jitter_step_); - const char *smooth_level_s; - switch (this->smooth_level_) { - case TOUCH_PAD_SMOOTH_OFF: - smooth_level_s = "OFF"; - break; - case TOUCH_PAD_SMOOTH_IIR_2: - smooth_level_s = "IIR_2"; - break; - case TOUCH_PAD_SMOOTH_IIR_4: - smooth_level_s = "IIR_4"; - break; - case TOUCH_PAD_SMOOTH_IIR_8: - smooth_level_s = "IIR_8"; - break; - default: - smooth_level_s = "UNKNOWN"; - break; - } - ESP_LOGCONFIG(TAG, " Smooth level: %s", smooth_level_s); - } - - if (this->denoise_configured_()) { - const char *grade_s; - switch (this->grade_) { - case TOUCH_PAD_DENOISE_BIT12: - grade_s = "BIT12"; - break; - case TOUCH_PAD_DENOISE_BIT10: - grade_s = "BIT10"; - break; - case TOUCH_PAD_DENOISE_BIT8: - grade_s = "BIT8"; - break; - case TOUCH_PAD_DENOISE_BIT4: - grade_s = "BIT4"; - break; - default: - grade_s = "UNKNOWN"; - break; - } - ESP_LOGCONFIG(TAG, " Denoise grade: %s", grade_s); - - const char *cap_level_s; - switch (this->cap_level_) { - case TOUCH_PAD_DENOISE_CAP_L0: - cap_level_s = "L0"; - break; - case TOUCH_PAD_DENOISE_CAP_L1: - cap_level_s = "L1"; - break; - case TOUCH_PAD_DENOISE_CAP_L2: - cap_level_s = "L2"; - break; - case TOUCH_PAD_DENOISE_CAP_L3: - cap_level_s = "L3"; - break; - case TOUCH_PAD_DENOISE_CAP_L4: - cap_level_s = "L4"; - break; - case TOUCH_PAD_DENOISE_CAP_L5: - cap_level_s = "L5"; - break; - case TOUCH_PAD_DENOISE_CAP_L6: - cap_level_s = "L6"; - break; - case TOUCH_PAD_DENOISE_CAP_L7: - cap_level_s = "L7"; - break; - default: - cap_level_s = "UNKNOWN"; - break; - } - ESP_LOGCONFIG(TAG, " Denoise capacitance level: %s", cap_level_s); - } -#else - if (this->iir_filter_enabled_()) { - ESP_LOGCONFIG(TAG, " IIR Filter: %" PRIu32 "ms", this->iir_filter_); - } else { - ESP_LOGCONFIG(TAG, " IIR Filter DISABLED"); - } -#endif - - if (this->setup_mode_) { - ESP_LOGCONFIG(TAG, " Setup Mode ENABLED"); - } - - for (auto *child : this->children_) { - LOG_BINARY_SENSOR(" ", "Touch Pad", child); - ESP_LOGCONFIG(TAG, " Pad: T%" PRIu32, (uint32_t) child->get_touch_pad()); - ESP_LOGCONFIG(TAG, " Threshold: %" PRIu32, child->get_threshold()); - } -} - -void ESP32TouchComponent::loop() { - const uint32_t now = App.get_loop_component_start_time(); - bool should_print = this->setup_mode_ && now - this->setup_mode_last_log_print_ > 250; - - // Print debug info for all pads in setup mode - if (should_print) { - for (auto *child : this->children_) { - ESP_LOGD(TAG, "Touch Pad '%s' (T%" PRIu32 "): %" PRIu32, child->get_name().c_str(), - (uint32_t) child->get_touch_pad(), child->value_); - } - this->setup_mode_last_log_print_ = now; - } - - // Process any queued touch events from interrupts - TouchPadEvent event; - while (xQueueReceive(this->touch_queue_, &event, 0) == pdTRUE) { - // Find the corresponding sensor - for (auto *child : this->children_) { - if (child->get_touch_pad() == event.pad) { - child->value_ = event.value; - - // The interrupt gives us the touch state directly - bool new_state = event.is_touched; - - // Track when we last saw this pad as touched - if (new_state) { - this->last_touch_time_[event.pad] = now; - } - - // Only publish if state changed - if (new_state != child->last_state_) { - child->last_state_ = new_state; - child->publish_state(new_state); -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - // ESP32-S2/S3: ISR fires for both touch (ACTIVE) and release (INACTIVE) events - ESP_LOGV(TAG, "Touch Pad '%s' state: %s (value: %" PRIu32 ", threshold: %" PRIu32 ")", - child->get_name().c_str(), new_state ? "ON" : "OFF", event.value, child->get_threshold()); -#else - // Original ESP32: ISR only fires when touched, release is detected by timeout - ESP_LOGV(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 ", threshold: %" PRIu32 ")", - child->get_name().c_str(), event.value, child->get_threshold()); -#endif - } - break; - } - } - } - - // Check for released pads periodically - static uint32_t last_release_check = 0; - if (now - last_release_check < this->release_check_interval_ms_) { - return; - } - last_release_check = now; - - for (auto *child : this->children_) { - touch_pad_t pad = child->get_touch_pad(); - uint32_t last_time = this->last_touch_time_[pad]; - - // If we've never seen this pad touched (last_time == 0) and enough time has passed - // since startup, publish OFF state and mark as published with value 1 - if (last_time == 0 && now > this->release_timeout_ms_) { - child->publish_state(false); - this->last_touch_time_[pad] = 1; // Mark as "initial state published" - ESP_LOGV(TAG, "Touch Pad '%s' state: OFF (initial)", child->get_name().c_str()); - } else if (child->last_state_ && last_time > 1) { // last_time > 1 means it's a real timestamp - uint32_t time_diff = now - last_time; - - // Check if we haven't seen this pad recently - if (time_diff > this->release_timeout_ms_) { - // Haven't seen this pad recently, assume it's released - child->last_state_ = false; - child->publish_state(false); - this->last_touch_time_[pad] = 1; // Reset to "initial published" state - ESP_LOGV(TAG, "Touch Pad '%s' state: OFF (timeout)", child->get_name().c_str()); - } - } - } -} - -void ESP32TouchComponent::on_shutdown() { -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - // For ESP32-S2/S3, disable the interrupts we enabled - touch_pad_intr_disable(static_cast(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE)); -#else - // For original ESP32 - touch_pad_intr_disable(); -#endif - touch_pad_isr_deregister(touch_isr_handler, this); - if (this->touch_queue_) { - vQueueDelete(this->touch_queue_); - } - - bool is_wakeup_source = false; - -#if !(defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)) - if (this->iir_filter_enabled_()) { - touch_pad_filter_stop(); - touch_pad_filter_delete(); - } -#endif - - for (auto *child : this->children_) { - if (child->get_wakeup_threshold() != 0) { - if (!is_wakeup_source) { - is_wakeup_source = true; - // Touch sensor FSM mode must be 'TOUCH_FSM_MODE_TIMER' to use it to wake-up. - touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); - } - -#if !(defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)) - // No filter available when using as wake-up source. - touch_pad_config(child->get_touch_pad(), child->get_wakeup_threshold()); -#endif - } - } - - if (!is_wakeup_source) { - touch_pad_deinit(); - } -} - -void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { - ESP32TouchComponent *component = static_cast(arg); - -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - // For S2/S3, read the interrupt status mask to see what type of interrupt occurred - uint32_t intr_mask = touch_pad_read_intr_status_mask(); - touch_pad_intr_clear(static_cast(intr_mask)); -#else - // For original ESP32 - uint32_t pad_status = touch_pad_get_status(); - touch_pad_clear_status(); -#endif - - // Process all configured pads to check their current state - // Send events for ALL pads with valid readings so we catch both touches and releases - for (auto *child : component->children_) { - touch_pad_t pad = child->get_touch_pad(); - - // Read current value using ISR-safe API - uint32_t value; -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - if (component->filter_configured_()) { - touch_pad_filter_read_smooth(pad, &value); - } else { - // Use low-level HAL function when filter is not configured - value = touch_ll_read_raw_data(pad); - } -#else - if (component->iir_filter_enabled_()) { - uint16_t temp_value = 0; - touch_pad_read_filtered(pad, &temp_value); - value = temp_value; - } else { - // Use low-level HAL function when filter is not enabled - value = touch_ll_read_raw_data(pad); - } -#endif - - // Skip pads with 0 value - they haven't been measured in this cycle - if (value == 0) { - continue; - } - - // Determine current touch state based on value vs threshold -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - bool is_touched = value > child->get_threshold(); -#else - bool is_touched = value < child->get_threshold(); -#endif - - // Always send the current state - the main loop will filter for changes - TouchPadEvent event; - event.pad = pad; - event.value = value; - event.is_touched = is_touched; - - // Send to queue from ISR - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - xQueueSendFromISR(component->touch_queue_, &event, &xHigherPriorityTaskWoken); - if (xHigherPriorityTaskWoken) { - portYIELD_FROM_ISR(); - } - } -} - -ESP32TouchBinarySensor::ESP32TouchBinarySensor(touch_pad_t touch_pad, uint32_t threshold, uint32_t wakeup_threshold) - : touch_pad_(touch_pad), threshold_(threshold), wakeup_threshold_(wakeup_threshold) {} - -} // namespace esp32_touch -} // namespace esphome - -#endif +// ESP32 touch sensor implementation +// Platform-specific implementations are in: +// - esp32_touch_esp32.cpp for original ESP32 +// - esp32_touch_esp32s2s3.cpp for ESP32-S2/S3 \ No newline at end of file diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 22a7db45ca..3d776f2d6e 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -21,6 +21,10 @@ struct TouchPadEvent { touch_pad_t pad; uint32_t value; bool is_touched; // Whether this pad is currently touched +#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + uint32_t intr_mask; // Interrupt mask for S2/S3 + uint32_t pad_status; // Pad status bitmap for S2/S3 +#endif }; class ESP32TouchComponent : public Component { @@ -84,6 +88,52 @@ class ESP32TouchComponent : public Component { bool iir_filter_enabled_() const { return this->iir_filter_ > 0; } #endif + // Helper functions for dump_config - common to both implementations + static const char *get_low_voltage_reference_str(touch_low_volt_t ref) { + switch (ref) { + case TOUCH_LVOLT_0V5: + return "0.5V"; + case TOUCH_LVOLT_0V6: + return "0.6V"; + case TOUCH_LVOLT_0V7: + return "0.7V"; + case TOUCH_LVOLT_0V8: + return "0.8V"; + default: + return "UNKNOWN"; + } + } + + static const char *get_high_voltage_reference_str(touch_high_volt_t ref) { + switch (ref) { + case TOUCH_HVOLT_2V4: + return "2.4V"; + case TOUCH_HVOLT_2V5: + return "2.5V"; + case TOUCH_HVOLT_2V6: + return "2.6V"; + case TOUCH_HVOLT_2V7: + return "2.7V"; + default: + return "UNKNOWN"; + } + } + + static const char *get_voltage_attenuation_str(touch_volt_atten_t atten) { + switch (atten) { + case TOUCH_HVOLT_ATTEN_1V5: + return "1.5V"; + case TOUCH_HVOLT_ATTEN_1V: + return "1V"; + case TOUCH_HVOLT_ATTEN_0V5: + return "0.5V"; + case TOUCH_HVOLT_ATTEN_0V: + return "0V"; + default: + return "UNKNOWN"; + } + } + std::vector children_; bool setup_mode_{false}; uint32_t setup_mode_last_log_print_{0}; @@ -111,7 +161,8 @@ class ESP32TouchComponent : public Component { /// Simple helper class to expose a touch pad value as a binary sensor. class ESP32TouchBinarySensor : public binary_sensor::BinarySensor { public: - ESP32TouchBinarySensor(touch_pad_t touch_pad, uint32_t threshold, uint32_t wakeup_threshold); + ESP32TouchBinarySensor(touch_pad_t touch_pad, uint32_t threshold, uint32_t wakeup_threshold) + : touch_pad_(touch_pad), threshold_(threshold), wakeup_threshold_(wakeup_threshold) {} touch_pad_t get_touch_pad() const { return this->touch_pad_; } uint32_t get_threshold() const { return this->threshold_; } diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp new file mode 100644 index 0000000000..515c384279 --- /dev/null +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -0,0 +1,270 @@ +#ifdef USE_ESP32_VARIANT_ESP32 + +#include "esp32_touch.h" +#include "esphome/core/application.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +#include +#include + +// Include HAL for ISR-safe touch reading +#include "hal/touch_sensor_ll.h" +// Include for RTC clock frequency +#include "soc/rtc.h" + +namespace esphome { +namespace esp32_touch { + +static const char *const TAG = "esp32_touch"; + +void ESP32TouchComponent::setup() { + ESP_LOGCONFIG(TAG, "Running setup for ESP32"); + + touch_pad_init(); + touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); + + // Create queue for touch events + size_t queue_size = this->children_.size() * 4; + if (queue_size < 8) + queue_size = 8; + + this->touch_queue_ = xQueueCreate(queue_size, sizeof(TouchPadEvent)); + if (this->touch_queue_ == nullptr) { + ESP_LOGE(TAG, "Failed to create touch event queue of size %d", queue_size); + this->mark_failed(); + return; + } + + // Set up IIR filter if enabled + if (this->iir_filter_enabled_()) { + touch_pad_filter_start(this->iir_filter_); + } + + // Configure measurement parameters +#if ESP_IDF_VERSION_MAJOR >= 5 + touch_pad_set_measurement_clock_cycles(this->meas_cycle_); + touch_pad_set_measurement_interval(this->sleep_cycle_); +#else + touch_pad_set_meas_time(this->sleep_cycle_, this->meas_cycle_); +#endif + touch_pad_set_voltage(this->high_voltage_reference_, this->low_voltage_reference_, this->voltage_attenuation_); + + // Configure each touch pad + for (auto *child : this->children_) { + touch_pad_config(child->get_touch_pad(), child->get_threshold()); + } + + // Register ISR handler + esp_err_t err = touch_pad_isr_register(touch_isr_handler, this); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to register touch ISR: %s", esp_err_to_name(err)); + vQueueDelete(this->touch_queue_); + this->touch_queue_ = nullptr; + this->mark_failed(); + return; + } + + // Calculate release timeout based on sleep cycle + uint32_t rtc_freq = rtc_clk_slow_freq_get_hz(); + this->release_timeout_ms_ = (this->sleep_cycle_ * 1000 * 3) / (rtc_freq * 2); + if (this->release_timeout_ms_ < 100) { + this->release_timeout_ms_ = 100; + } + this->release_check_interval_ms_ = std::min(this->release_timeout_ms_ / 4, (uint32_t) 50); + + // Enable touch pad interrupt + touch_pad_intr_enable(); +} + +void ESP32TouchComponent::dump_config() { + const char *lv_s = get_low_voltage_reference_str(this->low_voltage_reference_); + const char *hv_s = get_high_voltage_reference_str(this->high_voltage_reference_); + const char *atten_s = get_voltage_attenuation_str(this->voltage_attenuation_); + + ESP_LOGCONFIG(TAG, + "Config for ESP32 Touch Hub:\n" + " Meas cycle: %.2fms\n" + " Sleep cycle: %.2fms\n" + " Low Voltage Reference: %s\n" + " High Voltage Reference: %s\n" + " Voltage Attenuation: %s\n" + " ISR Configuration:\n" + " Release timeout: %" PRIu32 "ms\n" + " Release check interval: %" PRIu32 "ms", + this->meas_cycle_ / (8000000.0f / 1000.0f), this->sleep_cycle_ / (150000.0f / 1000.0f), lv_s, hv_s, + atten_s, this->release_timeout_ms_, this->release_check_interval_ms_); + + if (this->iir_filter_enabled_()) { + ESP_LOGCONFIG(TAG, " IIR Filter: %" PRIu32 "ms", this->iir_filter_); + } else { + ESP_LOGCONFIG(TAG, " IIR Filter DISABLED"); + } + + if (this->setup_mode_) { + ESP_LOGCONFIG(TAG, " Setup Mode ENABLED"); + } + + for (auto *child : this->children_) { + LOG_BINARY_SENSOR(" ", "Touch Pad", child); + ESP_LOGCONFIG(TAG, " Pad: T%" PRIu32, (uint32_t) child->get_touch_pad()); + ESP_LOGCONFIG(TAG, " Threshold: %" PRIu32, child->get_threshold()); + } +} + +void ESP32TouchComponent::loop() { + const uint32_t now = App.get_loop_component_start_time(); + bool should_print = this->setup_mode_ && now - this->setup_mode_last_log_print_ > 250; + + // Print debug info for all pads in setup mode + if (should_print) { + for (auto *child : this->children_) { + ESP_LOGD(TAG, "Touch Pad '%s' (T%" PRIu32 "): %" PRIu32, child->get_name().c_str(), + (uint32_t) child->get_touch_pad(), child->value_); + } + this->setup_mode_last_log_print_ = now; + } + + // Process any queued touch events from interrupts + TouchPadEvent event; + while (xQueueReceive(this->touch_queue_, &event, 0) == pdTRUE) { + // Find the corresponding sensor + for (auto *child : this->children_) { + if (child->get_touch_pad() == event.pad) { + child->value_ = event.value; + + // The interrupt gives us the touch state directly + bool new_state = event.is_touched; + + // Track when we last saw this pad as touched + if (new_state) { + this->last_touch_time_[event.pad] = now; + } + + // Only publish if state changed + if (new_state != child->last_state_) { + child->last_state_ = new_state; + child->publish_state(new_state); + // Original ESP32: ISR only fires when touched, release is detected by timeout + ESP_LOGV(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 ", threshold: %" PRIu32 ")", + child->get_name().c_str(), event.value, child->get_threshold()); + } + break; + } + } + } + + // Check for released pads periodically + static uint32_t last_release_check = 0; + if (now - last_release_check < this->release_check_interval_ms_) { + return; + } + last_release_check = now; + + for (auto *child : this->children_) { + touch_pad_t pad = child->get_touch_pad(); + uint32_t last_time = this->last_touch_time_[pad]; + + // If we've never seen this pad touched (last_time == 0) and enough time has passed + // since startup, publish OFF state and mark as published with value 1 + if (last_time == 0 && now > this->release_timeout_ms_) { + child->publish_state(false); + this->last_touch_time_[pad] = 1; // Mark as "initial state published" + ESP_LOGV(TAG, "Touch Pad '%s' state: OFF (initial)", child->get_name().c_str()); + } else if (child->last_state_ && last_time > 1) { // last_time > 1 means it's a real timestamp + uint32_t time_diff = now - last_time; + + // Check if we haven't seen this pad recently + if (time_diff > this->release_timeout_ms_) { + // Haven't seen this pad recently, assume it's released + child->last_state_ = false; + child->publish_state(false); + this->last_touch_time_[pad] = 1; // Reset to "initial published" state + ESP_LOGV(TAG, "Touch Pad '%s' state: OFF (timeout)", child->get_name().c_str()); + } + } + } +} + +void ESP32TouchComponent::on_shutdown() { + touch_pad_intr_disable(); + touch_pad_isr_deregister(touch_isr_handler, this); + if (this->touch_queue_) { + vQueueDelete(this->touch_queue_); + } + + bool is_wakeup_source = false; + + if (this->iir_filter_enabled_()) { + touch_pad_filter_stop(); + touch_pad_filter_delete(); + } + + for (auto *child : this->children_) { + if (child->get_wakeup_threshold() != 0) { + if (!is_wakeup_source) { + is_wakeup_source = true; + // Touch sensor FSM mode must be 'TOUCH_FSM_MODE_TIMER' to use it to wake-up. + touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); + } + + // No filter available when using as wake-up source. + touch_pad_config(child->get_touch_pad(), child->get_wakeup_threshold()); + } + } + + if (!is_wakeup_source) { + touch_pad_deinit(); + } +} + +void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { + ESP32TouchComponent *component = static_cast(arg); + + uint32_t pad_status = touch_pad_get_status(); + touch_pad_clear_status(); + + // Process all configured pads to check their current state + for (auto *child : component->children_) { + touch_pad_t pad = child->get_touch_pad(); + + // Read current value using ISR-safe API + uint32_t value; + if (component->iir_filter_enabled_()) { + uint16_t temp_value = 0; + touch_pad_read_filtered(pad, &temp_value); + value = temp_value; + } else { + // Use low-level HAL function when filter is not enabled + value = touch_ll_read_raw_data(pad); + } + + // Skip pads with 0 value - they haven't been measured in this cycle + if (value == 0) { + continue; + } + + // For original ESP32, lower value means touched + bool is_touched = value < child->get_threshold(); + + // Always send the current state - the main loop will filter for changes + TouchPadEvent event; + event.pad = pad; + event.value = value; + event.is_touched = is_touched; + + // Send to queue from ISR + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xQueueSendFromISR(component->touch_queue_, &event, &xHigherPriorityTaskWoken); + if (xHigherPriorityTaskWoken) { + portYIELD_FROM_ISR(); + } + } +} + +bool ESP32TouchComponent::iir_filter_enabled_() const { return this->iir_filter_ > 0; } + +} // namespace esp32_touch +} // namespace esphome + +#endif // USE_ESP32_VARIANT_ESP32 \ No newline at end of file diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp new file mode 100644 index 0000000000..6ce3594dac --- /dev/null +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -0,0 +1,378 @@ +#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + +#include "esp32_touch.h" +#include "esphome/core/application.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +#include +#include + +// Include HAL for ISR-safe touch reading +#include "hal/touch_sensor_ll.h" +// Include for RTC clock frequency +#include "soc/rtc.h" +// Include for ISR-safe printing +#include "rom/ets_sys.h" + +namespace esphome { +namespace esp32_touch { + +static const char *const TAG = "esp32_touch"; + +void ESP32TouchComponent::setup() { + ESP_LOGCONFIG(TAG, "Running setup for ESP32-S2/S3"); + + touch_pad_init(); + touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); + + // Create queue for touch events + size_t queue_size = this->children_.size() * 4; + if (queue_size < 8) + queue_size = 8; + + this->touch_queue_ = xQueueCreate(queue_size, sizeof(TouchPadEvent)); + if (this->touch_queue_ == nullptr) { + ESP_LOGE(TAG, "Failed to create touch event queue of size %d", queue_size); + this->mark_failed(); + return; + } + + // Set up filtering if configured + if (this->filter_configured_()) { + touch_filter_config_t filter_info = { + .mode = this->filter_mode_, + .debounce_cnt = this->debounce_count_, + .noise_thr = this->noise_threshold_, + .jitter_step = this->jitter_step_, + .smh_lvl = this->smooth_level_, + }; + touch_pad_filter_set_config(&filter_info); + touch_pad_filter_enable(); + } + + if (this->denoise_configured_()) { + touch_pad_denoise_t denoise = { + .grade = this->grade_, + .cap_level = this->cap_level_, + }; + touch_pad_denoise_set_config(&denoise); + touch_pad_denoise_enable(); + } + + if (this->waterproof_configured_()) { + touch_pad_waterproof_t waterproof = { + .guard_ring_pad = this->waterproof_guard_ring_pad_, + .shield_driver = this->waterproof_shield_driver_, + }; + touch_pad_waterproof_set_config(&waterproof); + touch_pad_waterproof_enable(); + } + + // Configure measurement parameters + touch_pad_set_charge_discharge_times(this->meas_cycle_); + touch_pad_set_measurement_interval(this->sleep_cycle_); + touch_pad_set_voltage(this->high_voltage_reference_, this->low_voltage_reference_, this->voltage_attenuation_); + + // Set up the channel mask for all configured pads + uint16_t channel_mask = 0; + for (auto *child : this->children_) { + channel_mask |= BIT(child->get_touch_pad()); + } + touch_pad_set_channel_mask(channel_mask); + + // Configure each touch pad + for (auto *child : this->children_) { + // Initialize the touch pad + touch_pad_config(child->get_touch_pad()); + + // Set threshold + if (child->get_threshold() != 0) { + touch_pad_set_thresh(child->get_touch_pad(), child->get_threshold()); + } + } + + // Configure timeout + touch_pad_timeout_set(true, TOUCH_PAD_THRESHOLD_MAX); + + // Register ISR handler with all interrupts + esp_err_t err = + touch_pad_isr_register(touch_isr_handler, this, static_cast(TOUCH_PAD_INTR_MASK_ALL)); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to register touch ISR: %s", esp_err_to_name(err)); + vQueueDelete(this->touch_queue_); + this->touch_queue_ = nullptr; + this->mark_failed(); + return; + } + + // Calculate release timeout based on sleep cycle + uint32_t rtc_freq = rtc_clk_slow_freq_get_hz(); + this->release_timeout_ms_ = (this->sleep_cycle_ * 1000 * 3) / (rtc_freq * 2); + if (this->release_timeout_ms_ < 100) { + this->release_timeout_ms_ = 100; + } + this->release_check_interval_ms_ = std::min(this->release_timeout_ms_ / 4, (uint32_t) 50); + + // Enable the interrupts we need + touch_pad_intr_enable(static_cast(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE | + TOUCH_PAD_INTR_MASK_TIMEOUT)); + + // Start the FSM after all configuration is complete + touch_pad_fsm_start(); +} + +void ESP32TouchComponent::dump_config() { + const char *lv_s = get_low_voltage_reference_str(this->low_voltage_reference_); + const char *hv_s = get_high_voltage_reference_str(this->high_voltage_reference_); + const char *atten_s = get_voltage_attenuation_str(this->voltage_attenuation_); + + ESP_LOGCONFIG(TAG, + "Config for ESP32 Touch Hub:\n" + " Meas cycle: %.2fms\n" + " Sleep cycle: %.2fms\n" + " Low Voltage Reference: %s\n" + " High Voltage Reference: %s\n" + " Voltage Attenuation: %s\n" + " ISR Configuration:\n" + " Release timeout: %" PRIu32 "ms\n" + " Release check interval: %" PRIu32 "ms", + this->meas_cycle_ / (8000000.0f / 1000.0f), this->sleep_cycle_ / (150000.0f / 1000.0f), lv_s, hv_s, + atten_s, this->release_timeout_ms_, this->release_check_interval_ms_); + + if (this->filter_configured_()) { + const char *filter_mode_s; + switch (this->filter_mode_) { + case TOUCH_PAD_FILTER_IIR_4: + filter_mode_s = "IIR_4"; + break; + case TOUCH_PAD_FILTER_IIR_8: + filter_mode_s = "IIR_8"; + break; + case TOUCH_PAD_FILTER_IIR_16: + filter_mode_s = "IIR_16"; + break; + case TOUCH_PAD_FILTER_IIR_32: + filter_mode_s = "IIR_32"; + break; + case TOUCH_PAD_FILTER_IIR_64: + filter_mode_s = "IIR_64"; + break; + case TOUCH_PAD_FILTER_IIR_128: + filter_mode_s = "IIR_128"; + break; + case TOUCH_PAD_FILTER_IIR_256: + filter_mode_s = "IIR_256"; + break; + case TOUCH_PAD_FILTER_JITTER: + filter_mode_s = "JITTER"; + break; + default: + filter_mode_s = "UNKNOWN"; + break; + } + ESP_LOGCONFIG(TAG, + " Filter mode: %s\n" + " Debounce count: %" PRIu32 "\n" + " Noise threshold coefficient: %" PRIu32 "\n" + " Jitter filter step size: %" PRIu32, + filter_mode_s, this->debounce_count_, this->noise_threshold_, this->jitter_step_); + const char *smooth_level_s; + switch (this->smooth_level_) { + case TOUCH_PAD_SMOOTH_OFF: + smooth_level_s = "OFF"; + break; + case TOUCH_PAD_SMOOTH_IIR_2: + smooth_level_s = "IIR_2"; + break; + case TOUCH_PAD_SMOOTH_IIR_4: + smooth_level_s = "IIR_4"; + break; + case TOUCH_PAD_SMOOTH_IIR_8: + smooth_level_s = "IIR_8"; + break; + default: + smooth_level_s = "UNKNOWN"; + break; + } + ESP_LOGCONFIG(TAG, " Smooth level: %s", smooth_level_s); + } + + if (this->denoise_configured_()) { + const char *grade_s; + switch (this->grade_) { + case TOUCH_PAD_DENOISE_BIT12: + grade_s = "BIT12"; + break; + case TOUCH_PAD_DENOISE_BIT10: + grade_s = "BIT10"; + break; + case TOUCH_PAD_DENOISE_BIT8: + grade_s = "BIT8"; + break; + case TOUCH_PAD_DENOISE_BIT4: + grade_s = "BIT4"; + break; + default: + grade_s = "UNKNOWN"; + break; + } + ESP_LOGCONFIG(TAG, " Denoise grade: %s", grade_s); + + const char *cap_level_s; + switch (this->cap_level_) { + case TOUCH_PAD_DENOISE_CAP_L0: + cap_level_s = "L0"; + break; + case TOUCH_PAD_DENOISE_CAP_L1: + cap_level_s = "L1"; + break; + case TOUCH_PAD_DENOISE_CAP_L2: + cap_level_s = "L2"; + break; + case TOUCH_PAD_DENOISE_CAP_L3: + cap_level_s = "L3"; + break; + case TOUCH_PAD_DENOISE_CAP_L4: + cap_level_s = "L4"; + break; + case TOUCH_PAD_DENOISE_CAP_L5: + cap_level_s = "L5"; + break; + case TOUCH_PAD_DENOISE_CAP_L6: + cap_level_s = "L6"; + break; + case TOUCH_PAD_DENOISE_CAP_L7: + cap_level_s = "L7"; + break; + default: + cap_level_s = "UNKNOWN"; + break; + } + ESP_LOGCONFIG(TAG, " Denoise capacitance level: %s", cap_level_s); + } + + if (this->setup_mode_) { + ESP_LOGCONFIG(TAG, " Setup Mode ENABLED"); + } + + for (auto *child : this->children_) { + LOG_BINARY_SENSOR(" ", "Touch Pad", child); + ESP_LOGCONFIG(TAG, " Pad: T%" PRIu32, (uint32_t) child->get_touch_pad()); + ESP_LOGCONFIG(TAG, " Threshold: %" PRIu32, child->get_threshold()); + } +} + +void ESP32TouchComponent::loop() { + const uint32_t now = App.get_loop_component_start_time(); + bool should_print = this->setup_mode_ && now - this->setup_mode_last_log_print_ > 250; + + // Print debug info for all pads in setup mode + if (should_print) { + for (auto *child : this->children_) { + uint32_t value = 0; + touch_pad_read_raw_data(child->get_touch_pad(), &value); + child->value_ = value; + ESP_LOGD(TAG, "Touch Pad '%s' (T%" PRIu32 "): %" PRIu32, child->get_name().c_str(), + (uint32_t) child->get_touch_pad(), value); + } + this->setup_mode_last_log_print_ = now; + } + + // Process any queued touch events from interrupts + TouchPadEvent event; + while (xQueueReceive(this->touch_queue_, &event, 0) == pdTRUE) { + // Handle timeout events + if (event.intr_mask & TOUCH_PAD_INTR_MASK_TIMEOUT) { + // Resume measurement after timeout + touch_pad_timeout_resume(); + continue; + } + + // Handle active/inactive events + if (event.intr_mask & (TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE)) { + // Process touch status for each pad + for (auto *child : this->children_) { + touch_pad_t pad = child->get_touch_pad(); + + // Check if this pad is in the status mask + if (event.pad_status & BIT(pad)) { + // Read current value + uint32_t value = 0; + if (this->filter_configured_()) { + touch_pad_filter_read_smooth(pad, &value); + } else { + touch_pad_read_raw_data(pad, &value); + } + + child->value_ = value; + + // For S2/S3, higher value means touched + bool is_touched = (event.intr_mask & TOUCH_PAD_INTR_MASK_ACTIVE) != 0; + + if (is_touched != child->last_state_) { + child->last_state_ = is_touched; + child->publish_state(is_touched); + ESP_LOGV(TAG, "Touch Pad '%s' state: %s (value: %" PRIu32 ", threshold: %" PRIu32 ")", + child->get_name().c_str(), is_touched ? "ON" : "OFF", value, child->get_threshold()); + } + } + } + } + } +} + +void ESP32TouchComponent::on_shutdown() { + // Disable interrupts + touch_pad_intr_disable(static_cast(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE | + TOUCH_PAD_INTR_MASK_TIMEOUT)); + touch_pad_isr_deregister(touch_isr_handler, this); + if (this->touch_queue_) { + vQueueDelete(this->touch_queue_); + } + + // Check if any pad is configured for wakeup + bool is_wakeup_source = false; + for (auto *child : this->children_) { + if (child->get_wakeup_threshold() != 0) { + if (!is_wakeup_source) { + is_wakeup_source = true; + // Touch sensor FSM mode must be 'TOUCH_FSM_MODE_TIMER' to use it to wake-up. + touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); + } + } + } + + if (!is_wakeup_source) { + touch_pad_deinit(); + } +} + +void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { + ESP32TouchComponent *component = static_cast(arg); + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + + // Read interrupt status and pad status + TouchPadEvent event; + event.intr_mask = touch_pad_read_intr_status_mask(); + event.pad_status = touch_pad_get_status(); + event.pad = touch_pad_get_current_meas_channel(); + + // Send event to queue for processing in main loop + xQueueSendFromISR(component->touch_queue_, &event, &xHigherPriorityTaskWoken); + + if (xHigherPriorityTaskWoken) { + portYIELD_FROM_ISR(); + } +} + +bool ESP32TouchComponent::filter_configured_() const { return this->filter_mode_ != TOUCH_PAD_FILTER_MAX; } + +bool ESP32TouchComponent::denoise_configured_() const { return this->grade_ != TOUCH_PAD_DENOISE_MAX; } + +bool ESP32TouchComponent::waterproof_configured_() const { return this->waterproof_guard_ring_pad_ != TOUCH_PAD_MAX; } + +} // namespace esp32_touch +} // namespace esphome + +#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 \ No newline at end of file From 719d8cac977b2c2d5c75b15af2af1fb9127415cd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 11:45:50 -0500 Subject: [PATCH 05/51] split it --- esphome/components/esp32_touch/esp32_touch_v1.cpp | 2 -- esphome/components/esp32_touch/esp32_touch_v2.cpp | 6 ------ 2 files changed, 8 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index 515c384279..f04aa7a048 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -262,8 +262,6 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { } } -bool ESP32TouchComponent::iir_filter_enabled_() const { return this->iir_filter_ > 0; } - } // namespace esp32_touch } // namespace esphome diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 6ce3594dac..9ea9fa1e02 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -366,12 +366,6 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { } } -bool ESP32TouchComponent::filter_configured_() const { return this->filter_mode_ != TOUCH_PAD_FILTER_MAX; } - -bool ESP32TouchComponent::denoise_configured_() const { return this->grade_ != TOUCH_PAD_DENOISE_MAX; } - -bool ESP32TouchComponent::waterproof_configured_() const { return this->waterproof_guard_ring_pad_ != TOUCH_PAD_MAX; } - } // namespace esp32_touch } // namespace esphome From 4ac2141307e40ef9372041f5ed7ae4e4ff5286be Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 11:52:29 -0500 Subject: [PATCH 06/51] adjust --- esphome/components/esp32_touch/esp32_touch.h | 4 ++ .../esp32_touch/esp32_touch_common.cpp | 42 +++++++++++++++++++ .../components/esp32_touch/esp32_touch_v1.cpp | 23 +--------- .../components/esp32_touch/esp32_touch_v2.cpp | 23 +--------- 4 files changed, 50 insertions(+), 42 deletions(-) create mode 100644 esphome/components/esp32_touch/esp32_touch_common.cpp diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 3d776f2d6e..758036c641 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -69,6 +69,10 @@ class ESP32TouchComponent : public Component { protected: static void touch_isr_handler(void *arg); + // Common helper methods used by both v1 and v2 + void dump_config_base_(); + void dump_config_sensors_(); + QueueHandle_t touch_queue_{nullptr}; uint32_t last_touch_time_[TOUCH_PAD_MAX] = {0}; // Track last time each pad was seen as touched uint32_t release_timeout_ms_{1500}; // Calculated timeout for release detection diff --git a/esphome/components/esp32_touch/esp32_touch_common.cpp b/esphome/components/esp32_touch/esp32_touch_common.cpp new file mode 100644 index 0000000000..132290401f --- /dev/null +++ b/esphome/components/esp32_touch/esp32_touch_common.cpp @@ -0,0 +1,42 @@ +#ifdef USE_ESP32 + +#include "esp32_touch.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace esp32_touch { + +static const char *const TAG = "esp32_touch"; + +void ESP32TouchComponent::dump_config_base_() { + const char *lv_s = get_low_voltage_reference_str(this->low_voltage_reference_); + const char *hv_s = get_high_voltage_reference_str(this->high_voltage_reference_); + const char *atten_s = get_voltage_attenuation_str(this->voltage_attenuation_); + + ESP_LOGCONFIG(TAG, + "Config for ESP32 Touch Hub:\n" + " Meas cycle: %.2fms\n" + " Sleep cycle: %.2fms\n" + " Low Voltage Reference: %s\n" + " High Voltage Reference: %s\n" + " Voltage Attenuation: %s\n" + " ISR Configuration:\n" + " Release timeout: %" PRIu32 "ms\n" + " Release check interval: %" PRIu32 "ms", + this->meas_cycle_ / (8000000.0f / 1000.0f), this->sleep_cycle_ / (150000.0f / 1000.0f), lv_s, hv_s, + atten_s, this->release_timeout_ms_, this->release_check_interval_ms_); +} + +void ESP32TouchComponent::dump_config_sensors_() { + for (auto *child : this->children_) { + LOG_BINARY_SENSOR(" ", "Touch Pad", child); + ESP_LOGCONFIG(TAG, " Pad: T%" PRIu32, (uint32_t) child->get_touch_pad()); + ESP_LOGCONFIG(TAG, " Threshold: %" PRIu32, child->get_threshold()); + } +} + +} // namespace esp32_touch +} // namespace esphome + +#endif // USE_ESP32 \ No newline at end of file diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index f04aa7a048..9356bd4c7c 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -78,22 +78,7 @@ void ESP32TouchComponent::setup() { } void ESP32TouchComponent::dump_config() { - const char *lv_s = get_low_voltage_reference_str(this->low_voltage_reference_); - const char *hv_s = get_high_voltage_reference_str(this->high_voltage_reference_); - const char *atten_s = get_voltage_attenuation_str(this->voltage_attenuation_); - - ESP_LOGCONFIG(TAG, - "Config for ESP32 Touch Hub:\n" - " Meas cycle: %.2fms\n" - " Sleep cycle: %.2fms\n" - " Low Voltage Reference: %s\n" - " High Voltage Reference: %s\n" - " Voltage Attenuation: %s\n" - " ISR Configuration:\n" - " Release timeout: %" PRIu32 "ms\n" - " Release check interval: %" PRIu32 "ms", - this->meas_cycle_ / (8000000.0f / 1000.0f), this->sleep_cycle_ / (150000.0f / 1000.0f), lv_s, hv_s, - atten_s, this->release_timeout_ms_, this->release_check_interval_ms_); + this->dump_config_base_(); if (this->iir_filter_enabled_()) { ESP_LOGCONFIG(TAG, " IIR Filter: %" PRIu32 "ms", this->iir_filter_); @@ -105,11 +90,7 @@ void ESP32TouchComponent::dump_config() { ESP_LOGCONFIG(TAG, " Setup Mode ENABLED"); } - for (auto *child : this->children_) { - LOG_BINARY_SENSOR(" ", "Touch Pad", child); - ESP_LOGCONFIG(TAG, " Pad: T%" PRIu32, (uint32_t) child->get_touch_pad()); - ESP_LOGCONFIG(TAG, " Threshold: %" PRIu32, child->get_threshold()); - } + this->dump_config_sensors_(); } void ESP32TouchComponent::loop() { diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 9ea9fa1e02..9d27286682 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -123,22 +123,7 @@ void ESP32TouchComponent::setup() { } void ESP32TouchComponent::dump_config() { - const char *lv_s = get_low_voltage_reference_str(this->low_voltage_reference_); - const char *hv_s = get_high_voltage_reference_str(this->high_voltage_reference_); - const char *atten_s = get_voltage_attenuation_str(this->voltage_attenuation_); - - ESP_LOGCONFIG(TAG, - "Config for ESP32 Touch Hub:\n" - " Meas cycle: %.2fms\n" - " Sleep cycle: %.2fms\n" - " Low Voltage Reference: %s\n" - " High Voltage Reference: %s\n" - " Voltage Attenuation: %s\n" - " ISR Configuration:\n" - " Release timeout: %" PRIu32 "ms\n" - " Release check interval: %" PRIu32 "ms", - this->meas_cycle_ / (8000000.0f / 1000.0f), this->sleep_cycle_ / (150000.0f / 1000.0f), lv_s, hv_s, - atten_s, this->release_timeout_ms_, this->release_check_interval_ms_); + this->dump_config_base_(); if (this->filter_configured_()) { const char *filter_mode_s; @@ -256,11 +241,7 @@ void ESP32TouchComponent::dump_config() { ESP_LOGCONFIG(TAG, " Setup Mode ENABLED"); } - for (auto *child : this->children_) { - LOG_BINARY_SENSOR(" ", "Touch Pad", child); - ESP_LOGCONFIG(TAG, " Pad: T%" PRIu32, (uint32_t) child->get_touch_pad()); - ESP_LOGCONFIG(TAG, " Threshold: %" PRIu32, child->get_threshold()); - } + this->dump_config_sensors_(); } void ESP32TouchComponent::loop() { From 48f43d3eb193dc94d8fd67e30106942b46a0d462 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 11:58:21 -0500 Subject: [PATCH 07/51] tweak --- esphome/components/esp32_touch/esp32_touch.cpp | 4 ---- esphome/components/esp32_touch/esp32_touch_common.cpp | 2 +- esphome/components/esp32_touch/esp32_touch_v1.cpp | 2 +- esphome/components/esp32_touch/esp32_touch_v2.cpp | 2 +- 4 files changed, 3 insertions(+), 7 deletions(-) delete mode 100644 esphome/components/esp32_touch/esp32_touch.cpp diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp deleted file mode 100644 index 4b2635e685..0000000000 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ /dev/null @@ -1,4 +0,0 @@ -// ESP32 touch sensor implementation -// Platform-specific implementations are in: -// - esp32_touch_esp32.cpp for original ESP32 -// - esp32_touch_esp32s2s3.cpp for ESP32-S2/S3 \ No newline at end of file diff --git a/esphome/components/esp32_touch/esp32_touch_common.cpp b/esphome/components/esp32_touch/esp32_touch_common.cpp index 132290401f..1ad195dd8f 100644 --- a/esphome/components/esp32_touch/esp32_touch_common.cpp +++ b/esphome/components/esp32_touch/esp32_touch_common.cpp @@ -39,4 +39,4 @@ void ESP32TouchComponent::dump_config_sensors_() { } // namespace esp32_touch } // namespace esphome -#endif // USE_ESP32 \ No newline at end of file +#endif // USE_ESP32 diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index 9356bd4c7c..bb715c8587 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -246,4 +246,4 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { } // namespace esp32_touch } // namespace esphome -#endif // USE_ESP32_VARIANT_ESP32 \ No newline at end of file +#endif // USE_ESP32_VARIANT_ESP32 diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 9d27286682..27cfef2b2d 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -350,4 +350,4 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { } // namespace esp32_touch } // namespace esphome -#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 \ No newline at end of file +#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 From 5f1383344d187159d045193eaf800bcadbf9edf4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 12:10:50 -0500 Subject: [PATCH 08/51] tweak --- .../components/esp32_touch/esp32_touch_v2.cpp | 120 ++++++++++-------- 1 file changed, 70 insertions(+), 50 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 27cfef2b2d..920c9508b0 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -23,10 +23,7 @@ static const char *const TAG = "esp32_touch"; void ESP32TouchComponent::setup() { ESP_LOGCONFIG(TAG, "Running setup for ESP32-S2/S3"); - touch_pad_init(); - touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); - - // Create queue for touch events + // Create queue for touch events first size_t queue_size = this->children_.size() * 4; if (queue_size < 8) queue_size = 8; @@ -38,6 +35,14 @@ void ESP32TouchComponent::setup() { return; } + // Initialize touch pad peripheral + touch_pad_init(); + + // Configure each touch pad first + for (auto *child : this->children_) { + touch_pad_config(child->get_touch_pad()); + } + // Set up filtering if configured if (this->filter_configured_()) { touch_filter_config_t filter_info = { @@ -70,34 +75,12 @@ void ESP32TouchComponent::setup() { } // Configure measurement parameters + touch_pad_set_voltage(this->high_voltage_reference_, this->low_voltage_reference_, this->voltage_attenuation_); touch_pad_set_charge_discharge_times(this->meas_cycle_); touch_pad_set_measurement_interval(this->sleep_cycle_); - touch_pad_set_voltage(this->high_voltage_reference_, this->low_voltage_reference_, this->voltage_attenuation_); - // Set up the channel mask for all configured pads - uint16_t channel_mask = 0; - for (auto *child : this->children_) { - channel_mask |= BIT(child->get_touch_pad()); - } - touch_pad_set_channel_mask(channel_mask); - - // Configure each touch pad - for (auto *child : this->children_) { - // Initialize the touch pad - touch_pad_config(child->get_touch_pad()); - - // Set threshold - if (child->get_threshold() != 0) { - touch_pad_set_thresh(child->get_touch_pad(), child->get_threshold()); - } - } - - // Configure timeout - touch_pad_timeout_set(true, TOUCH_PAD_THRESHOLD_MAX); - - // Register ISR handler with all interrupts - esp_err_t err = - touch_pad_isr_register(touch_isr_handler, this, static_cast(TOUCH_PAD_INTR_MASK_ALL)); + // Register ISR handler + esp_err_t err = touch_pad_isr_register(touch_isr_handler, this); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to register touch ISR: %s", esp_err_to_name(err)); vQueueDelete(this->touch_queue_); @@ -106,6 +89,36 @@ void ESP32TouchComponent::setup() { return; } + // Enable interrupts + touch_pad_intr_enable(static_cast(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE)); + + // Set FSM mode + touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); + + // Start FSM + touch_pad_fsm_start(); + + // Wait a bit for initial measurements + vTaskDelay(10 / portTICK_PERIOD_MS); + + // Read initial benchmark values and set thresholds if not explicitly configured + for (auto *child : this->children_) { + uint32_t benchmark = 0; + touch_pad_read_benchmark(child->get_touch_pad(), &benchmark); + + ESP_LOGD(TAG, "Touch pad %d benchmark value: %d", child->get_touch_pad(), benchmark); + + // If threshold is 0, calculate it as 80% of benchmark (20% change threshold) + if (child->get_threshold() == 0 && benchmark > 0) { + uint32_t threshold = benchmark * 0.8; + child->set_threshold(threshold); + ESP_LOGD(TAG, "Setting threshold for pad %d to %d (80%% of benchmark)", child->get_touch_pad(), threshold); + } + + // Set the threshold + touch_pad_set_thresh(child->get_touch_pad(), child->get_threshold()); + } + // Calculate release timeout based on sleep cycle uint32_t rtc_freq = rtc_clk_slow_freq_get_hz(); this->release_timeout_ms_ = (this->sleep_cycle_ * 1000 * 3) / (rtc_freq * 2); @@ -113,13 +126,6 @@ void ESP32TouchComponent::setup() { this->release_timeout_ms_ = 100; } this->release_check_interval_ms_ = std::min(this->release_timeout_ms_ / 4, (uint32_t) 50); - - // Enable the interrupts we need - touch_pad_intr_enable(static_cast(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE | - TOUCH_PAD_INTR_MASK_TIMEOUT)); - - // Start the FSM after all configuration is complete - touch_pad_fsm_start(); } void ESP32TouchComponent::dump_config() { @@ -246,19 +252,6 @@ void ESP32TouchComponent::dump_config() { void ESP32TouchComponent::loop() { const uint32_t now = App.get_loop_component_start_time(); - bool should_print = this->setup_mode_ && now - this->setup_mode_last_log_print_ > 250; - - // Print debug info for all pads in setup mode - if (should_print) { - for (auto *child : this->children_) { - uint32_t value = 0; - touch_pad_read_raw_data(child->get_touch_pad(), &value); - child->value_ = value; - ESP_LOGD(TAG, "Touch Pad '%s' (T%" PRIu32 "): %" PRIu32, child->get_name().c_str(), - (uint32_t) child->get_touch_pad(), value); - } - this->setup_mode_last_log_print_ = now; - } // Process any queued touch events from interrupts TouchPadEvent event; @@ -283,7 +276,7 @@ void ESP32TouchComponent::loop() { if (this->filter_configured_()) { touch_pad_filter_read_smooth(pad, &value); } else { - touch_pad_read_raw_data(pad, &value); + touch_pad_read_benchmark(pad, &value); } child->value_ = value; @@ -297,10 +290,37 @@ void ESP32TouchComponent::loop() { ESP_LOGV(TAG, "Touch Pad '%s' state: %s (value: %" PRIu32 ", threshold: %" PRIu32 ")", child->get_name().c_str(), is_touched ? "ON" : "OFF", value, child->get_threshold()); } + + // In setup mode, log every event + if (this->setup_mode_) { + ESP_LOGD(TAG, "Touch Pad '%s' (T%d): value=%d, threshold=%d, touched=%s", child->get_name().c_str(), pad, + value, child->get_threshold(), is_touched ? "YES" : "NO"); + } } } } } + + // In setup mode, periodically log all pad values + if (this->setup_mode_ && now - this->setup_mode_last_log_print_ > 1000) { + ESP_LOGD(TAG, "=== Touch Pad Status ==="); + for (auto *child : this->children_) { + uint32_t benchmark = 0; + uint32_t smooth = 0; + + touch_pad_read_benchmark(child->get_touch_pad(), &benchmark); + + if (this->filter_configured_()) { + touch_pad_filter_read_smooth(child->get_touch_pad(), &smooth); + ESP_LOGD(TAG, " Pad T%d: benchmark=%d, smooth=%d, threshold=%d", child->get_touch_pad(), benchmark, smooth, + child->get_threshold()); + } else { + ESP_LOGD(TAG, " Pad T%d: benchmark=%d, threshold=%d", child->get_touch_pad(), benchmark, + child->get_threshold()); + } + } + this->setup_mode_last_log_print_ = now; + } } void ESP32TouchComponent::on_shutdown() { From 13d7c5a9a9312f0cd53d77bf59fada1648114adc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 12:12:55 -0500 Subject: [PATCH 09/51] more debug --- .../components/esp32_touch/esp32_touch_v2.cpp | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 920c9508b0..e9ede2539c 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -79,8 +79,22 @@ void ESP32TouchComponent::setup() { touch_pad_set_charge_discharge_times(this->meas_cycle_); touch_pad_set_measurement_interval(this->sleep_cycle_); - // Register ISR handler - esp_err_t err = touch_pad_isr_register(touch_isr_handler, this); + // Set FSM mode before starting + touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); + + // Configure which pads to scan + uint16_t channel_mask = 0; + for (auto *child : this->children_) { + channel_mask |= BIT(child->get_touch_pad()); + } + touch_pad_set_channel_mask(channel_mask); + + // Configure timeout if needed + touch_pad_timeout_set(true, TOUCH_PAD_THRESHOLD_MAX); + + // Register ISR handler with interrupt mask + esp_err_t err = + touch_pad_isr_register(touch_isr_handler, this, static_cast(TOUCH_PAD_INTR_MASK_ALL)); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to register touch ISR: %s", esp_err_to_name(err)); vQueueDelete(this->touch_queue_); @@ -92,9 +106,6 @@ void ESP32TouchComponent::setup() { // Enable interrupts touch_pad_intr_enable(static_cast(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE)); - // Set FSM mode - touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); - // Start FSM touch_pad_fsm_start(); From a28c951272edbb12710f828b6f71108772a459f7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 12:13:46 -0500 Subject: [PATCH 10/51] more debug --- esphome/components/esp32_touch/esp32_touch_v2.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index e9ede2539c..8d378b58eb 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -109,8 +109,9 @@ void ESP32TouchComponent::setup() { // Start FSM touch_pad_fsm_start(); - // Wait a bit for initial measurements - vTaskDelay(10 / portTICK_PERIOD_MS); + // Wait longer for initial measurements to complete + // Need to wait for at least one full measurement cycle + vTaskDelay(100 / portTICK_PERIOD_MS); // Read initial benchmark values and set thresholds if not explicitly configured for (auto *child : this->children_) { From 919c32f0cc2488c4a2de4747423afd14f3e6964c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 12:20:47 -0500 Subject: [PATCH 11/51] tweak --- .../components/esp32_touch/esp32_touch_v2.cpp | 50 +++++-------------- 1 file changed, 13 insertions(+), 37 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 8d378b58eb..78f7949a74 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -40,7 +40,12 @@ void ESP32TouchComponent::setup() { // Configure each touch pad first for (auto *child : this->children_) { - touch_pad_config(child->get_touch_pad()); + esp_err_t config_err = touch_pad_config(child->get_touch_pad()); + if (config_err != ESP_OK) { + ESP_LOGE(TAG, "Failed to configure touch pad %d: %s", child->get_touch_pad(), esp_err_to_name(config_err)); + } else { + ESP_LOGD(TAG, "Configured touch pad %d", child->get_touch_pad()); + } } // Set up filtering if configured @@ -79,16 +84,6 @@ void ESP32TouchComponent::setup() { touch_pad_set_charge_discharge_times(this->meas_cycle_); touch_pad_set_measurement_interval(this->sleep_cycle_); - // Set FSM mode before starting - touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); - - // Configure which pads to scan - uint16_t channel_mask = 0; - for (auto *child : this->children_) { - channel_mask |= BIT(child->get_touch_pad()); - } - touch_pad_set_channel_mask(channel_mask); - // Configure timeout if needed touch_pad_timeout_set(true, TOUCH_PAD_THRESHOLD_MAX); @@ -104,40 +99,21 @@ void ESP32TouchComponent::setup() { } // Enable interrupts - touch_pad_intr_enable(static_cast(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE)); + touch_pad_intr_enable(static_cast(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE | + TOUCH_PAD_INTR_MASK_TIMEOUT)); + + // Set FSM mode before starting + touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); // Start FSM touch_pad_fsm_start(); - // Wait longer for initial measurements to complete - // Need to wait for at least one full measurement cycle - vTaskDelay(100 / portTICK_PERIOD_MS); - // Read initial benchmark values and set thresholds if not explicitly configured for (auto *child : this->children_) { - uint32_t benchmark = 0; - touch_pad_read_benchmark(child->get_touch_pad(), &benchmark); - - ESP_LOGD(TAG, "Touch pad %d benchmark value: %d", child->get_touch_pad(), benchmark); - - // If threshold is 0, calculate it as 80% of benchmark (20% change threshold) - if (child->get_threshold() == 0 && benchmark > 0) { - uint32_t threshold = benchmark * 0.8; - child->set_threshold(threshold); - ESP_LOGD(TAG, "Setting threshold for pad %d to %d (80%% of benchmark)", child->get_touch_pad(), threshold); + if (child->get_threshold() != 0) { + touch_pad_set_thresh(child->get_touch_pad(), child->get_threshold()); } - - // Set the threshold - touch_pad_set_thresh(child->get_touch_pad(), child->get_threshold()); } - - // Calculate release timeout based on sleep cycle - uint32_t rtc_freq = rtc_clk_slow_freq_get_hz(); - this->release_timeout_ms_ = (this->sleep_cycle_ * 1000 * 3) / (rtc_freq * 2); - if (this->release_timeout_ms_ < 100) { - this->release_timeout_ms_ = 100; - } - this->release_check_interval_ms_ = std::min(this->release_timeout_ms_ / 4, (uint32_t) 50); } void ESP32TouchComponent::dump_config() { From 7502c6b6c0567f3515f9c7d292b40b83d945d7d1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 12:44:28 -0500 Subject: [PATCH 12/51] debug --- .../components/esp32_touch/esp32_touch_v2.cpp | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 78f7949a74..89ca2d174c 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -21,7 +21,15 @@ namespace esp32_touch { static const char *const TAG = "esp32_touch"; void ESP32TouchComponent::setup() { - ESP_LOGCONFIG(TAG, "Running setup for ESP32-S2/S3"); + // Add a delay to allow serial connection, but feed the watchdog + ESP_LOGCONFIG(TAG, "Waiting 5 seconds before touch sensor setup..."); + for (int i = 0; i < 50; i++) { + vTaskDelay(100 / portTICK_PERIOD_MS); + App.feed_wdt(); + } + + ESP_LOGCONFIG(TAG, "=== ESP32 Touch Sensor v2 Setup Starting ==="); + ESP_LOGCONFIG(TAG, "Configuring %d touch pads", this->children_.size()); // Create queue for touch events first size_t queue_size = this->children_.size() * 4; @@ -36,9 +44,16 @@ void ESP32TouchComponent::setup() { } // Initialize touch pad peripheral - touch_pad_init(); + ESP_LOGD(TAG, "Initializing touch pad peripheral..."); + esp_err_t init_err = touch_pad_init(); + if (init_err != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize touch pad: %s", esp_err_to_name(init_err)); + this->mark_failed(); + return; + } // Configure each touch pad first + ESP_LOGD(TAG, "Configuring individual touch pads..."); for (auto *child : this->children_) { esp_err_t config_err = touch_pad_config(child->get_touch_pad()); if (config_err != ESP_OK) { @@ -108,11 +123,20 @@ void ESP32TouchComponent::setup() { // Start FSM touch_pad_fsm_start(); - // Read initial benchmark values and set thresholds if not explicitly configured + // Wait for initial measurements + vTaskDelay(50 / portTICK_PERIOD_MS); + + // Read initial values and set thresholds for (auto *child : this->children_) { if (child->get_threshold() != 0) { touch_pad_set_thresh(child->get_touch_pad(), child->get_threshold()); } + + // Try to read initial values for debugging + uint32_t raw = 0, benchmark = 0; + touch_pad_read_raw_data(child->get_touch_pad(), &raw); + touch_pad_read_benchmark(child->get_touch_pad(), &benchmark); + ESP_LOGD(TAG, "Initial pad %d: raw=%d, benchmark=%d", child->get_touch_pad(), raw, benchmark); } } @@ -293,17 +317,19 @@ void ESP32TouchComponent::loop() { if (this->setup_mode_ && now - this->setup_mode_last_log_print_ > 1000) { ESP_LOGD(TAG, "=== Touch Pad Status ==="); for (auto *child : this->children_) { + uint32_t raw = 0; uint32_t benchmark = 0; uint32_t smooth = 0; + touch_pad_read_raw_data(child->get_touch_pad(), &raw); touch_pad_read_benchmark(child->get_touch_pad(), &benchmark); if (this->filter_configured_()) { touch_pad_filter_read_smooth(child->get_touch_pad(), &smooth); - ESP_LOGD(TAG, " Pad T%d: benchmark=%d, smooth=%d, threshold=%d", child->get_touch_pad(), benchmark, smooth, - child->get_threshold()); + ESP_LOGD(TAG, " Pad T%d: raw=%d, benchmark=%d, smooth=%d, threshold=%d", child->get_touch_pad(), raw, + benchmark, smooth, child->get_threshold()); } else { - ESP_LOGD(TAG, " Pad T%d: benchmark=%d, threshold=%d", child->get_touch_pad(), benchmark, + ESP_LOGD(TAG, " Pad T%d: raw=%d, benchmark=%d, threshold=%d", child->get_touch_pad(), raw, benchmark, child->get_threshold()); } } @@ -347,6 +373,11 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { event.pad_status = touch_pad_get_status(); event.pad = touch_pad_get_current_meas_channel(); + // Debug logging from ISR (using ROM functions for ISR safety) - only log non-timeout events for now + // if (event.intr_mask != 0x10 || event.pad_status != 0) { + ets_printf("ISR: intr=0x%x, status=0x%x, pad=%d\n", event.intr_mask, event.pad_status, event.pad); + //} + // Send event to queue for processing in main loop xQueueSendFromISR(component->touch_queue_, &event, &xHigherPriorityTaskWoken); From 50840b210592c3ef51dcb34e58c97b41dd6946d7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 13:00:39 -0500 Subject: [PATCH 13/51] derbug --- .../components/esp32_touch/esp32_touch_v2.cpp | 70 ++++++++++++------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 89ca2d174c..6fd2394815 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -277,36 +277,56 @@ void ESP32TouchComponent::loop() { // Handle active/inactive events if (event.intr_mask & (TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE)) { - // Process touch status for each pad - for (auto *child : this->children_) { - touch_pad_t pad = child->get_touch_pad(); + // For INACTIVE events, we need to check which pad was released + // The pad number is in event.pad + if (event.intr_mask & TOUCH_PAD_INTR_MASK_INACTIVE) { + // Find the child for this pad + for (auto *child : this->children_) { + if (child->get_touch_pad() == event.pad) { + // Read current value + uint32_t value = 0; + if (this->filter_configured_()) { + touch_pad_filter_read_smooth(event.pad, &value); + } else { + touch_pad_read_benchmark(event.pad, &value); + } - // Check if this pad is in the status mask - if (event.pad_status & BIT(pad)) { - // Read current value - uint32_t value = 0; - if (this->filter_configured_()) { - touch_pad_filter_read_smooth(pad, &value); - } else { - touch_pad_read_benchmark(pad, &value); + child->value_ = value; + + // This is an INACTIVE event, so not touched + if (child->last_state_) { + child->last_state_ = false; + child->publish_state(false); + ESP_LOGD(TAG, "Touch Pad '%s' released (value: %d, threshold: %d)", child->get_name().c_str(), value, + child->get_threshold()); + } + break; } + } + } else if (event.intr_mask & TOUCH_PAD_INTR_MASK_ACTIVE) { + // For ACTIVE events, check the pad status mask + for (auto *child : this->children_) { + touch_pad_t pad = child->get_touch_pad(); - child->value_ = value; + // Check if this pad is in the status mask + if (event.pad_status & BIT(pad)) { + // Read current value + uint32_t value = 0; + if (this->filter_configured_()) { + touch_pad_filter_read_smooth(pad, &value); + } else { + touch_pad_read_benchmark(pad, &value); + } - // For S2/S3, higher value means touched - bool is_touched = (event.intr_mask & TOUCH_PAD_INTR_MASK_ACTIVE) != 0; + child->value_ = value; - if (is_touched != child->last_state_) { - child->last_state_ = is_touched; - child->publish_state(is_touched); - ESP_LOGV(TAG, "Touch Pad '%s' state: %s (value: %" PRIu32 ", threshold: %" PRIu32 ")", - child->get_name().c_str(), is_touched ? "ON" : "OFF", value, child->get_threshold()); - } - - // In setup mode, log every event - if (this->setup_mode_) { - ESP_LOGD(TAG, "Touch Pad '%s' (T%d): value=%d, threshold=%d, touched=%s", child->get_name().c_str(), pad, - value, child->get_threshold(), is_touched ? "YES" : "NO"); + // This is an ACTIVE event, so touched + if (!child->last_state_) { + child->last_state_ = true; + child->publish_state(true); + ESP_LOGD(TAG, "Touch Pad '%s' touched (value: %d, threshold: %d)", child->get_name().c_str(), value, + child->get_threshold()); + } } } } From d440c4bc43454b60b0ecec78de52aab3c3460f9f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 13:00:55 -0500 Subject: [PATCH 14/51] derbug --- .../components/esp32_touch/esp32_touch_v2.cpp | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 6fd2394815..37f6b2c49a 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -122,22 +122,6 @@ void ESP32TouchComponent::setup() { // Start FSM touch_pad_fsm_start(); - - // Wait for initial measurements - vTaskDelay(50 / portTICK_PERIOD_MS); - - // Read initial values and set thresholds - for (auto *child : this->children_) { - if (child->get_threshold() != 0) { - touch_pad_set_thresh(child->get_touch_pad(), child->get_threshold()); - } - - // Try to read initial values for debugging - uint32_t raw = 0, benchmark = 0; - touch_pad_read_raw_data(child->get_touch_pad(), &raw); - touch_pad_read_benchmark(child->get_touch_pad(), &benchmark); - ESP_LOGD(TAG, "Initial pad %d: raw=%d, benchmark=%d", child->get_touch_pad(), raw, benchmark); - } } void ESP32TouchComponent::dump_config() { From 0021e766496aaac9b0ecec2ac8727552540c1752 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 13:07:25 -0500 Subject: [PATCH 15/51] working --- .../components/esp32_touch/esp32_touch_v2.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 37f6b2c49a..020570e092 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -122,6 +122,13 @@ void ESP32TouchComponent::setup() { // Start FSM touch_pad_fsm_start(); + + // Set thresholds for each pad + for (auto *child : this->children_) { + if (child->get_threshold() != 0) { + touch_pad_set_thresh(child->get_touch_pad(), child->get_threshold()); + } + } } void ESP32TouchComponent::dump_config() { @@ -278,12 +285,10 @@ void ESP32TouchComponent::loop() { child->value_ = value; // This is an INACTIVE event, so not touched - if (child->last_state_) { - child->last_state_ = false; - child->publish_state(false); - ESP_LOGD(TAG, "Touch Pad '%s' released (value: %d, threshold: %d)", child->get_name().c_str(), value, - child->get_threshold()); - } + child->last_state_ = false; + child->publish_state(false); + ESP_LOGD(TAG, "Touch Pad '%s' released (value: %d, threshold: %d)", child->get_name().c_str(), value, + child->get_threshold()); break; } } From 376be1f00901ba54d7fc60533b048099a8bea1c7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 13:12:40 -0500 Subject: [PATCH 16/51] touch ups --- esphome/components/esp32_touch/esp32_touch.h | 2 + .../components/esp32_touch/esp32_touch_v1.cpp | 3 +- .../components/esp32_touch/esp32_touch_v2.cpp | 76 ++++++++----------- 3 files changed, 35 insertions(+), 46 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 758036c641..c1b0a3c377 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -15,6 +15,8 @@ namespace esphome { namespace esp32_touch { +static const uint32_t SETUP_MODE_LOG_INTERVAL_MS = 250; + class ESP32TouchBinarySensor; struct TouchPadEvent { diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index bb715c8587..b040a63355 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -95,10 +95,9 @@ void ESP32TouchComponent::dump_config() { void ESP32TouchComponent::loop() { const uint32_t now = App.get_loop_component_start_time(); - bool should_print = this->setup_mode_ && now - this->setup_mode_last_log_print_ > 250; // Print debug info for all pads in setup mode - if (should_print) { + if (this->setup_mode_ && now - this->setup_mode_last_log_print_ > SETUP_MODE_LOG_INTERVAL_MS) { for (auto *child : this->children_) { ESP_LOGD(TAG, "Touch Pad '%s' (T%" PRIu32 "): %" PRIu32, child->get_name().c_str(), (uint32_t) child->get_touch_pad(), child->value_); diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 020570e092..6df12f6440 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -268,54 +268,43 @@ void ESP32TouchComponent::loop() { // Handle active/inactive events if (event.intr_mask & (TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE)) { - // For INACTIVE events, we need to check which pad was released - // The pad number is in event.pad - if (event.intr_mask & TOUCH_PAD_INTR_MASK_INACTIVE) { - // Find the child for this pad - for (auto *child : this->children_) { - if (child->get_touch_pad() == event.pad) { - // Read current value - uint32_t value = 0; - if (this->filter_configured_()) { - touch_pad_filter_read_smooth(event.pad, &value); - } else { - touch_pad_read_benchmark(event.pad, &value); - } + bool is_touch_event = (event.intr_mask & TOUCH_PAD_INTR_MASK_ACTIVE) != 0; - child->value_ = value; + // For INACTIVE events, we check specific pad. For ACTIVE events, check pad status mask + for (auto *child : this->children_) { + touch_pad_t pad = child->get_touch_pad(); + bool should_process = false; - // This is an INACTIVE event, so not touched - child->last_state_ = false; - child->publish_state(false); - ESP_LOGD(TAG, "Touch Pad '%s' released (value: %d, threshold: %d)", child->get_name().c_str(), value, - child->get_threshold()); - break; - } + if (is_touch_event) { + // ACTIVE event - check if this pad is in the status mask + should_process = (event.pad_status & BIT(pad)) != 0; + } else { + // INACTIVE event - check if this is the specific pad that was released + should_process = (pad == event.pad); } - } else if (event.intr_mask & TOUCH_PAD_INTR_MASK_ACTIVE) { - // For ACTIVE events, check the pad status mask - for (auto *child : this->children_) { - touch_pad_t pad = child->get_touch_pad(); - // Check if this pad is in the status mask - if (event.pad_status & BIT(pad)) { - // Read current value - uint32_t value = 0; - if (this->filter_configured_()) { - touch_pad_filter_read_smooth(pad, &value); - } else { - touch_pad_read_benchmark(pad, &value); - } + if (should_process) { + // Read current value + uint32_t value = 0; + if (this->filter_configured_()) { + touch_pad_filter_read_smooth(pad, &value); + } else { + touch_pad_read_benchmark(pad, &value); + } - child->value_ = value; + child->value_ = value; - // This is an ACTIVE event, so touched - if (!child->last_state_) { - child->last_state_ = true; - child->publish_state(true); - ESP_LOGD(TAG, "Touch Pad '%s' touched (value: %d, threshold: %d)", child->get_name().c_str(), value, - child->get_threshold()); - } + // Update state if changed + if (child->last_state_ != is_touch_event) { + child->last_state_ = is_touch_event; + child->publish_state(is_touch_event); + ESP_LOGD(TAG, "Touch Pad '%s' %s (value: %d, threshold: %d)", child->get_name().c_str(), + is_touch_event ? "touched" : "released", value, child->get_threshold()); + } + + // For INACTIVE events, we only process one pad + if (!is_touch_event) { + break; } } } @@ -323,8 +312,7 @@ void ESP32TouchComponent::loop() { } // In setup mode, periodically log all pad values - if (this->setup_mode_ && now - this->setup_mode_last_log_print_ > 1000) { - ESP_LOGD(TAG, "=== Touch Pad Status ==="); + if (this->setup_mode_ && now - this->setup_mode_last_log_print_ > SETUP_MODE_LOG_INTERVAL_MS) { for (auto *child : this->children_) { uint32_t raw = 0; uint32_t benchmark = 0; From 851742035622ea20e8b990fd51f5a6f090725150 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 13:14:29 -0500 Subject: [PATCH 17/51] touch ups --- .../components/esp32_touch/esp32_touch_v2.cpp | 25 ++++--------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 6df12f6440..c570bcd8f6 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -270,26 +270,15 @@ void ESP32TouchComponent::loop() { if (event.intr_mask & (TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE)) { bool is_touch_event = (event.intr_mask & TOUCH_PAD_INTR_MASK_ACTIVE) != 0; - // For INACTIVE events, we check specific pad. For ACTIVE events, check pad status mask + // Find the child for the pad that triggered the interrupt for (auto *child : this->children_) { - touch_pad_t pad = child->get_touch_pad(); - bool should_process = false; - - if (is_touch_event) { - // ACTIVE event - check if this pad is in the status mask - should_process = (event.pad_status & BIT(pad)) != 0; - } else { - // INACTIVE event - check if this is the specific pad that was released - should_process = (pad == event.pad); - } - - if (should_process) { + if (child->get_touch_pad() == event.pad) { // Read current value uint32_t value = 0; if (this->filter_configured_()) { - touch_pad_filter_read_smooth(pad, &value); + touch_pad_filter_read_smooth(event.pad, &value); } else { - touch_pad_read_benchmark(pad, &value); + touch_pad_read_benchmark(event.pad, &value); } child->value_ = value; @@ -301,11 +290,7 @@ void ESP32TouchComponent::loop() { ESP_LOGD(TAG, "Touch Pad '%s' %s (value: %d, threshold: %d)", child->get_name().c_str(), is_touch_event ? "touched" : "released", value, child->get_threshold()); } - - // For INACTIVE events, we only process one pad - if (!is_touch_event) { - break; - } + break; } } } From aecf08021176d919508bb36a514bf4551a7fe633 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 13:16:48 -0500 Subject: [PATCH 18/51] touch ups --- esphome/components/esp32_touch/esp32_touch.h | 10 ---------- esphome/components/esp32_touch/esp32_touch_v1.cpp | 12 +++++++++--- esphome/components/esp32_touch/esp32_touch_v2.cpp | 12 +++++++++--- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index c1b0a3c377..ba05cdcebb 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -19,16 +19,6 @@ static const uint32_t SETUP_MODE_LOG_INTERVAL_MS = 250; class ESP32TouchBinarySensor; -struct TouchPadEvent { - touch_pad_t pad; - uint32_t value; - bool is_touched; // Whether this pad is currently touched -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - uint32_t intr_mask; // Interrupt mask for S2/S3 - uint32_t pad_status; // Pad status bitmap for S2/S3 -#endif -}; - class ESP32TouchComponent : public Component { public: void register_touch_pad(ESP32TouchBinarySensor *pad) { this->children_.push_back(pad); } diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index b040a63355..0ee7990a94 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -18,6 +18,12 @@ namespace esp32_touch { static const char *const TAG = "esp32_touch"; +struct TouchPadEventV1 { + touch_pad_t pad; + uint32_t value; + bool is_touched; +}; + void ESP32TouchComponent::setup() { ESP_LOGCONFIG(TAG, "Running setup for ESP32"); @@ -29,7 +35,7 @@ void ESP32TouchComponent::setup() { if (queue_size < 8) queue_size = 8; - this->touch_queue_ = xQueueCreate(queue_size, sizeof(TouchPadEvent)); + this->touch_queue_ = xQueueCreate(queue_size, sizeof(TouchPadEventV1)); if (this->touch_queue_ == nullptr) { ESP_LOGE(TAG, "Failed to create touch event queue of size %d", queue_size); this->mark_failed(); @@ -106,7 +112,7 @@ void ESP32TouchComponent::loop() { } // Process any queued touch events from interrupts - TouchPadEvent event; + TouchPadEventV1 event; while (xQueueReceive(this->touch_queue_, &event, 0) == pdTRUE) { // Find the corresponding sensor for (auto *child : this->children_) { @@ -228,7 +234,7 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { bool is_touched = value < child->get_threshold(); // Always send the current state - the main loop will filter for changes - TouchPadEvent event; + TouchPadEventV1 event; event.pad = pad; event.value = value; event.is_touched = is_touched; diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index c570bcd8f6..8aa40f1c6a 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -20,6 +20,12 @@ namespace esp32_touch { static const char *const TAG = "esp32_touch"; +struct TouchPadEventV2 { + touch_pad_t pad; + uint32_t intr_mask; + uint32_t pad_status; +}; + void ESP32TouchComponent::setup() { // Add a delay to allow serial connection, but feed the watchdog ESP_LOGCONFIG(TAG, "Waiting 5 seconds before touch sensor setup..."); @@ -36,7 +42,7 @@ void ESP32TouchComponent::setup() { if (queue_size < 8) queue_size = 8; - this->touch_queue_ = xQueueCreate(queue_size, sizeof(TouchPadEvent)); + this->touch_queue_ = xQueueCreate(queue_size, sizeof(TouchPadEventV2)); if (this->touch_queue_ == nullptr) { ESP_LOGE(TAG, "Failed to create touch event queue of size %d", queue_size); this->mark_failed(); @@ -257,7 +263,7 @@ void ESP32TouchComponent::loop() { const uint32_t now = App.get_loop_component_start_time(); // Process any queued touch events from interrupts - TouchPadEvent event; + TouchPadEventV2 event; while (xQueueReceive(this->touch_queue_, &event, 0) == pdTRUE) { // Handle timeout events if (event.intr_mask & TOUCH_PAD_INTR_MASK_TIMEOUT) { @@ -350,7 +356,7 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; // Read interrupt status and pad status - TouchPadEvent event; + TouchPadEventV2 event; event.intr_mask = touch_pad_read_intr_status_mask(); event.pad_status = touch_pad_get_status(); event.pad = touch_pad_get_current_meas_channel(); From 90c09a7650d0a4465b27fdd8e5bc6b50f1239393 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 13:29:12 -0500 Subject: [PATCH 19/51] split --- esphome/components/esp32_touch/esp32_touch.h | 93 +++++++++++-------- .../esp32_touch/esp32_touch_common.cpp | 7 +- .../components/esp32_touch/esp32_touch_v2.cpp | 28 +++++- 3 files changed, 82 insertions(+), 46 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index ba05cdcebb..6da2defe7d 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -35,6 +35,14 @@ class ESP32TouchComponent : public Component { void set_voltage_attenuation(touch_volt_atten_t voltage_attenuation) { this->voltage_attenuation_ = voltage_attenuation; } + + void setup() override; + void dump_config() override; + void loop() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + void on_shutdown() override; + #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) void set_filter_mode(touch_filter_mode_t filter_mode) { this->filter_mode_ = filter_mode; } void set_debounce_count(uint32_t debounce_count) { this->debounce_count_ = debounce_count; } @@ -51,25 +59,57 @@ class ESP32TouchComponent : public Component { void set_iir_filter(uint32_t iir_filter) { this->iir_filter_ = iir_filter; } #endif - void setup() override; - void dump_config() override; - void loop() override; - float get_setup_priority() const override { return setup_priority::DATA; } - - void on_shutdown() override; - protected: - static void touch_isr_handler(void *arg); - - // Common helper methods used by both v1 and v2 + // Common helper methods void dump_config_base_(); void dump_config_sensors_(); + // Common members + std::vector children_; + bool setup_mode_{false}; + uint32_t setup_mode_last_log_print_{0}; + + // Common configuration parameters + uint16_t sleep_cycle_{4095}; + uint16_t meas_cycle_{65535}; + touch_low_volt_t low_voltage_reference_{TOUCH_LVOLT_0V5}; + touch_high_volt_t high_voltage_reference_{TOUCH_HVOLT_2V7}; + touch_volt_atten_t voltage_attenuation_{TOUCH_HVOLT_ATTEN_0V}; + + // ==================== PLATFORM SPECIFIC ==================== + +#ifdef USE_ESP32_VARIANT_ESP32 + // ESP32 v1 specific + static void touch_isr_handler(void *arg); QueueHandle_t touch_queue_{nullptr}; - uint32_t last_touch_time_[TOUCH_PAD_MAX] = {0}; // Track last time each pad was seen as touched - uint32_t release_timeout_ms_{1500}; // Calculated timeout for release detection - uint32_t release_check_interval_ms_{50}; // How often to check for releases -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + uint32_t last_touch_time_[TOUCH_PAD_MAX] = {0}; + uint32_t release_timeout_ms_{1500}; + uint32_t release_check_interval_ms_{50}; + uint32_t iir_filter_{0}; + + bool iir_filter_enabled_() const { return this->iir_filter_ > 0; } + +#elif defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + // ESP32-S2/S3 v2 specific + static void touch_isr_handler(void *arg); + QueueHandle_t touch_queue_{nullptr}; + bool initial_state_read_{false}; + + // Filter configuration + touch_filter_mode_t filter_mode_{TOUCH_PAD_FILTER_MAX}; + uint32_t debounce_count_{0}; + uint32_t noise_threshold_{0}; + uint32_t jitter_step_{0}; + touch_smooth_mode_t smooth_level_{TOUCH_PAD_SMOOTH_MAX}; + + // Denoise configuration + touch_pad_denoise_grade_t grade_{TOUCH_PAD_DENOISE_MAX}; + touch_pad_denoise_cap_t cap_level_{TOUCH_PAD_DENOISE_CAP_MAX}; + + // Waterproof configuration + touch_pad_t waterproof_guard_ring_pad_{TOUCH_PAD_MAX}; + touch_pad_shield_driver_t waterproof_shield_driver_{TOUCH_PAD_SHIELD_DRV_MAX}; + bool filter_configured_() const { return (this->filter_mode_ != TOUCH_PAD_FILTER_MAX) && (this->smooth_level_ != TOUCH_PAD_SMOOTH_MAX); } @@ -80,8 +120,6 @@ class ESP32TouchComponent : public Component { return (this->waterproof_guard_ring_pad_ != TOUCH_PAD_MAX) && (this->waterproof_shield_driver_ != TOUCH_PAD_SHIELD_DRV_MAX); } -#else - bool iir_filter_enabled_() const { return this->iir_filter_ > 0; } #endif // Helper functions for dump_config - common to both implementations @@ -129,29 +167,6 @@ class ESP32TouchComponent : public Component { return "UNKNOWN"; } } - - std::vector children_; - bool setup_mode_{false}; - uint32_t setup_mode_last_log_print_{0}; - // common parameters - uint16_t sleep_cycle_{4095}; - uint16_t meas_cycle_{65535}; - touch_low_volt_t low_voltage_reference_{TOUCH_LVOLT_0V5}; - touch_high_volt_t high_voltage_reference_{TOUCH_HVOLT_2V7}; - touch_volt_atten_t voltage_attenuation_{TOUCH_HVOLT_ATTEN_0V}; -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - touch_filter_mode_t filter_mode_{TOUCH_PAD_FILTER_MAX}; - uint32_t debounce_count_{0}; - uint32_t noise_threshold_{0}; - uint32_t jitter_step_{0}; - touch_smooth_mode_t smooth_level_{TOUCH_PAD_SMOOTH_MAX}; - touch_pad_denoise_grade_t grade_{TOUCH_PAD_DENOISE_MAX}; - touch_pad_denoise_cap_t cap_level_{TOUCH_PAD_DENOISE_CAP_MAX}; - touch_pad_t waterproof_guard_ring_pad_{TOUCH_PAD_MAX}; - touch_pad_shield_driver_t waterproof_shield_driver_{TOUCH_PAD_SHIELD_DRV_MAX}; -#else - uint32_t iir_filter_{0}; -#endif }; /// Simple helper class to expose a touch pad value as a binary sensor. diff --git a/esphome/components/esp32_touch/esp32_touch_common.cpp b/esphome/components/esp32_touch/esp32_touch_common.cpp index 1ad195dd8f..cb9b2e79e1 100644 --- a/esphome/components/esp32_touch/esp32_touch_common.cpp +++ b/esphome/components/esp32_touch/esp32_touch_common.cpp @@ -20,12 +20,9 @@ void ESP32TouchComponent::dump_config_base_() { " Sleep cycle: %.2fms\n" " Low Voltage Reference: %s\n" " High Voltage Reference: %s\n" - " Voltage Attenuation: %s\n" - " ISR Configuration:\n" - " Release timeout: %" PRIu32 "ms\n" - " Release check interval: %" PRIu32 "ms", + " Voltage Attenuation: %s", this->meas_cycle_ / (8000000.0f / 1000.0f), this->sleep_cycle_ / (150000.0f / 1000.0f), lv_s, hv_s, - atten_s, this->release_timeout_ms_, this->release_check_interval_ms_); + atten_s); } void ESP32TouchComponent::dump_config_sensors_() { diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 8aa40f1c6a..05d224fdf5 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -262,6 +262,30 @@ void ESP32TouchComponent::dump_config() { void ESP32TouchComponent::loop() { const uint32_t now = App.get_loop_component_start_time(); + // Read initial states if not done yet + if (!this->initial_state_read_) { + this->initial_state_read_ = true; + for (auto *child : this->children_) { + // Read current value + uint32_t value = 0; + if (this->filter_configured_()) { + touch_pad_filter_read_smooth(child->get_touch_pad(), &value); + } else { + touch_pad_read_benchmark(child->get_touch_pad(), &value); + } + + child->value_ = value; + + // For S2/S3 v2, higher value means touched (opposite of v1) + bool is_touched = value > child->get_threshold(); + child->last_state_ = is_touched; + child->publish_state(is_touched); + + ESP_LOGD(TAG, "Touch Pad '%s' initial state: %s (value: %d, threshold: %d)", child->get_name().c_str(), + is_touched ? "touched" : "released", value, child->get_threshold()); + } + } + // Process any queued touch events from interrupts TouchPadEventV2 event; while (xQueueReceive(this->touch_queue_, &event, 0) == pdTRUE) { @@ -363,8 +387,8 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { // Debug logging from ISR (using ROM functions for ISR safety) - only log non-timeout events for now // if (event.intr_mask != 0x10 || event.pad_status != 0) { - ets_printf("ISR: intr=0x%x, status=0x%x, pad=%d\n", event.intr_mask, event.pad_status, event.pad); - //} + // ets_printf("ISR: intr=0x%x, status=0x%x, pad=%d\n", event.intr_mask, event.pad_status, event.pad); + // } // Send event to queue for processing in main loop xQueueSendFromISR(component->touch_queue_, &event, &xHigherPriorityTaskWoken); From eae0d90a1efe6476e3db2b0ecfbe4b9a96b8c347 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 15:41:41 -0500 Subject: [PATCH 20/51] adjust --- esphome/components/esp32_touch/esp32_touch_v2.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 05d224fdf5..fdf02932ae 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -8,10 +8,6 @@ #include #include -// Include HAL for ISR-safe touch reading -#include "hal/touch_sensor_ll.h" -// Include for RTC clock frequency -#include "soc/rtc.h" // Include for ISR-safe printing #include "rom/ets_sys.h" @@ -102,8 +98,8 @@ void ESP32TouchComponent::setup() { // Configure measurement parameters touch_pad_set_voltage(this->high_voltage_reference_, this->low_voltage_reference_, this->voltage_attenuation_); - touch_pad_set_charge_discharge_times(this->meas_cycle_); - touch_pad_set_measurement_interval(this->sleep_cycle_); + // ESP32-S2/S3 always use the older API + touch_pad_set_meas_time(this->sleep_cycle_, this->meas_cycle_); // Configure timeout if needed touch_pad_timeout_set(true, TOUCH_PAD_THRESHOLD_MAX); From 9b0d01e03f941943453bfb98344453e83e6a5e0d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 15:45:47 -0500 Subject: [PATCH 21/51] cleanup --- esphome/components/esp32_touch/esp32_touch.h | 4 ++++ esphome/components/esp32_touch/esp32_touch_v2.cpp | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 6da2defe7d..48d962b881 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -178,7 +178,9 @@ class ESP32TouchBinarySensor : public binary_sensor::BinarySensor { touch_pad_t get_touch_pad() const { return this->touch_pad_; } uint32_t get_threshold() const { return this->threshold_; } void set_threshold(uint32_t threshold) { this->threshold_ = threshold; } +#ifdef USE_ESP32_VARIANT_ESP32 uint32_t get_value() const { return this->value_; } +#endif uint32_t get_wakeup_threshold() const { return this->wakeup_threshold_; } protected: @@ -186,7 +188,9 @@ class ESP32TouchBinarySensor : public binary_sensor::BinarySensor { touch_pad_t touch_pad_{TOUCH_PAD_MAX}; uint32_t threshold_{0}; +#ifdef USE_ESP32_VARIANT_ESP32 uint32_t value_{0}; +#endif bool last_state_{false}; const uint32_t wakeup_threshold_{0}; }; diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index fdf02932ae..3bd2a6c937 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -270,8 +270,6 @@ void ESP32TouchComponent::loop() { touch_pad_read_benchmark(child->get_touch_pad(), &value); } - child->value_ = value; - // For S2/S3 v2, higher value means touched (opposite of v1) bool is_touched = value > child->get_threshold(); child->last_state_ = is_touched; @@ -307,8 +305,6 @@ void ESP32TouchComponent::loop() { touch_pad_read_benchmark(event.pad, &value); } - child->value_ = value; - // Update state if changed if (child->last_state_ != is_touch_event) { child->last_state_ = is_touch_event; From e83f4ae97435477b887a5266368102fb1f94ab28 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 15:46:56 -0500 Subject: [PATCH 22/51] cleanup --- esphome/components/esp32_touch/esp32_touch_v2.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 3bd2a6c937..b03fa53df5 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -296,7 +296,7 @@ void ESP32TouchComponent::loop() { // Find the child for the pad that triggered the interrupt for (auto *child : this->children_) { - if (child->get_touch_pad() == event.pad) { + if (child->get_touch_pad() == event.pad an && d child->last_state_ != is_touch_event) { // Read current value uint32_t value = 0; if (this->filter_configured_()) { @@ -305,13 +305,10 @@ void ESP32TouchComponent::loop() { touch_pad_read_benchmark(event.pad, &value); } - // Update state if changed - if (child->last_state_ != is_touch_event) { - child->last_state_ = is_touch_event; - child->publish_state(is_touch_event); - ESP_LOGD(TAG, "Touch Pad '%s' %s (value: %d, threshold: %d)", child->get_name().c_str(), - is_touch_event ? "touched" : "released", value, child->get_threshold()); - } + child->last_state_ = is_touch_event; + child->publish_state(is_touch_event); + ESP_LOGD(TAG, "Touch Pad '%s' %s (value: %d, threshold: %d)", child->get_name().c_str(), + is_touch_event ? "touched" : "released", value, child->get_threshold()); break; } } From bbf7d32676017ef1920d97a5df50d362676e3e66 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 15:47:31 -0500 Subject: [PATCH 23/51] cleanup --- .../components/esp32_touch/esp32_touch_v2.cpp | 64 ++++++++++--------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index b03fa53df5..c8bff966ee 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -296,46 +296,48 @@ void ESP32TouchComponent::loop() { // Find the child for the pad that triggered the interrupt for (auto *child : this->children_) { - if (child->get_touch_pad() == event.pad an && d child->last_state_ != is_touch_event) { - // Read current value - uint32_t value = 0; - if (this->filter_configured_()) { - touch_pad_filter_read_smooth(event.pad, &value); - } else { - touch_pad_read_benchmark(event.pad, &value); + if (child->get_touch_pad() == event.pad) + if (child->last_state_ != is_touch_event) { + // Read current value + uint32_t value = 0; + if (this->filter_configured_()) { + touch_pad_filter_read_smooth(event.pad, &value); + } else { + touch_pad_read_benchmark(event.pad, &value); + } + + child->last_state_ = is_touch_event; + child->publish_state(is_touch_event); + ESP_LOGD(TAG, "Touch Pad '%s' %s (value: %d, threshold: %d)", child->get_name().c_str(), + is_touch_event ? "touched" : "released", value, child->get_threshold()); } - - child->last_state_ = is_touch_event; - child->publish_state(is_touch_event); - ESP_LOGD(TAG, "Touch Pad '%s' %s (value: %d, threshold: %d)", child->get_name().c_str(), - is_touch_event ? "touched" : "released", value, child->get_threshold()); - break; - } + break; } } } +} - // In setup mode, periodically log all pad values - if (this->setup_mode_ && now - this->setup_mode_last_log_print_ > SETUP_MODE_LOG_INTERVAL_MS) { - for (auto *child : this->children_) { - uint32_t raw = 0; - uint32_t benchmark = 0; - uint32_t smooth = 0; +// In setup mode, periodically log all pad values +if (this->setup_mode_ && now - this->setup_mode_last_log_print_ > SETUP_MODE_LOG_INTERVAL_MS) { + for (auto *child : this->children_) { + uint32_t raw = 0; + uint32_t benchmark = 0; + uint32_t smooth = 0; - touch_pad_read_raw_data(child->get_touch_pad(), &raw); - touch_pad_read_benchmark(child->get_touch_pad(), &benchmark); + touch_pad_read_raw_data(child->get_touch_pad(), &raw); + touch_pad_read_benchmark(child->get_touch_pad(), &benchmark); - if (this->filter_configured_()) { - touch_pad_filter_read_smooth(child->get_touch_pad(), &smooth); - ESP_LOGD(TAG, " Pad T%d: raw=%d, benchmark=%d, smooth=%d, threshold=%d", child->get_touch_pad(), raw, - benchmark, smooth, child->get_threshold()); - } else { - ESP_LOGD(TAG, " Pad T%d: raw=%d, benchmark=%d, threshold=%d", child->get_touch_pad(), raw, benchmark, - child->get_threshold()); - } + if (this->filter_configured_()) { + touch_pad_filter_read_smooth(child->get_touch_pad(), &smooth); + ESP_LOGD(TAG, " Pad T%d: raw=%d, benchmark=%d, smooth=%d, threshold=%d", child->get_touch_pad(), raw, benchmark, + smooth, child->get_threshold()); + } else { + ESP_LOGD(TAG, " Pad T%d: raw=%d, benchmark=%d, threshold=%d", child->get_touch_pad(), raw, benchmark, + child->get_threshold()); } - this->setup_mode_last_log_print_ = now; } + this->setup_mode_last_log_print_ = now; +} } void ESP32TouchComponent::on_shutdown() { From 0545b9c7f2cef602d2b04d22963c896e84c29db9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 15:48:00 -0500 Subject: [PATCH 24/51] cleanup --- esphome/components/esp32_touch/esp32_touch_v2.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index c8bff966ee..584456fe4a 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -296,7 +296,7 @@ void ESP32TouchComponent::loop() { // Find the child for the pad that triggered the interrupt for (auto *child : this->children_) { - if (child->get_touch_pad() == event.pad) + if (child->get_touch_pad() == event.pad) { if (child->last_state_ != is_touch_event) { // Read current value uint32_t value = 0; @@ -311,7 +311,8 @@ void ESP32TouchComponent::loop() { ESP_LOGD(TAG, "Touch Pad '%s' %s (value: %d, threshold: %d)", child->get_name().c_str(), is_touch_event ? "touched" : "released", value, child->get_threshold()); } - break; + break; + } } } } From 08a74890da8066344cf0179a86abf4a81231b18a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 15:48:29 -0500 Subject: [PATCH 25/51] cleanup --- esphome/components/esp32_touch/esp32_touch_v2.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 584456fe4a..29f54ed378 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -339,7 +339,6 @@ if (this->setup_mode_ && now - this->setup_mode_last_log_print_ > SETUP_MODE_LOG } this->setup_mode_last_log_print_ = now; } -} void ESP32TouchComponent::on_shutdown() { // Disable interrupts From 5d5e346199682b92dd2aa2745482899df448041a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 15:50:21 -0500 Subject: [PATCH 26/51] cleanup --- .../components/esp32_touch/esp32_touch_v2.cpp | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 29f54ed378..b4181d2db6 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -290,30 +290,37 @@ void ESP32TouchComponent::loop() { continue; } - // Handle active/inactive events - if (event.intr_mask & (TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE)) { - bool is_touch_event = (event.intr_mask & TOUCH_PAD_INTR_MASK_ACTIVE) != 0; + // Skip if not an active/inactive event + if (!(event.intr_mask & (TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE))) { + continue; + } - // Find the child for the pad that triggered the interrupt - for (auto *child : this->children_) { - if (child->get_touch_pad() == event.pad) { - if (child->last_state_ != is_touch_event) { - // Read current value - uint32_t value = 0; - if (this->filter_configured_()) { - touch_pad_filter_read_smooth(event.pad, &value); - } else { - touch_pad_read_benchmark(event.pad, &value); - } + bool is_touch_event = (event.intr_mask & TOUCH_PAD_INTR_MASK_ACTIVE) != 0; - child->last_state_ = is_touch_event; - child->publish_state(is_touch_event); - ESP_LOGD(TAG, "Touch Pad '%s' %s (value: %d, threshold: %d)", child->get_name().c_str(), - is_touch_event ? "touched" : "released", value, child->get_threshold()); - } - break; - } + // Find the child for the pad that triggered the interrupt + for (auto *child : this->children_) { + if (child->get_touch_pad() != event.pad) { + continue; } + + // Skip if state hasn't changed + if (child->last_state_ == is_touch_event) { + break; + } + + // Read current value + uint32_t value = 0; + if (this->filter_configured_()) { + touch_pad_filter_read_smooth(event.pad, &value); + } else { + touch_pad_read_benchmark(event.pad, &value); + } + + child->last_state_ = is_touch_event; + child->publish_state(is_touch_event); + ESP_LOGD(TAG, "Touch Pad '%s' %s (value: %d, threshold: %d)", child->get_name().c_str(), + is_touch_event ? "touched" : "released", value, child->get_threshold()); + break; } } } From efb2e5e7a821d67c62872effe3eb0183d1ca5645 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 15:52:38 -0500 Subject: [PATCH 27/51] cleanup --- esphome/components/esp32_touch/esp32_touch_v2.cpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index b4181d2db6..344ec109fe 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -23,16 +23,6 @@ struct TouchPadEventV2 { }; void ESP32TouchComponent::setup() { - // Add a delay to allow serial connection, but feed the watchdog - ESP_LOGCONFIG(TAG, "Waiting 5 seconds before touch sensor setup..."); - for (int i = 0; i < 50; i++) { - vTaskDelay(100 / portTICK_PERIOD_MS); - App.feed_wdt(); - } - - ESP_LOGCONFIG(TAG, "=== ESP32 Touch Sensor v2 Setup Starting ==="); - ESP_LOGCONFIG(TAG, "Configuring %d touch pads", this->children_.size()); - // Create queue for touch events first size_t queue_size = this->children_.size() * 4; if (queue_size < 8) @@ -46,7 +36,6 @@ void ESP32TouchComponent::setup() { } // Initialize touch pad peripheral - ESP_LOGD(TAG, "Initializing touch pad peripheral..."); esp_err_t init_err = touch_pad_init(); if (init_err != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize touch pad: %s", esp_err_to_name(init_err)); @@ -55,13 +44,10 @@ void ESP32TouchComponent::setup() { } // Configure each touch pad first - ESP_LOGD(TAG, "Configuring individual touch pads..."); for (auto *child : this->children_) { esp_err_t config_err = touch_pad_config(child->get_touch_pad()); if (config_err != ESP_OK) { ESP_LOGE(TAG, "Failed to configure touch pad %d: %s", child->get_touch_pad(), esp_err_to_name(config_err)); - } else { - ESP_LOGD(TAG, "Configured touch pad %d", child->get_touch_pad()); } } From 5d765413ef0a62fef1f8f1dbcd82ef99bcf7ce20 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 15:53:42 -0500 Subject: [PATCH 28/51] cleanup --- .../components/esp32_touch/esp32_touch_v2.cpp | 115 +++++++++--------- 1 file changed, 57 insertions(+), 58 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 344ec109fe..a502e95991 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -309,78 +309,77 @@ void ESP32TouchComponent::loop() { break; } } -} -// In setup mode, periodically log all pad values -if (this->setup_mode_ && now - this->setup_mode_last_log_print_ > SETUP_MODE_LOG_INTERVAL_MS) { - for (auto *child : this->children_) { - uint32_t raw = 0; - uint32_t benchmark = 0; - uint32_t smooth = 0; + // In setup mode, periodically log all pad values + if (this->setup_mode_ && now - this->setup_mode_last_log_print_ > SETUP_MODE_LOG_INTERVAL_MS) { + for (auto *child : this->children_) { + uint32_t raw = 0; + uint32_t benchmark = 0; + uint32_t smooth = 0; - touch_pad_read_raw_data(child->get_touch_pad(), &raw); - touch_pad_read_benchmark(child->get_touch_pad(), &benchmark); + touch_pad_read_raw_data(child->get_touch_pad(), &raw); + touch_pad_read_benchmark(child->get_touch_pad(), &benchmark); - if (this->filter_configured_()) { - touch_pad_filter_read_smooth(child->get_touch_pad(), &smooth); - ESP_LOGD(TAG, " Pad T%d: raw=%d, benchmark=%d, smooth=%d, threshold=%d", child->get_touch_pad(), raw, benchmark, - smooth, child->get_threshold()); - } else { - ESP_LOGD(TAG, " Pad T%d: raw=%d, benchmark=%d, threshold=%d", child->get_touch_pad(), raw, benchmark, - child->get_threshold()); - } - } - this->setup_mode_last_log_print_ = now; -} - -void ESP32TouchComponent::on_shutdown() { - // Disable interrupts - touch_pad_intr_disable(static_cast(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE | - TOUCH_PAD_INTR_MASK_TIMEOUT)); - touch_pad_isr_deregister(touch_isr_handler, this); - if (this->touch_queue_) { - vQueueDelete(this->touch_queue_); - } - - // Check if any pad is configured for wakeup - bool is_wakeup_source = false; - for (auto *child : this->children_) { - if (child->get_wakeup_threshold() != 0) { - if (!is_wakeup_source) { - is_wakeup_source = true; - // Touch sensor FSM mode must be 'TOUCH_FSM_MODE_TIMER' to use it to wake-up. - touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); + if (this->filter_configured_()) { + touch_pad_filter_read_smooth(child->get_touch_pad(), &smooth); + ESP_LOGD(TAG, " Pad T%d: raw=%d, benchmark=%d, smooth=%d, threshold=%d", child->get_touch_pad(), raw, + benchmark, smooth, child->get_threshold()); + } else { + ESP_LOGD(TAG, " Pad T%d: raw=%d, benchmark=%d, threshold=%d", child->get_touch_pad(), raw, benchmark, + child->get_threshold()); } } + this->setup_mode_last_log_print_ = now; } - if (!is_wakeup_source) { - touch_pad_deinit(); + void ESP32TouchComponent::on_shutdown() { + // Disable interrupts + touch_pad_intr_disable(static_cast( + TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE | TOUCH_PAD_INTR_MASK_TIMEOUT)); + touch_pad_isr_deregister(touch_isr_handler, this); + if (this->touch_queue_) { + vQueueDelete(this->touch_queue_); + } + + // Check if any pad is configured for wakeup + bool is_wakeup_source = false; + for (auto *child : this->children_) { + if (child->get_wakeup_threshold() != 0) { + if (!is_wakeup_source) { + is_wakeup_source = true; + // Touch sensor FSM mode must be 'TOUCH_FSM_MODE_TIMER' to use it to wake-up. + touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); + } + } + } + + if (!is_wakeup_source) { + touch_pad_deinit(); + } } -} -void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { - ESP32TouchComponent *component = static_cast(arg); - BaseType_t xHigherPriorityTaskWoken = pdFALSE; + void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { + ESP32TouchComponent *component = static_cast(arg); + BaseType_t xHigherPriorityTaskWoken = pdFALSE; - // Read interrupt status and pad status - TouchPadEventV2 event; - event.intr_mask = touch_pad_read_intr_status_mask(); - event.pad_status = touch_pad_get_status(); - event.pad = touch_pad_get_current_meas_channel(); + // Read interrupt status and pad status + TouchPadEventV2 event; + event.intr_mask = touch_pad_read_intr_status_mask(); + event.pad_status = touch_pad_get_status(); + event.pad = touch_pad_get_current_meas_channel(); - // Debug logging from ISR (using ROM functions for ISR safety) - only log non-timeout events for now - // if (event.intr_mask != 0x10 || event.pad_status != 0) { - // ets_printf("ISR: intr=0x%x, status=0x%x, pad=%d\n", event.intr_mask, event.pad_status, event.pad); - // } + // Debug logging from ISR (using ROM functions for ISR safety) - only log non-timeout events for now + // if (event.intr_mask != 0x10 || event.pad_status != 0) { + // ets_printf("ISR: intr=0x%x, status=0x%x, pad=%d\n", event.intr_mask, event.pad_status, event.pad); + // } - // Send event to queue for processing in main loop - xQueueSendFromISR(component->touch_queue_, &event, &xHigherPriorityTaskWoken); + // Send event to queue for processing in main loop + xQueueSendFromISR(component->touch_queue_, &event, &xHigherPriorityTaskWoken); - if (xHigherPriorityTaskWoken) { - portYIELD_FROM_ISR(); + if (xHigherPriorityTaskWoken) { + portYIELD_FROM_ISR(); + } } -} } // namespace esp32_touch } // namespace esphome From bcb6b8533394ec1eb51550f72d9afeaf4115faf8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 15:54:15 -0500 Subject: [PATCH 29/51] cleanup --- .../components/esp32_touch/esp32_touch_v2.cpp | 85 ++++++++++--------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index a502e95991..9d6f222f65 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -331,55 +331,56 @@ void ESP32TouchComponent::loop() { } this->setup_mode_last_log_print_ = now; } +} - void ESP32TouchComponent::on_shutdown() { - // Disable interrupts - touch_pad_intr_disable(static_cast( - TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE | TOUCH_PAD_INTR_MASK_TIMEOUT)); - touch_pad_isr_deregister(touch_isr_handler, this); - if (this->touch_queue_) { - vQueueDelete(this->touch_queue_); - } +void ESP32TouchComponent::on_shutdown() { + // Disable interrupts + touch_pad_intr_disable(static_cast(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE | + TOUCH_PAD_INTR_MASK_TIMEOUT)); + touch_pad_isr_deregister(touch_isr_handler, this); + if (this->touch_queue_) { + vQueueDelete(this->touch_queue_); + } - // Check if any pad is configured for wakeup - bool is_wakeup_source = false; - for (auto *child : this->children_) { - if (child->get_wakeup_threshold() != 0) { - if (!is_wakeup_source) { - is_wakeup_source = true; - // Touch sensor FSM mode must be 'TOUCH_FSM_MODE_TIMER' to use it to wake-up. - touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); - } + // Check if any pad is configured for wakeup + bool is_wakeup_source = false; + for (auto *child : this->children_) { + if (child->get_wakeup_threshold() != 0) { + if (!is_wakeup_source) { + is_wakeup_source = true; + // Touch sensor FSM mode must be 'TOUCH_FSM_MODE_TIMER' to use it to wake-up. + touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); } } - - if (!is_wakeup_source) { - touch_pad_deinit(); - } } - void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { - ESP32TouchComponent *component = static_cast(arg); - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - - // Read interrupt status and pad status - TouchPadEventV2 event; - event.intr_mask = touch_pad_read_intr_status_mask(); - event.pad_status = touch_pad_get_status(); - event.pad = touch_pad_get_current_meas_channel(); - - // Debug logging from ISR (using ROM functions for ISR safety) - only log non-timeout events for now - // if (event.intr_mask != 0x10 || event.pad_status != 0) { - // ets_printf("ISR: intr=0x%x, status=0x%x, pad=%d\n", event.intr_mask, event.pad_status, event.pad); - // } - - // Send event to queue for processing in main loop - xQueueSendFromISR(component->touch_queue_, &event, &xHigherPriorityTaskWoken); - - if (xHigherPriorityTaskWoken) { - portYIELD_FROM_ISR(); - } + if (!is_wakeup_source) { + touch_pad_deinit(); } +} + +void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { + ESP32TouchComponent *component = static_cast(arg); + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + + // Read interrupt status and pad status + TouchPadEventV2 event; + event.intr_mask = touch_pad_read_intr_status_mask(); + event.pad_status = touch_pad_get_status(); + event.pad = touch_pad_get_current_meas_channel(); + + // Debug logging from ISR (using ROM functions for ISR safety) - only log non-timeout events for now + // if (event.intr_mask != 0x10 || event.pad_status != 0) { + // ets_printf("ISR: intr=0x%x, status=0x%x, pad=%d\n", event.intr_mask, event.pad_status, event.pad); + // } + + // Send event to queue for processing in main loop + xQueueSendFromISR(component->touch_queue_, &event, &xHigherPriorityTaskWoken); + + if (xHigherPriorityTaskWoken) { + portYIELD_FROM_ISR(); + } +} } // namespace esp32_touch } // namespace esphome From 5719d334aa203eddae5efffecb29cb2db6dc2b40 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 15:56:04 -0500 Subject: [PATCH 30/51] cleanup --- esphome/components/esp32_touch/esp32_touch_v2.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 9d6f222f65..925fa15203 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -369,11 +369,6 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { event.pad_status = touch_pad_get_status(); event.pad = touch_pad_get_current_meas_channel(); - // Debug logging from ISR (using ROM functions for ISR safety) - only log non-timeout events for now - // if (event.intr_mask != 0x10 || event.pad_status != 0) { - // ets_printf("ISR: intr=0x%x, status=0x%x, pad=%d\n", event.intr_mask, event.pad_status, event.pad); - // } - // Send event to queue for processing in main loop xQueueSendFromISR(component->touch_queue_, &event, &xHigherPriorityTaskWoken); From e72e0d064629df81788b4828617d1ecec34dbc68 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 15:56:19 -0500 Subject: [PATCH 31/51] cleanup --- esphome/components/esp32_touch/esp32_touch_v2.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 925fa15203..8f49fb61ab 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -8,9 +8,6 @@ #include #include -// Include for ISR-safe printing -#include "rom/ets_sys.h" - namespace esphome { namespace esp32_touch { From f1c56b7254e5bc6400fee32cffceae2046b351e1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 15:56:32 -0500 Subject: [PATCH 32/51] cleanup --- esphome/components/esp32_touch/esp32_touch_v2.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 8f49fb61ab..d17da43069 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -5,9 +5,6 @@ #include "esphome/core/log.h" #include "esphome/core/hal.h" -#include -#include - namespace esphome { namespace esp32_touch { From 1e12614f9a818ed627747f3f0054409dec29fd28 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 16:14:37 -0500 Subject: [PATCH 33/51] cleanup --- esphome/components/esp32_touch/esp32_touch_v1.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index 0ee7990a94..e81c7bbab0 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -207,7 +207,6 @@ void ESP32TouchComponent::on_shutdown() { void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { ESP32TouchComponent *component = static_cast(arg); - uint32_t pad_status = touch_pad_get_status(); touch_pad_clear_status(); // Process all configured pads to check their current state From 73b40dd2e73ff6a363edacd1545e82f34121f219 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 16:19:15 -0500 Subject: [PATCH 34/51] cleanup --- esphome/components/esp32_touch/esp32_touch_v1.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index e81c7bbab0..d12722c87f 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -239,9 +239,9 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { event.is_touched = is_touched; // Send to queue from ISR - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - xQueueSendFromISR(component->touch_queue_, &event, &xHigherPriorityTaskWoken); - if (xHigherPriorityTaskWoken) { + BaseType_t x_higher_priority_task_woken = pdFALSE; + xQueueSendFromISR(component->touch_queue_, &event, &x_higher_priority_task_woken); + if (x_higher_priority_task_woken) { portYIELD_FROM_ISR(); } } From 3adcae783c8a326c565ea1fd55b2930c89eb25f4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 16:19:27 -0500 Subject: [PATCH 35/51] cleanup --- esphome/components/esp32_touch/esp32_touch_v2.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index d17da43069..e0122202e9 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -355,7 +355,7 @@ void ESP32TouchComponent::on_shutdown() { void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { ESP32TouchComponent *component = static_cast(arg); - BaseType_t xHigherPriorityTaskWoken = pdFALSE; + BaseType_t x_higher_priority_task_woken = pdFALSE; // Read interrupt status and pad status TouchPadEventV2 event; @@ -364,9 +364,9 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { event.pad = touch_pad_get_current_meas_channel(); // Send event to queue for processing in main loop - xQueueSendFromISR(component->touch_queue_, &event, &xHigherPriorityTaskWoken); + xQueueSendFromISR(component->touch_queue_, &event, &x_higher_priority_task_woken); - if (xHigherPriorityTaskWoken) { + if (x_higher_priority_task_woken) { portYIELD_FROM_ISR(); } } From f7afcb3b2489fc757dd6b96e20964aa503a9bd46 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 16:30:41 -0500 Subject: [PATCH 36/51] cleanup --- esphome/components/esp32_touch/esp32_touch.h | 7 +++++++ esphome/components/esp32_touch/esp32_touch_v1.cpp | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 48d962b881..3c512e2de6 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -82,6 +82,13 @@ class ESP32TouchComponent : public Component { // ESP32 v1 specific static void touch_isr_handler(void *arg); QueueHandle_t touch_queue_{nullptr}; + + // Design note: last_touch_time_ does not require synchronization primitives because: + // 1. ESP32 guarantees atomic 32-bit aligned reads/writes + // 2. ISR only writes timestamps, main loop only reads (except sentinel value 1) + // 3. Timing tolerance allows for occasional stale reads (50ms check interval) + // 4. Queue operations provide implicit memory barriers + // Using atomic/critical sections would add overhead without meaningful benefit uint32_t last_touch_time_[TOUCH_PAD_MAX] = {0}; uint32_t release_timeout_ms_{1500}; uint32_t release_check_interval_ms_{50}; diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index d12722c87f..16fe677f22 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -72,6 +72,9 @@ void ESP32TouchComponent::setup() { } // Calculate release timeout based on sleep cycle + // Design note: ESP32 v1 hardware limitation - interrupts only fire on touch (not release) + // We must use timeout-based detection for release events + // Formula: 3 sleep cycles converted to ms, with 100ms minimum uint32_t rtc_freq = rtc_clk_slow_freq_get_hz(); this->release_timeout_ms_ = (this->sleep_cycle_ * 1000 * 3) / (rtc_freq * 2); if (this->release_timeout_ms_ < 100) { @@ -151,6 +154,12 @@ void ESP32TouchComponent::loop() { touch_pad_t pad = child->get_touch_pad(); uint32_t last_time = this->last_touch_time_[pad]; + // Design note: Sentinel value pattern explanation + // - 0: Never touched since boot (waiting for initial timeout) + // - 1: Initial OFF state has been published (prevents repeated publishes) + // - >1: Actual timestamp of last touch event + // This avoids needing a separate boolean flag for initial state tracking + // If we've never seen this pad touched (last_time == 0) and enough time has passed // since startup, publish OFF state and mark as published with value 1 if (last_time == 0 && now > this->release_timeout_ms_) { From a18374e1ad595dd0cbfdcaf13b5f9dcf660d9f17 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 16:33:15 -0500 Subject: [PATCH 37/51] cleanup --- esphome/components/esp32_touch/esp32_touch.h | 2 ++ esphome/components/esp32_touch/esp32_touch_v1.cpp | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 3c512e2de6..af516efc5d 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -80,6 +80,8 @@ class ESP32TouchComponent : public Component { #ifdef USE_ESP32_VARIANT_ESP32 // ESP32 v1 specific + static constexpr uint32_t MINIMUM_RELEASE_TIME_MS = 100; + static void touch_isr_handler(void *arg); QueueHandle_t touch_queue_{nullptr}; diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index 16fe677f22..774ff0b0bf 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -74,11 +74,11 @@ void ESP32TouchComponent::setup() { // Calculate release timeout based on sleep cycle // Design note: ESP32 v1 hardware limitation - interrupts only fire on touch (not release) // We must use timeout-based detection for release events - // Formula: 3 sleep cycles converted to ms, with 100ms minimum + // Formula: 3 sleep cycles converted to ms, with MINIMUM_RELEASE_TIME_MS minimum uint32_t rtc_freq = rtc_clk_slow_freq_get_hz(); this->release_timeout_ms_ = (this->sleep_cycle_ * 1000 * 3) / (rtc_freq * 2); - if (this->release_timeout_ms_ < 100) { - this->release_timeout_ms_ = 100; + if (this->release_timeout_ms_ < MINIMUM_RELEASE_TIME_MS) { + this->release_timeout_ms_ = MINIMUM_RELEASE_TIME_MS; } this->release_check_interval_ms_ = std::min(this->release_timeout_ms_ / 4, (uint32_t) 50); From 866eaed73d62600adcb8c2a65047f538f2070d5c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 16:58:24 -0500 Subject: [PATCH 38/51] preen --- esphome/components/esp32_touch/esp32_touch.h | 8 +- .../components/esp32_touch/esp32_touch_v1.cpp | 140 +++++++++++------- 2 files changed, 96 insertions(+), 52 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index af516efc5d..29fc28cd2e 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace esphome { namespace esp32_touch { @@ -83,13 +84,16 @@ class ESP32TouchComponent : public Component { static constexpr uint32_t MINIMUM_RELEASE_TIME_MS = 100; static void touch_isr_handler(void *arg); - QueueHandle_t touch_queue_{nullptr}; + + // Ring buffer handle for FreeRTOS ring buffer + RingbufHandle_t ring_buffer_handle_{nullptr}; + uint32_t ring_buffer_overflow_count_{0}; // Design note: last_touch_time_ does not require synchronization primitives because: // 1. ESP32 guarantees atomic 32-bit aligned reads/writes // 2. ISR only writes timestamps, main loop only reads (except sentinel value 1) // 3. Timing tolerance allows for occasional stale reads (50ms check interval) - // 4. Queue operations provide implicit memory barriers + // 4. Ring buffer operations provide implicit memory barriers // Using atomic/critical sections would add overhead without meaningful benefit uint32_t last_touch_time_[TOUCH_PAD_MAX] = {0}; uint32_t release_timeout_ms_{1500}; diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index 774ff0b0bf..2f5da4df60 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -12,16 +12,19 @@ #include "hal/touch_sensor_ll.h" // Include for RTC clock frequency #include "soc/rtc.h" +// Include FreeRTOS ring buffer +#include "freertos/ringbuf.h" namespace esphome { namespace esp32_touch { static const char *const TAG = "esp32_touch"; -struct TouchPadEventV1 { - touch_pad_t pad; - uint32_t value; - bool is_touched; +// Structure for a single pad's state in the ring buffer +struct TouchPadState { + uint8_t pad; // touch_pad_t + uint32_t value; // Current reading + bool is_touched; // Touch state }; void ESP32TouchComponent::setup() { @@ -30,14 +33,19 @@ void ESP32TouchComponent::setup() { touch_pad_init(); touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); - // Create queue for touch events - size_t queue_size = this->children_.size() * 4; - if (queue_size < 8) - queue_size = 8; + // Create ring buffer for touch events + // Size calculation: We need space for multiple snapshots + // Each snapshot contains: array of TouchPadState structures + size_t pad_state_size = sizeof(TouchPadState); + size_t snapshot_size = this->children_.size() * pad_state_size; - this->touch_queue_ = xQueueCreate(queue_size, sizeof(TouchPadEventV1)); - if (this->touch_queue_ == nullptr) { - ESP_LOGE(TAG, "Failed to create touch event queue of size %d", queue_size); + // Allow for 4 snapshots in the buffer to handle normal operation and bursts + size_t buffer_size = snapshot_size * 4; + + // Create a byte buffer ring buffer (allows variable sized items) + this->ring_buffer_handle_ = xRingbufferCreate(buffer_size, RINGBUF_TYPE_BYTEBUF); + if (this->ring_buffer_handle_ == nullptr) { + ESP_LOGE(TAG, "Failed to create ring buffer of size %d", buffer_size); this->mark_failed(); return; } @@ -65,8 +73,8 @@ void ESP32TouchComponent::setup() { esp_err_t err = touch_pad_isr_register(touch_isr_handler, this); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to register touch ISR: %s", esp_err_to_name(err)); - vQueueDelete(this->touch_queue_); - this->touch_queue_ = nullptr; + vRingbufferDelete(this->ring_buffer_handle_); + this->ring_buffer_handle_ = nullptr; this->mark_failed(); return; } @@ -114,33 +122,44 @@ void ESP32TouchComponent::loop() { this->setup_mode_last_log_print_ = now; } - // Process any queued touch events from interrupts - TouchPadEventV1 event; - while (xQueueReceive(this->touch_queue_, &event, 0) == pdTRUE) { - // Find the corresponding sensor - for (auto *child : this->children_) { - if (child->get_touch_pad() == event.pad) { - child->value_ = event.value; + // Process ring buffer entries + size_t item_size; + TouchPadState *pad_states; - // The interrupt gives us the touch state directly - bool new_state = event.is_touched; + // Receive all available items from ring buffer (non-blocking) + while ((pad_states = (TouchPadState *) xRingbufferReceive(this->ring_buffer_handle_, &item_size, 0)) != nullptr) { + // Calculate number of pads in this snapshot + size_t num_pads = item_size / sizeof(TouchPadState); - // Track when we last saw this pad as touched - if (new_state) { - this->last_touch_time_[event.pad] = now; + // Process each pad in the snapshot + for (size_t i = 0; i < num_pads; i++) { + const TouchPadState &pad_state = pad_states[i]; + + // Find the corresponding sensor + for (auto *child : this->children_) { + if (child->get_touch_pad() == static_cast(pad_state.pad)) { + child->value_ = pad_state.value; + + // Track when we last saw this pad as touched + if (pad_state.is_touched) { + this->last_touch_time_[pad_state.pad] = now; + } + + // Only publish if state changed + if (pad_state.is_touched != child->last_state_) { + child->last_state_ = pad_state.is_touched; + child->publish_state(pad_state.is_touched); + ESP_LOGV(TAG, "Touch Pad '%s' state: %s (value: %" PRIu32 ", threshold: %" PRIu32 ")", + child->get_name().c_str(), pad_state.is_touched ? "ON" : "OFF", pad_state.value, + child->get_threshold()); + } + break; } - - // Only publish if state changed - if (new_state != child->last_state_) { - child->last_state_ = new_state; - child->publish_state(new_state); - // Original ESP32: ISR only fires when touched, release is detected by timeout - ESP_LOGV(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 ", threshold: %" PRIu32 ")", - child->get_name().c_str(), event.value, child->get_threshold()); - } - break; } } + + // Return item to ring buffer + vRingbufferReturnItem(this->ring_buffer_handle_, (void *) pad_states); } // Check for released pads periodically @@ -184,8 +203,10 @@ void ESP32TouchComponent::loop() { void ESP32TouchComponent::on_shutdown() { touch_pad_intr_disable(); touch_pad_isr_deregister(touch_isr_handler, this); - if (this->touch_queue_) { - vQueueDelete(this->touch_queue_); + + if (this->ring_buffer_handle_) { + vRingbufferDelete(this->ring_buffer_handle_); + this->ring_buffer_handle_ = nullptr; } bool is_wakeup_source = false; @@ -218,7 +239,23 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { touch_pad_clear_status(); - // Process all configured pads to check their current state + // Calculate size needed for this snapshot + size_t num_pads = component->children_.size(); + size_t snapshot_size = num_pads * sizeof(TouchPadState); + + // Allocate space in ring buffer (ISR-safe version) + void *buffer = xRingbufferSendAcquireFromISR(component->ring_buffer_handle_, snapshot_size); + if (buffer == nullptr) { + // Buffer full - track overflow + component->ring_buffer_overflow_count_++; + return; + } + + // Fill the buffer with pad states + TouchPadState *pad_states = (TouchPadState *) buffer; + + // Process all configured pads + size_t pad_index = 0; for (auto *child : component->children_) { touch_pad_t pad = child->get_touch_pad(); @@ -238,21 +275,24 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { continue; } + // Store pad state + pad_states[pad_index].pad = static_cast(pad); + pad_states[pad_index].value = value; // For original ESP32, lower value means touched - bool is_touched = value < child->get_threshold(); + pad_states[pad_index].is_touched = value < child->get_threshold(); - // Always send the current state - the main loop will filter for changes - TouchPadEventV1 event; - event.pad = pad; - event.value = value; - event.is_touched = is_touched; + pad_index++; + } - // Send to queue from ISR - BaseType_t x_higher_priority_task_woken = pdFALSE; - xQueueSendFromISR(component->touch_queue_, &event, &x_higher_priority_task_woken); - if (x_higher_priority_task_woken) { - portYIELD_FROM_ISR(); - } + // Adjust size if we skipped any pads + size_t actual_size = pad_index * sizeof(TouchPadState); + + // Send the item + BaseType_t higher_priority_task_woken = pdFALSE; + xRingbufferSendCompleteFromISR(component->ring_buffer_handle_, buffer, actual_size, &higher_priority_task_woken); + + if (higher_priority_task_woken) { + portYIELD_FROM_ISR(); } } From ec1dc42e58114d6608ce9bcc7fdf75452a168959 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 17:05:06 -0500 Subject: [PATCH 39/51] Revert "preen" This reverts commit 866eaed73d62600adcb8c2a65047f538f2070d5c. --- esphome/components/esp32_touch/esp32_touch.h | 8 +- .../components/esp32_touch/esp32_touch_v1.cpp | 140 +++++++----------- 2 files changed, 52 insertions(+), 96 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 29fc28cd2e..af516efc5d 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -11,7 +11,6 @@ #include #include #include -#include namespace esphome { namespace esp32_touch { @@ -84,16 +83,13 @@ class ESP32TouchComponent : public Component { static constexpr uint32_t MINIMUM_RELEASE_TIME_MS = 100; static void touch_isr_handler(void *arg); - - // Ring buffer handle for FreeRTOS ring buffer - RingbufHandle_t ring_buffer_handle_{nullptr}; - uint32_t ring_buffer_overflow_count_{0}; + QueueHandle_t touch_queue_{nullptr}; // Design note: last_touch_time_ does not require synchronization primitives because: // 1. ESP32 guarantees atomic 32-bit aligned reads/writes // 2. ISR only writes timestamps, main loop only reads (except sentinel value 1) // 3. Timing tolerance allows for occasional stale reads (50ms check interval) - // 4. Ring buffer operations provide implicit memory barriers + // 4. Queue operations provide implicit memory barriers // Using atomic/critical sections would add overhead without meaningful benefit uint32_t last_touch_time_[TOUCH_PAD_MAX] = {0}; uint32_t release_timeout_ms_{1500}; diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index 2f5da4df60..774ff0b0bf 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -12,19 +12,16 @@ #include "hal/touch_sensor_ll.h" // Include for RTC clock frequency #include "soc/rtc.h" -// Include FreeRTOS ring buffer -#include "freertos/ringbuf.h" namespace esphome { namespace esp32_touch { static const char *const TAG = "esp32_touch"; -// Structure for a single pad's state in the ring buffer -struct TouchPadState { - uint8_t pad; // touch_pad_t - uint32_t value; // Current reading - bool is_touched; // Touch state +struct TouchPadEventV1 { + touch_pad_t pad; + uint32_t value; + bool is_touched; }; void ESP32TouchComponent::setup() { @@ -33,19 +30,14 @@ void ESP32TouchComponent::setup() { touch_pad_init(); touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); - // Create ring buffer for touch events - // Size calculation: We need space for multiple snapshots - // Each snapshot contains: array of TouchPadState structures - size_t pad_state_size = sizeof(TouchPadState); - size_t snapshot_size = this->children_.size() * pad_state_size; + // Create queue for touch events + size_t queue_size = this->children_.size() * 4; + if (queue_size < 8) + queue_size = 8; - // Allow for 4 snapshots in the buffer to handle normal operation and bursts - size_t buffer_size = snapshot_size * 4; - - // Create a byte buffer ring buffer (allows variable sized items) - this->ring_buffer_handle_ = xRingbufferCreate(buffer_size, RINGBUF_TYPE_BYTEBUF); - if (this->ring_buffer_handle_ == nullptr) { - ESP_LOGE(TAG, "Failed to create ring buffer of size %d", buffer_size); + this->touch_queue_ = xQueueCreate(queue_size, sizeof(TouchPadEventV1)); + if (this->touch_queue_ == nullptr) { + ESP_LOGE(TAG, "Failed to create touch event queue of size %d", queue_size); this->mark_failed(); return; } @@ -73,8 +65,8 @@ void ESP32TouchComponent::setup() { esp_err_t err = touch_pad_isr_register(touch_isr_handler, this); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to register touch ISR: %s", esp_err_to_name(err)); - vRingbufferDelete(this->ring_buffer_handle_); - this->ring_buffer_handle_ = nullptr; + vQueueDelete(this->touch_queue_); + this->touch_queue_ = nullptr; this->mark_failed(); return; } @@ -122,44 +114,33 @@ void ESP32TouchComponent::loop() { this->setup_mode_last_log_print_ = now; } - // Process ring buffer entries - size_t item_size; - TouchPadState *pad_states; + // Process any queued touch events from interrupts + TouchPadEventV1 event; + while (xQueueReceive(this->touch_queue_, &event, 0) == pdTRUE) { + // Find the corresponding sensor + for (auto *child : this->children_) { + if (child->get_touch_pad() == event.pad) { + child->value_ = event.value; - // Receive all available items from ring buffer (non-blocking) - while ((pad_states = (TouchPadState *) xRingbufferReceive(this->ring_buffer_handle_, &item_size, 0)) != nullptr) { - // Calculate number of pads in this snapshot - size_t num_pads = item_size / sizeof(TouchPadState); + // The interrupt gives us the touch state directly + bool new_state = event.is_touched; - // Process each pad in the snapshot - for (size_t i = 0; i < num_pads; i++) { - const TouchPadState &pad_state = pad_states[i]; - - // Find the corresponding sensor - for (auto *child : this->children_) { - if (child->get_touch_pad() == static_cast(pad_state.pad)) { - child->value_ = pad_state.value; - - // Track when we last saw this pad as touched - if (pad_state.is_touched) { - this->last_touch_time_[pad_state.pad] = now; - } - - // Only publish if state changed - if (pad_state.is_touched != child->last_state_) { - child->last_state_ = pad_state.is_touched; - child->publish_state(pad_state.is_touched); - ESP_LOGV(TAG, "Touch Pad '%s' state: %s (value: %" PRIu32 ", threshold: %" PRIu32 ")", - child->get_name().c_str(), pad_state.is_touched ? "ON" : "OFF", pad_state.value, - child->get_threshold()); - } - break; + // Track when we last saw this pad as touched + if (new_state) { + this->last_touch_time_[event.pad] = now; } + + // Only publish if state changed + if (new_state != child->last_state_) { + child->last_state_ = new_state; + child->publish_state(new_state); + // Original ESP32: ISR only fires when touched, release is detected by timeout + ESP_LOGV(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 ", threshold: %" PRIu32 ")", + child->get_name().c_str(), event.value, child->get_threshold()); + } + break; } } - - // Return item to ring buffer - vRingbufferReturnItem(this->ring_buffer_handle_, (void *) pad_states); } // Check for released pads periodically @@ -203,10 +184,8 @@ void ESP32TouchComponent::loop() { void ESP32TouchComponent::on_shutdown() { touch_pad_intr_disable(); touch_pad_isr_deregister(touch_isr_handler, this); - - if (this->ring_buffer_handle_) { - vRingbufferDelete(this->ring_buffer_handle_); - this->ring_buffer_handle_ = nullptr; + if (this->touch_queue_) { + vQueueDelete(this->touch_queue_); } bool is_wakeup_source = false; @@ -239,23 +218,7 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { touch_pad_clear_status(); - // Calculate size needed for this snapshot - size_t num_pads = component->children_.size(); - size_t snapshot_size = num_pads * sizeof(TouchPadState); - - // Allocate space in ring buffer (ISR-safe version) - void *buffer = xRingbufferSendAcquireFromISR(component->ring_buffer_handle_, snapshot_size); - if (buffer == nullptr) { - // Buffer full - track overflow - component->ring_buffer_overflow_count_++; - return; - } - - // Fill the buffer with pad states - TouchPadState *pad_states = (TouchPadState *) buffer; - - // Process all configured pads - size_t pad_index = 0; + // Process all configured pads to check their current state for (auto *child : component->children_) { touch_pad_t pad = child->get_touch_pad(); @@ -275,24 +238,21 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { continue; } - // Store pad state - pad_states[pad_index].pad = static_cast(pad); - pad_states[pad_index].value = value; // For original ESP32, lower value means touched - pad_states[pad_index].is_touched = value < child->get_threshold(); + bool is_touched = value < child->get_threshold(); - pad_index++; - } + // Always send the current state - the main loop will filter for changes + TouchPadEventV1 event; + event.pad = pad; + event.value = value; + event.is_touched = is_touched; - // Adjust size if we skipped any pads - size_t actual_size = pad_index * sizeof(TouchPadState); - - // Send the item - BaseType_t higher_priority_task_woken = pdFALSE; - xRingbufferSendCompleteFromISR(component->ring_buffer_handle_, buffer, actual_size, &higher_priority_task_woken); - - if (higher_priority_task_woken) { - portYIELD_FROM_ISR(); + // Send to queue from ISR + BaseType_t x_higher_priority_task_woken = pdFALSE; + xQueueSendFromISR(component->touch_queue_, &event, &x_higher_priority_task_woken); + if (x_higher_priority_task_woken) { + portYIELD_FROM_ISR(); + } } } From a0c81ffd7aa2c883ac1838dda43510b994cbc3d1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 17:08:47 -0500 Subject: [PATCH 40/51] preen --- .../components/esp32_touch/esp32_touch_v1.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index 774ff0b0bf..0f5a65970b 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -31,6 +31,9 @@ void ESP32TouchComponent::setup() { touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); // Create queue for touch events + // Queue size calculation: children * 4 allows for burst scenarios where ISR + // fires multiple times before main loop processes. This is important because + // ESP32 v1 scans all pads on each interrupt, potentially sending multiple events. size_t queue_size = this->children_.size() * 4; if (queue_size < 8) queue_size = 8; @@ -75,11 +78,13 @@ void ESP32TouchComponent::setup() { // Design note: ESP32 v1 hardware limitation - interrupts only fire on touch (not release) // We must use timeout-based detection for release events // Formula: 3 sleep cycles converted to ms, with MINIMUM_RELEASE_TIME_MS minimum + // The division by 2 accounts for the fact that sleep_cycle is in half-cycles uint32_t rtc_freq = rtc_clk_slow_freq_get_hz(); this->release_timeout_ms_ = (this->sleep_cycle_ * 1000 * 3) / (rtc_freq * 2); if (this->release_timeout_ms_ < MINIMUM_RELEASE_TIME_MS) { this->release_timeout_ms_ = MINIMUM_RELEASE_TIME_MS; } + // Check for releases at 1/4 the timeout interval, capped at 50ms this->release_check_interval_ms_ = std::min(this->release_timeout_ms_ / 4, (uint32_t) 50); // Enable touch pad interrupt @@ -115,9 +120,11 @@ void ESP32TouchComponent::loop() { } // Process any queued touch events from interrupts + // Note: Events are only sent by ISR for pads that were measured in that cycle (value != 0) + // This is more efficient than sending all pad states every interrupt TouchPadEventV1 event; while (xQueueReceive(this->touch_queue_, &event, 0) == pdTRUE) { - // Find the corresponding sensor + // Find the corresponding sensor - O(n) search is acceptable since events are infrequent for (auto *child : this->children_) { if (child->get_touch_pad() == event.pad) { child->value_ = event.value; @@ -130,7 +137,7 @@ void ESP32TouchComponent::loop() { this->last_touch_time_[event.pad] = now; } - // Only publish if state changed + // Only publish if state changed - this filters out repeated events if (new_state != child->last_state_) { child->last_state_ = new_state; child->publish_state(new_state); @@ -219,6 +226,8 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { touch_pad_clear_status(); // Process all configured pads to check their current state + // Note: ESP32 v1 doesn't tell us which specific pad triggered the interrupt, + // so we must scan all configured pads to find which ones were touched for (auto *child : component->children_) { touch_pad_t pad = child->get_touch_pad(); @@ -234,6 +243,8 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { } // Skip pads with 0 value - they haven't been measured in this cycle + // This is important: not all pads are measured every interrupt cycle, + // only those that the hardware has updated if (value == 0) { continue; } @@ -242,12 +253,14 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { bool is_touched = value < child->get_threshold(); // Always send the current state - the main loop will filter for changes + // We send both touched and untouched states because the ISR doesn't + // track previous state (to keep ISR fast and simple) TouchPadEventV1 event; event.pad = pad; event.value = value; event.is_touched = is_touched; - // Send to queue from ISR + // Send to queue from ISR - non-blocking, drops if queue full BaseType_t x_higher_priority_task_woken = pdFALSE; xQueueSendFromISR(component->touch_queue_, &event, &x_higher_priority_task_woken); if (x_higher_priority_task_woken) { From 86be1f56d00159fa3c0032ae67db6f34891fbbf2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 17:14:00 -0500 Subject: [PATCH 41/51] preen --- esphome/components/esp32_touch/esp32_touch_v1.cpp | 5 ++++- esphome/components/esp32_touch/esp32_touch_v2.cpp | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index 0f5a65970b..27aa5fc5f4 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -249,7 +249,10 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { continue; } - // For original ESP32, lower value means touched + // IMPORTANT: ESP32 v1 touch detection logic - INVERTED compared to v2! + // ESP32 v1: Touch is detected when capacitance INCREASES, causing the measured value to DECREASE + // Therefore: touched = (value < threshold) + // This is opposite to ESP32-S2/S3 v2 where touched = (value > threshold) bool is_touched = value < child->get_threshold(); // Always send the current state - the main loop will filter for changes diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index e0122202e9..0f3b3f5ebf 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -250,7 +250,10 @@ void ESP32TouchComponent::loop() { touch_pad_read_benchmark(child->get_touch_pad(), &value); } - // For S2/S3 v2, higher value means touched (opposite of v1) + // IMPORTANT: ESP32-S2/S3 v2 touch detection logic - INVERTED compared to v1! + // ESP32-S2/S3 v2: Touch is detected when capacitance INCREASES, causing the measured value to INCREASE + // Therefore: touched = (value > threshold) + // This is opposite to original ESP32 v1 where touched = (value < threshold) bool is_touched = value > child->get_threshold(); child->last_state_ = is_touched; child->publish_state(is_touched); From 6d9d22d42213a384e6d39f8bca1d365ca2ce85d5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 17:17:16 -0500 Subject: [PATCH 42/51] help with setup --- esphome/components/esp32_touch/esp32_touch.h | 5 +++++ esphome/components/esp32_touch/esp32_touch_v1.cpp | 3 ++- esphome/components/esp32_touch/esp32_touch_v2.cpp | 10 ++++++---- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index af516efc5d..0c620a2b8e 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -15,6 +15,11 @@ namespace esphome { namespace esp32_touch { +// IMPORTANT: Touch detection logic differs between ESP32 variants: +// - ESP32 v1 (original): Touch detected when value < threshold (capacitance increase causes value decrease) +// - ESP32-S2/S3 v2: Touch detected when value > threshold (capacitance increase causes value increase) +// This inversion is due to different hardware implementations between chip generations. + static const uint32_t SETUP_MODE_LOG_INTERVAL_MS = 250; class ESP32TouchBinarySensor; diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index 27aa5fc5f4..f5410f910e 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -142,7 +142,8 @@ void ESP32TouchComponent::loop() { child->last_state_ = new_state; child->publish_state(new_state); // Original ESP32: ISR only fires when touched, release is detected by timeout - ESP_LOGV(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 ", threshold: %" PRIu32 ")", + // Note: ESP32 v1 uses inverted logic - touched when value < threshold + ESP_LOGV(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 " < threshold: %" PRIu32 ")", child->get_name().c_str(), event.value, child->get_threshold()); } break; diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 0f3b3f5ebf..dba8d0355a 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -258,8 +258,9 @@ void ESP32TouchComponent::loop() { child->last_state_ = is_touched; child->publish_state(is_touched); - ESP_LOGD(TAG, "Touch Pad '%s' initial state: %s (value: %d, threshold: %d)", child->get_name().c_str(), - is_touched ? "touched" : "released", value, child->get_threshold()); + // Note: ESP32-S2/S3 v2 uses inverted logic compared to v1 - touched when value > threshold + ESP_LOGD(TAG, "Touch Pad '%s' initial state: %s (value: %d %s threshold: %d)", child->get_name().c_str(), + is_touched ? "touched" : "released", value, is_touched ? ">" : "<=", child->get_threshold()); } } @@ -301,8 +302,9 @@ void ESP32TouchComponent::loop() { child->last_state_ = is_touch_event; child->publish_state(is_touch_event); - ESP_LOGD(TAG, "Touch Pad '%s' %s (value: %d, threshold: %d)", child->get_name().c_str(), - is_touch_event ? "touched" : "released", value, child->get_threshold()); + // Note: ESP32-S2/S3 v2 uses inverted logic compared to v1 - touched when value > threshold + ESP_LOGD(TAG, "Touch Pad '%s' %s (value: %d %s threshold: %d)", child->get_name().c_str(), + is_touch_event ? "touched" : "released", value, is_touch_event ? ">" : "<=", child->get_threshold()); break; } } From b3c43ce31f35fb4d4ba5ed6a337a5bb60956199f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 17:23:10 -0500 Subject: [PATCH 43/51] help with setup --- esphome/components/esp32_touch/esp32_touch.h | 1 - .../components/esp32_touch/esp32_touch_v2.cpp | 51 +++++++++---------- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 0c620a2b8e..ef26478f1e 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -107,7 +107,6 @@ class ESP32TouchComponent : public Component { // ESP32-S2/S3 v2 specific static void touch_isr_handler(void *arg); QueueHandle_t touch_queue_{nullptr}; - bool initial_state_read_{false}; // Filter configuration touch_filter_mode_t filter_mode_{TOUCH_PAD_FILTER_MAX}; diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index dba8d0355a..12b8989ee2 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -111,6 +111,29 @@ void ESP32TouchComponent::setup() { touch_pad_set_thresh(child->get_touch_pad(), child->get_threshold()); } } + + // Read initial states after all hardware is initialized + for (auto *child : this->children_) { + // Read current value + uint32_t value = 0; + if (this->filter_configured_()) { + touch_pad_filter_read_smooth(child->get_touch_pad(), &value); + } else { + touch_pad_read_raw_data(child->get_touch_pad(), &value); + } + + // IMPORTANT: ESP32-S2/S3 v2 touch detection logic - INVERTED compared to v1! + // ESP32-S2/S3 v2: Touch is detected when capacitance INCREASES, causing the measured value to INCREASE + // Therefore: touched = (value > threshold) + // This is opposite to original ESP32 v1 where touched = (value < threshold) + bool is_touched = value > child->get_threshold(); + child->last_state_ = is_touched; + child->publish_initial_state(is_touched); + + // Note: ESP32-S2/S3 v2 uses inverted logic compared to v1 - touched when value > threshold + ESP_LOGD(TAG, "Touch Pad '%s' initial state: %s (value: %d %s threshold: %d)", child->get_name().c_str(), + is_touched ? "touched" : "released", value, is_touched ? ">" : "<=", child->get_threshold()); + } } void ESP32TouchComponent::dump_config() { @@ -238,32 +261,6 @@ void ESP32TouchComponent::dump_config() { void ESP32TouchComponent::loop() { const uint32_t now = App.get_loop_component_start_time(); - // Read initial states if not done yet - if (!this->initial_state_read_) { - this->initial_state_read_ = true; - for (auto *child : this->children_) { - // Read current value - uint32_t value = 0; - if (this->filter_configured_()) { - touch_pad_filter_read_smooth(child->get_touch_pad(), &value); - } else { - touch_pad_read_benchmark(child->get_touch_pad(), &value); - } - - // IMPORTANT: ESP32-S2/S3 v2 touch detection logic - INVERTED compared to v1! - // ESP32-S2/S3 v2: Touch is detected when capacitance INCREASES, causing the measured value to INCREASE - // Therefore: touched = (value > threshold) - // This is opposite to original ESP32 v1 where touched = (value < threshold) - bool is_touched = value > child->get_threshold(); - child->last_state_ = is_touched; - child->publish_state(is_touched); - - // Note: ESP32-S2/S3 v2 uses inverted logic compared to v1 - touched when value > threshold - ESP_LOGD(TAG, "Touch Pad '%s' initial state: %s (value: %d %s threshold: %d)", child->get_name().c_str(), - is_touched ? "touched" : "released", value, is_touched ? ">" : "<=", child->get_threshold()); - } - } - // Process any queued touch events from interrupts TouchPadEventV2 event; while (xQueueReceive(this->touch_queue_, &event, 0) == pdTRUE) { @@ -297,7 +294,7 @@ void ESP32TouchComponent::loop() { if (this->filter_configured_()) { touch_pad_filter_read_smooth(event.pad, &value); } else { - touch_pad_read_benchmark(event.pad, &value); + touch_pad_read_raw_data(event.pad, &value); } child->last_state_ = is_touch_event; From 1d90388ffc3c3d150d4fe9039c27e2f280ef2319 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 17:27:09 -0500 Subject: [PATCH 44/51] help with setup --- .../components/esp32_touch/esp32_touch_v1.cpp | 2 +- .../components/esp32_touch/esp32_touch_v2.cpp | 17 ++++++----------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index f5410f910e..2c1f7a79e0 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -171,7 +171,7 @@ void ESP32TouchComponent::loop() { // If we've never seen this pad touched (last_time == 0) and enough time has passed // since startup, publish OFF state and mark as published with value 1 if (last_time == 0 && now > this->release_timeout_ms_) { - child->publish_state(false); + child->publish_initial_state(false); this->last_touch_time_[pad] = 1; // Mark as "initial state published" ESP_LOGV(TAG, "Touch Pad '%s' state: OFF (initial)", child->get_name().c_str()); } else if (child->last_state_ && last_time > 1) { // last_time > 1 means it's a real timestamp diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 12b8989ee2..105ac19c0a 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -309,21 +309,16 @@ void ESP32TouchComponent::loop() { // In setup mode, periodically log all pad values if (this->setup_mode_ && now - this->setup_mode_last_log_print_ > SETUP_MODE_LOG_INTERVAL_MS) { for (auto *child : this->children_) { - uint32_t raw = 0; - uint32_t benchmark = 0; - uint32_t smooth = 0; - - touch_pad_read_raw_data(child->get_touch_pad(), &raw); - touch_pad_read_benchmark(child->get_touch_pad(), &benchmark); + uint32_t value = 0; + // Read the value being used for touch detection if (this->filter_configured_()) { - touch_pad_filter_read_smooth(child->get_touch_pad(), &smooth); - ESP_LOGD(TAG, " Pad T%d: raw=%d, benchmark=%d, smooth=%d, threshold=%d", child->get_touch_pad(), raw, - benchmark, smooth, child->get_threshold()); + touch_pad_filter_read_smooth(child->get_touch_pad(), &value); } else { - ESP_LOGD(TAG, " Pad T%d: raw=%d, benchmark=%d, threshold=%d", child->get_touch_pad(), raw, benchmark, - child->get_threshold()); + touch_pad_read_raw_data(child->get_touch_pad(), &value); } + + ESP_LOGD(TAG, "Touch Pad '%s' (T%d): %d", child->get_name().c_str(), child->get_touch_pad(), value); } this->setup_mode_last_log_print_ = now; } From 88d9361050a23e39524a7069aa18e8f202aa6824 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 17:34:24 -0500 Subject: [PATCH 45/51] help with setup --- esphome/components/esp32_touch/esp32_touch.h | 5 +++ .../components/esp32_touch/esp32_touch_v2.cpp | 37 +++++++++---------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index ef26478f1e..04444ae91e 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -133,6 +133,11 @@ class ESP32TouchComponent : public Component { return (this->waterproof_guard_ring_pad_ != TOUCH_PAD_MAX) && (this->waterproof_shield_driver_ != TOUCH_PAD_SHIELD_DRV_MAX); } + + // Helper method to read touch values - non-blocking operation + // Returns the current touch pad value using either filtered or raw reading + // based on the filter configuration + uint32_t read_touch_value(touch_pad_t pad) const; #endif // Helper functions for dump_config - common to both implementations diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 105ac19c0a..28104371af 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -115,12 +115,7 @@ void ESP32TouchComponent::setup() { // Read initial states after all hardware is initialized for (auto *child : this->children_) { // Read current value - uint32_t value = 0; - if (this->filter_configured_()) { - touch_pad_filter_read_smooth(child->get_touch_pad(), &value); - } else { - touch_pad_read_raw_data(child->get_touch_pad(), &value); - } + uint32_t value = this->read_touch_value(child->get_touch_pad()); // IMPORTANT: ESP32-S2/S3 v2 touch detection logic - INVERTED compared to v1! // ESP32-S2/S3 v2: Touch is detected when capacitance INCREASES, causing the measured value to INCREASE @@ -290,12 +285,7 @@ void ESP32TouchComponent::loop() { } // Read current value - uint32_t value = 0; - if (this->filter_configured_()) { - touch_pad_filter_read_smooth(event.pad, &value); - } else { - touch_pad_read_raw_data(event.pad, &value); - } + uint32_t value = this->read_touch_value(event.pad); child->last_state_ = is_touch_event; child->publish_state(is_touch_event); @@ -309,14 +299,8 @@ void ESP32TouchComponent::loop() { // In setup mode, periodically log all pad values if (this->setup_mode_ && now - this->setup_mode_last_log_print_ > SETUP_MODE_LOG_INTERVAL_MS) { for (auto *child : this->children_) { - uint32_t value = 0; - // Read the value being used for touch detection - if (this->filter_configured_()) { - touch_pad_filter_read_smooth(child->get_touch_pad(), &value); - } else { - touch_pad_read_raw_data(child->get_touch_pad(), &value); - } + uint32_t value = this->read_touch_value(child->get_touch_pad()); ESP_LOGD(TAG, "Touch Pad '%s' (T%d): %d", child->get_name().c_str(), child->get_touch_pad(), value); } @@ -368,6 +352,21 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { } } +uint32_t ESP32TouchComponent::read_touch_value(touch_pad_t pad) const { + // Unlike ESP32 v1, touch reads on ESP32-S2/S3 v2 are non-blocking operations. + // The hardware continuously samples in the background and we can read the + // latest value at any time without waiting. + uint32_t value = 0; + if (this->filter_configured_()) { + // Read filtered/smoothed value when filter is enabled + touch_pad_filter_read_smooth(pad, &value); + } else { + // Read raw value when filter is not configured + touch_pad_read_raw_data(pad, &value); + } + return value; +} + } // namespace esp32_touch } // namespace esphome From b5da84479ea9a5d1cf2c09d5102e7350375b752c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 17:43:08 -0500 Subject: [PATCH 46/51] help with setup --- esphome/components/esp32_touch/esp32_touch.h | 2 + .../esp32_touch/esp32_touch_common.cpp | 42 +++++++++++++++++++ .../components/esp32_touch/esp32_touch_v1.cpp | 16 ++----- .../components/esp32_touch/esp32_touch_v2.cpp | 20 ++------- 4 files changed, 51 insertions(+), 29 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 04444ae91e..965494f523 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -68,6 +68,8 @@ class ESP32TouchComponent : public Component { // Common helper methods void dump_config_base_(); void dump_config_sensors_(); + bool create_touch_queue(); + void cleanup_touch_queue(); // Common members std::vector children_; diff --git a/esphome/components/esp32_touch/esp32_touch_common.cpp b/esphome/components/esp32_touch/esp32_touch_common.cpp index cb9b2e79e1..d9c1c22320 100644 --- a/esphome/components/esp32_touch/esp32_touch_common.cpp +++ b/esphome/components/esp32_touch/esp32_touch_common.cpp @@ -9,6 +9,20 @@ namespace esp32_touch { static const char *const TAG = "esp32_touch"; +// Forward declare the event structures that are defined in the variant-specific files +#ifdef USE_ESP32_VARIANT_ESP32 +struct TouchPadEventV1 { + touch_pad_t pad; + uint32_t value; + bool is_touched; +}; +#else +struct TouchPadEventV2 { + touch_pad_t pad; + uint32_t intr_mask; +}; +#endif + void ESP32TouchComponent::dump_config_base_() { const char *lv_s = get_low_voltage_reference_str(this->low_voltage_reference_); const char *hv_s = get_high_voltage_reference_str(this->high_voltage_reference_); @@ -33,6 +47,34 @@ void ESP32TouchComponent::dump_config_sensors_() { } } +bool ESP32TouchComponent::create_touch_queue() { + // Queue size calculation: children * 4 allows for burst scenarios where ISR + // fires multiple times before main loop processes. + size_t queue_size = this->children_.size() * 4; + if (queue_size < 8) + queue_size = 8; + +#ifdef USE_ESP32_VARIANT_ESP32 + this->touch_queue_ = xQueueCreate(queue_size, sizeof(TouchPadEventV1)); +#else + this->touch_queue_ = xQueueCreate(queue_size, sizeof(TouchPadEventV2)); +#endif + + if (this->touch_queue_ == nullptr) { + ESP_LOGE(TAG, "Failed to create touch event queue of size %d", queue_size); + this->mark_failed(); + return false; + } + return true; +} + +void ESP32TouchComponent::cleanup_touch_queue() { + if (this->touch_queue_) { + vQueueDelete(this->touch_queue_); + this->touch_queue_ = nullptr; + } +} + } // namespace esp32_touch } // namespace esphome diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index 2c1f7a79e0..d6cf2983d5 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -34,14 +34,7 @@ void ESP32TouchComponent::setup() { // Queue size calculation: children * 4 allows for burst scenarios where ISR // fires multiple times before main loop processes. This is important because // ESP32 v1 scans all pads on each interrupt, potentially sending multiple events. - size_t queue_size = this->children_.size() * 4; - if (queue_size < 8) - queue_size = 8; - - this->touch_queue_ = xQueueCreate(queue_size, sizeof(TouchPadEventV1)); - if (this->touch_queue_ == nullptr) { - ESP_LOGE(TAG, "Failed to create touch event queue of size %d", queue_size); - this->mark_failed(); + if (!this->create_touch_queue()) { return; } @@ -68,8 +61,7 @@ void ESP32TouchComponent::setup() { esp_err_t err = touch_pad_isr_register(touch_isr_handler, this); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to register touch ISR: %s", esp_err_to_name(err)); - vQueueDelete(this->touch_queue_); - this->touch_queue_ = nullptr; + this->cleanup_touch_queue(); this->mark_failed(); return; } @@ -192,9 +184,7 @@ void ESP32TouchComponent::loop() { void ESP32TouchComponent::on_shutdown() { touch_pad_intr_disable(); touch_pad_isr_deregister(touch_isr_handler, this); - if (this->touch_queue_) { - vQueueDelete(this->touch_queue_); - } + this->cleanup_touch_queue(); bool is_wakeup_source = false; diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 28104371af..08d3d0aba0 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -13,19 +13,11 @@ static const char *const TAG = "esp32_touch"; struct TouchPadEventV2 { touch_pad_t pad; uint32_t intr_mask; - uint32_t pad_status; }; void ESP32TouchComponent::setup() { // Create queue for touch events first - size_t queue_size = this->children_.size() * 4; - if (queue_size < 8) - queue_size = 8; - - this->touch_queue_ = xQueueCreate(queue_size, sizeof(TouchPadEventV2)); - if (this->touch_queue_ == nullptr) { - ESP_LOGE(TAG, "Failed to create touch event queue of size %d", queue_size); - this->mark_failed(); + if (!this->create_touch_queue()) { return; } @@ -89,8 +81,7 @@ void ESP32TouchComponent::setup() { touch_pad_isr_register(touch_isr_handler, this, static_cast(TOUCH_PAD_INTR_MASK_ALL)); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to register touch ISR: %s", esp_err_to_name(err)); - vQueueDelete(this->touch_queue_); - this->touch_queue_ = nullptr; + this->cleanup_touch_queue(); this->mark_failed(); return; } @@ -313,9 +304,7 @@ void ESP32TouchComponent::on_shutdown() { touch_pad_intr_disable(static_cast(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE | TOUCH_PAD_INTR_MASK_TIMEOUT)); touch_pad_isr_deregister(touch_isr_handler, this); - if (this->touch_queue_) { - vQueueDelete(this->touch_queue_); - } + this->cleanup_touch_queue(); // Check if any pad is configured for wakeup bool is_wakeup_source = false; @@ -338,10 +327,9 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { ESP32TouchComponent *component = static_cast(arg); BaseType_t x_higher_priority_task_woken = pdFALSE; - // Read interrupt status and pad status + // Read interrupt status TouchPadEventV2 event; event.intr_mask = touch_pad_read_intr_status_mask(); - event.pad_status = touch_pad_get_status(); event.pad = touch_pad_get_current_meas_channel(); // Send event to queue for processing in main loop From aabacb745431257e66a8bf940e8e52c16563268e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 17:47:25 -0500 Subject: [PATCH 47/51] help with setup --- esphome/components/esp32_touch/esp32_touch.h | 19 +++++++++++++++++++ .../esp32_touch/esp32_touch_common.cpp | 14 -------------- .../components/esp32_touch/esp32_touch_v1.cpp | 14 +++----------- .../components/esp32_touch/esp32_touch_v2.cpp | 5 ----- 4 files changed, 22 insertions(+), 30 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 965494f523..20db00fe15 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -92,6 +92,16 @@ class ESP32TouchComponent : public Component { static void touch_isr_handler(void *arg); QueueHandle_t touch_queue_{nullptr}; + private: + // Touch event structure for ESP32 v1 + // Contains touch pad info, value, and touch state for queue communication + struct TouchPadEventV1 { + touch_pad_t pad; + uint32_t value; + bool is_touched; + }; + + protected: // Design note: last_touch_time_ does not require synchronization primitives because: // 1. ESP32 guarantees atomic 32-bit aligned reads/writes // 2. ISR only writes timestamps, main loop only reads (except sentinel value 1) @@ -110,6 +120,15 @@ class ESP32TouchComponent : public Component { static void touch_isr_handler(void *arg); QueueHandle_t touch_queue_{nullptr}; + private: + // Touch event structure for ESP32 v2 (S2/S3) + // Contains touch pad and interrupt mask for queue communication + struct TouchPadEventV2 { + touch_pad_t pad; + uint32_t intr_mask; + }; + + protected: // Filter configuration touch_filter_mode_t filter_mode_{TOUCH_PAD_FILTER_MAX}; uint32_t debounce_count_{0}; diff --git a/esphome/components/esp32_touch/esp32_touch_common.cpp b/esphome/components/esp32_touch/esp32_touch_common.cpp index d9c1c22320..7ca0b4155d 100644 --- a/esphome/components/esp32_touch/esp32_touch_common.cpp +++ b/esphome/components/esp32_touch/esp32_touch_common.cpp @@ -9,20 +9,6 @@ namespace esp32_touch { static const char *const TAG = "esp32_touch"; -// Forward declare the event structures that are defined in the variant-specific files -#ifdef USE_ESP32_VARIANT_ESP32 -struct TouchPadEventV1 { - touch_pad_t pad; - uint32_t value; - bool is_touched; -}; -#else -struct TouchPadEventV2 { - touch_pad_t pad; - uint32_t intr_mask; -}; -#endif - void ESP32TouchComponent::dump_config_base_() { const char *lv_s = get_low_voltage_reference_str(this->low_voltage_reference_); const char *hv_s = get_high_voltage_reference_str(this->high_voltage_reference_); diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index d6cf2983d5..c4be859b3f 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -18,18 +18,7 @@ namespace esp32_touch { static const char *const TAG = "esp32_touch"; -struct TouchPadEventV1 { - touch_pad_t pad; - uint32_t value; - bool is_touched; -}; - void ESP32TouchComponent::setup() { - ESP_LOGCONFIG(TAG, "Running setup for ESP32"); - - touch_pad_init(); - touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); - // Create queue for touch events // Queue size calculation: children * 4 allows for burst scenarios where ISR // fires multiple times before main loop processes. This is important because @@ -38,6 +27,9 @@ void ESP32TouchComponent::setup() { return; } + touch_pad_init(); + touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); + // Set up IIR filter if enabled if (this->iir_filter_enabled_()) { touch_pad_filter_start(this->iir_filter_); diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 08d3d0aba0..f0737a6cec 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -10,11 +10,6 @@ namespace esp32_touch { static const char *const TAG = "esp32_touch"; -struct TouchPadEventV2 { - touch_pad_t pad; - uint32_t intr_mask; -}; - void ESP32TouchComponent::setup() { // Create queue for touch events first if (!this->create_touch_queue()) { From 6c5f4cdb70ac08a6039366b7e48bf773faf2e508 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 17:49:01 -0500 Subject: [PATCH 48/51] help with setup --- .../components/esp32_touch/esp32_touch_v2.cpp | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index f0737a6cec..8ac21676d0 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -242,6 +242,17 @@ void ESP32TouchComponent::dump_config() { void ESP32TouchComponent::loop() { const uint32_t now = App.get_loop_component_start_time(); + // In setup mode, periodically log all pad values + if (this->setup_mode_ && now - this->setup_mode_last_log_print_ > SETUP_MODE_LOG_INTERVAL_MS) { + for (auto *child : this->children_) { + // Read the value being used for touch detection + uint32_t value = this->read_touch_value(child->get_touch_pad()); + + ESP_LOGD(TAG, "Touch Pad '%s' (T%d): %d", child->get_name().c_str(), child->get_touch_pad(), value); + } + this->setup_mode_last_log_print_ = now; + } + // Process any queued touch events from interrupts TouchPadEventV2 event; while (xQueueReceive(this->touch_queue_, &event, 0) == pdTRUE) { @@ -281,17 +292,6 @@ void ESP32TouchComponent::loop() { break; } } - - // In setup mode, periodically log all pad values - if (this->setup_mode_ && now - this->setup_mode_last_log_print_ > SETUP_MODE_LOG_INTERVAL_MS) { - for (auto *child : this->children_) { - // Read the value being used for touch detection - uint32_t value = this->read_touch_value(child->get_touch_pad()); - - ESP_LOGD(TAG, "Touch Pad '%s' (T%d): %d", child->get_name().c_str(), child->get_touch_pad(), value); - } - this->setup_mode_last_log_print_ = now; - } } void ESP32TouchComponent::on_shutdown() { From fb9387ecc58c6c169f446b51d59133f1a2657047 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 17:55:21 -0500 Subject: [PATCH 49/51] help with setup --- .../components/esp32_touch/esp32_touch_v1.cpp | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index c4be859b3f..d28233d9c6 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -110,28 +110,31 @@ void ESP32TouchComponent::loop() { while (xQueueReceive(this->touch_queue_, &event, 0) == pdTRUE) { // Find the corresponding sensor - O(n) search is acceptable since events are infrequent for (auto *child : this->children_) { - if (child->get_touch_pad() == event.pad) { - child->value_ = event.value; - - // The interrupt gives us the touch state directly - bool new_state = event.is_touched; - - // Track when we last saw this pad as touched - if (new_state) { - this->last_touch_time_[event.pad] = now; - } - - // Only publish if state changed - this filters out repeated events - if (new_state != child->last_state_) { - child->last_state_ = new_state; - child->publish_state(new_state); - // Original ESP32: ISR only fires when touched, release is detected by timeout - // Note: ESP32 v1 uses inverted logic - touched when value < threshold - ESP_LOGV(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 " < threshold: %" PRIu32 ")", - child->get_name().c_str(), event.value, child->get_threshold()); - } - break; + if (child->get_touch_pad() != event.pad) { + continue; } + + // Found matching pad - process it + child->value_ = event.value; + + // The interrupt gives us the touch state directly + bool new_state = event.is_touched; + + // Track when we last saw this pad as touched + if (new_state) { + this->last_touch_time_[event.pad] = now; + } + + // Only publish if state changed - this filters out repeated events + if (new_state != child->last_state_) { + child->last_state_ = new_state; + child->publish_state(new_state); + // Original ESP32: ISR only fires when touched, release is detected by timeout + // Note: ESP32 v1 uses inverted logic - touched when value < threshold + ESP_LOGV(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 " < threshold: %" PRIu32 ")", + child->get_name().c_str(), event.value, child->get_threshold()); + } + break; // Exit inner loop after processing matching pad } } From 1e24417db0a25ae4adaa233c65b93e80ff6cfdd2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 18:09:39 -0500 Subject: [PATCH 50/51] help with setup --- esphome/components/esp32_touch/esp32_touch.h | 1 + .../esp32_touch/esp32_touch_common.cpp | 24 +++++++++++++++++++ .../components/esp32_touch/esp32_touch_v1.cpp | 20 ++-------------- .../components/esp32_touch/esp32_touch_v2.cpp | 17 ++----------- 4 files changed, 29 insertions(+), 33 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 20db00fe15..a092b414ac 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -70,6 +70,7 @@ class ESP32TouchComponent : public Component { void dump_config_sensors_(); bool create_touch_queue(); void cleanup_touch_queue(); + void configure_wakeup_pads(); // Common members std::vector children_; diff --git a/esphome/components/esp32_touch/esp32_touch_common.cpp b/esphome/components/esp32_touch/esp32_touch_common.cpp index 7ca0b4155d..7e9de689de 100644 --- a/esphome/components/esp32_touch/esp32_touch_common.cpp +++ b/esphome/components/esp32_touch/esp32_touch_common.cpp @@ -61,6 +61,30 @@ void ESP32TouchComponent::cleanup_touch_queue() { } } +void ESP32TouchComponent::configure_wakeup_pads() { + bool is_wakeup_source = false; + + // Check if any pad is configured for wakeup + for (auto *child : this->children_) { + if (child->get_wakeup_threshold() != 0) { + is_wakeup_source = true; + +#ifdef USE_ESP32_VARIANT_ESP32 + // ESP32 v1: No filter available when using as wake-up source. + touch_pad_config(child->get_touch_pad(), child->get_wakeup_threshold()); +#else + // ESP32-S2/S3 v2: Set threshold for wakeup + touch_pad_set_thresh(child->get_touch_pad(), child->get_wakeup_threshold()); +#endif + } + } + + if (!is_wakeup_source) { + // If no pad is configured for wakeup, deinitialize touch pad + touch_pad_deinit(); + } +} + } // namespace esp32_touch } // namespace esphome diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index d28233d9c6..8feccf3604 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -181,29 +181,13 @@ void ESP32TouchComponent::on_shutdown() { touch_pad_isr_deregister(touch_isr_handler, this); this->cleanup_touch_queue(); - bool is_wakeup_source = false; - if (this->iir_filter_enabled_()) { touch_pad_filter_stop(); touch_pad_filter_delete(); } - for (auto *child : this->children_) { - if (child->get_wakeup_threshold() != 0) { - if (!is_wakeup_source) { - is_wakeup_source = true; - // Touch sensor FSM mode must be 'TOUCH_FSM_MODE_TIMER' to use it to wake-up. - touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); - } - - // No filter available when using as wake-up source. - touch_pad_config(child->get_touch_pad(), child->get_wakeup_threshold()); - } - } - - if (!is_wakeup_source) { - touch_pad_deinit(); - } + // Configure wakeup pads if any are set + this->configure_wakeup_pads(); } void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 8ac21676d0..d5c7b9db9b 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -301,21 +301,8 @@ void ESP32TouchComponent::on_shutdown() { touch_pad_isr_deregister(touch_isr_handler, this); this->cleanup_touch_queue(); - // Check if any pad is configured for wakeup - bool is_wakeup_source = false; - for (auto *child : this->children_) { - if (child->get_wakeup_threshold() != 0) { - if (!is_wakeup_source) { - is_wakeup_source = true; - // Touch sensor FSM mode must be 'TOUCH_FSM_MODE_TIMER' to use it to wake-up. - touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); - } - } - } - - if (!is_wakeup_source) { - touch_pad_deinit(); - } + // Configure wakeup pads if any are set + this->configure_wakeup_pads(); } void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { From b32fc3bfdd0d60df080434802e2955caa16029b7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 18:30:53 -0500 Subject: [PATCH 51/51] lint --- esphome/components/esp32_touch/esp32_touch.h | 6 +++--- esphome/components/esp32_touch/esp32_touch_common.cpp | 6 +++--- esphome/components/esp32_touch/esp32_touch_v1.cpp | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index a092b414ac..041549c519 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -68,9 +68,9 @@ class ESP32TouchComponent : public Component { // Common helper methods void dump_config_base_(); void dump_config_sensors_(); - bool create_touch_queue(); - void cleanup_touch_queue(); - void configure_wakeup_pads(); + bool create_touch_queue_(); + void cleanup_touch_queue_(); + void configure_wakeup_pads_(); // Common members std::vector children_; diff --git a/esphome/components/esp32_touch/esp32_touch_common.cpp b/esphome/components/esp32_touch/esp32_touch_common.cpp index 7e9de689de..0119e28acf 100644 --- a/esphome/components/esp32_touch/esp32_touch_common.cpp +++ b/esphome/components/esp32_touch/esp32_touch_common.cpp @@ -33,7 +33,7 @@ void ESP32TouchComponent::dump_config_sensors_() { } } -bool ESP32TouchComponent::create_touch_queue() { +bool ESP32TouchComponent::create_touch_queue_() { // Queue size calculation: children * 4 allows for burst scenarios where ISR // fires multiple times before main loop processes. size_t queue_size = this->children_.size() * 4; @@ -54,14 +54,14 @@ bool ESP32TouchComponent::create_touch_queue() { return true; } -void ESP32TouchComponent::cleanup_touch_queue() { +void ESP32TouchComponent::cleanup_touch_queue_() { if (this->touch_queue_) { vQueueDelete(this->touch_queue_); this->touch_queue_ = nullptr; } } -void ESP32TouchComponent::configure_wakeup_pads() { +void ESP32TouchComponent::configure_wakeup_pads_() { bool is_wakeup_source = false; // Check if any pad is configured for wakeup diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index 8feccf3604..b5e8e2c0c9 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -23,7 +23,7 @@ void ESP32TouchComponent::setup() { // Queue size calculation: children * 4 allows for burst scenarios where ISR // fires multiple times before main loop processes. This is important because // ESP32 v1 scans all pads on each interrupt, potentially sending multiple events. - if (!this->create_touch_queue()) { + if (!this->create_touch_queue_()) { return; } @@ -53,7 +53,7 @@ void ESP32TouchComponent::setup() { esp_err_t err = touch_pad_isr_register(touch_isr_handler, this); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to register touch ISR: %s", esp_err_to_name(err)); - this->cleanup_touch_queue(); + this->cleanup_touch_queue_(); this->mark_failed(); return; } @@ -187,7 +187,7 @@ void ESP32TouchComponent::on_shutdown() { } // Configure wakeup pads if any are set - this->configure_wakeup_pads(); + this->configure_wakeup_pads_(); } void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) {