From 9a37323eb8484ae528846d1ad97e19347e76d68d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 11 Jun 2025 22:32:25 -0500 Subject: [PATCH 01/79] Use interrupt based approach for esp32_touch --- .../components/esp32_touch/esp32_touch.cpp | 112 +++++++++++++++--- esphome/components/esp32_touch/esp32_touch.h | 11 ++ 2 files changed, 107 insertions(+), 16 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 366aa10697..29ac51df4d 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -15,6 +15,20 @@ static const char *const TAG = "esp32_touch"; void ESP32TouchComponent::setup() { ESP_LOGCONFIG(TAG, "Running setup"); touch_pad_init(); + + // Create queue for touch events - size based on number of touch pads + // Each pad can have at most a few events queued (press/release) + // 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_()) { @@ -63,15 +77,32 @@ void ESP32TouchComponent::setup() { for (auto *child : this->children_) { #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) touch_pad_config(child->get_touch_pad()); + if (child->get_threshold() > 0) { + touch_pad_set_thresh(child->get_touch_pad(), child->get_threshold()); + } #else - // Disable interrupt threshold - touch_pad_config(child->get_touch_pad(), 0); + // Set interrupt threshold + touch_pad_config(child->get_touch_pad(), child->get_threshold()); #endif } #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); touch_pad_fsm_start(); #endif + + // 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; + } + + // Enable touch pad interrupt + touch_pad_intr_enable(); + ESP_LOGI(TAG, "Touch pad interrupts enabled"); } void ESP32TouchComponent::dump_config() { @@ -294,29 +325,48 @@ uint32_t ESP32TouchComponent::component_touch_pad_read(touch_pad_t tp) { 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; - for (auto *child : this->children_) { - child->value_ = this->component_touch_pad_read(child->get_touch_pad()); -#if !(defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)) - child->publish_state(child->value_ < child->get_threshold()); -#else - child->publish_state(child->value_ > child->get_threshold()); -#endif - if (should_print) { + // In setup mode, also read values directly for calibration + if (this->setup_mode_ && should_print) { + for (auto *child : this->children_) { + uint32_t value = this->component_touch_pad_read(child->get_touch_pad()); ESP_LOGD(TAG, "Touch Pad '%s' (T%" PRIu32 "): %" PRIu32, child->get_name().c_str(), - (uint32_t) child->get_touch_pad(), child->value_); + (uint32_t) child->get_touch_pad(), value); } - - App.feed_wdt(); + this->setup_mode_last_log_print_ = now; } - if (should_print) { - // Avoid spamming logs - this->setup_mode_last_log_print_ = now; + // Process any queued touch events + 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; + bool new_state; +#if !(defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)) + new_state = child->value_ < child->get_threshold(); +#else + new_state = child->value_ > child->get_threshold(); +#endif + // Only publish if state changed + if (new_state != child->last_state_) { + child->last_state_ = new_state; + child->publish_state(new_state); + } + break; + } + } } } 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 !(defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)) @@ -346,6 +396,36 @@ void ESP32TouchComponent::on_shutdown() { } } +void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { + ESP32TouchComponent *component = static_cast(arg); + uint32_t pad_intr = touch_pad_get_status(); + touch_pad_clear_status(); + + // Check which pads triggered + for (int i = 0; i < TOUCH_PAD_MAX; i++) { + if ((pad_intr >> i) & 0x01) { + touch_pad_t pad = static_cast(i); + TouchPadEvent event; + event.pad = pad; + // Read value in ISR + event.value = 0; +#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + touch_pad_read_raw_data(pad, &event.value); +#else + uint16_t val = 0; + touch_pad_read(pad, &val); + event.value = val; +#endif + // 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) {} diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 0eac590ce7..7b863c9b23 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -9,12 +9,19 @@ #include #include +#include +#include namespace esphome { namespace esp32_touch { class ESP32TouchBinarySensor; +struct TouchPadEvent { + touch_pad_t pad; + uint32_t value; +}; + class ESP32TouchComponent : public Component { public: void register_touch_pad(ESP32TouchBinarySensor *pad) { this->children_.push_back(pad); } @@ -57,6 +64,9 @@ class ESP32TouchComponent : public Component { void on_shutdown() override; protected: + static void touch_isr_handler(void *arg); + + QueueHandle_t touch_queue_{nullptr}; #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) bool filter_configured_() const { return (this->filter_mode_ != TOUCH_PAD_FILTER_MAX) && (this->smooth_level_ != TOUCH_PAD_SMOOTH_MAX); @@ -113,6 +123,7 @@ class ESP32TouchBinarySensor : public binary_sensor::BinarySensor { touch_pad_t touch_pad_{TOUCH_PAD_MAX}; uint32_t threshold_{0}; uint32_t value_{0}; + bool last_state_{false}; const uint32_t wakeup_threshold_{0}; }; From 61bca5631633e30cf5ca61da656b1529186a4aab Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 11 Jun 2025 22:43:25 -0500 Subject: [PATCH 02/79] try touch_ll_read_raw_data --- esphome/components/esp32_touch/esp32_touch.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 29ac51df4d..e661cbe388 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -7,6 +7,11 @@ #include +#if !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) +// For ESP32 classic, we need the low-level HAL functions for ISR-safe reads +#include "hal/touch_sensor_ll.h" +#endif + namespace esphome { namespace esp32_touch { @@ -412,9 +417,9 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) touch_pad_read_raw_data(pad, &event.value); #else - uint16_t val = 0; - touch_pad_read(pad, &val); - event.value = val; + // For ESP32, we need to use the low-level HAL function that doesn't use semaphores + // touch_pad_read() uses a semaphore internally and cannot be called from ISR + event.value = touch_ll_read_raw_data(pad); #endif // Send to queue from ISR BaseType_t xHigherPriorityTaskWoken = pdFALSE; From c047aa47eb3dcd36290733725bdc8cf0358d6054 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 11 Jun 2025 22:46:40 -0500 Subject: [PATCH 03/79] use ll for all --- esphome/components/esp32_touch/esp32_touch.cpp | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index e661cbe388..53656ec226 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -7,10 +7,8 @@ #include -#if !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) -// For ESP32 classic, we need the low-level HAL functions for ISR-safe reads +// Include HAL for ISR-safe touch reading on all variants #include "hal/touch_sensor_ll.h" -#endif namespace esphome { namespace esp32_touch { @@ -412,15 +410,9 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { touch_pad_t pad = static_cast(i); TouchPadEvent event; event.pad = pad; - // Read value in ISR - event.value = 0; -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - touch_pad_read_raw_data(pad, &event.value); -#else - // For ESP32, we need to use the low-level HAL function that doesn't use semaphores - // touch_pad_read() uses a semaphore internally and cannot be called from ISR + // Read value in ISR using HAL function (safe for all variants) + // touch_pad_read() and touch_pad_read_raw_data() use semaphores and cannot be called from ISR event.value = touch_ll_read_raw_data(pad); -#endif // Send to queue from ISR BaseType_t xHigherPriorityTaskWoken = pdFALSE; xQueueSendFromISR(component->touch_queue_, &event, &xHigherPriorityTaskWoken); From a7bb7fc14d07bdf8421f1386e1de66e4aa9c19e5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 11 Jun 2025 22:55:15 -0500 Subject: [PATCH 04/79] fix --- .../components/esp32_touch/esp32_touch.cpp | 47 ++++++++++--------- esphome/components/esp32_touch/esp32_touch.h | 1 + 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 53656ec226..0e864773e7 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -339,23 +339,23 @@ void ESP32TouchComponent::loop() { this->setup_mode_last_log_print_ = now; } - // Process any queued touch events + // 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; - bool new_state; -#if !(defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)) - new_state = child->value_ < child->get_threshold(); -#else - new_state = child->value_ > child->get_threshold(); -#endif + + // The interrupt gives us the triggered state directly + bool new_state = event.triggered; + // Only publish if state changed if (new_state != child->last_state_) { child->last_state_ = new_state; child->publish_state(new_state); + ESP_LOGD(TAG, "Touch Pad '%s' state: %s (value: %" PRIu32 ", threshold: %" PRIu32 ")", + child->get_name().c_str(), new_state ? "ON" : "OFF", event.value, child->get_threshold()); } break; } @@ -401,24 +401,25 @@ void ESP32TouchComponent::on_shutdown() { void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { ESP32TouchComponent *component = static_cast(arg); - uint32_t pad_intr = touch_pad_get_status(); + uint32_t pad_status = touch_pad_get_status(); touch_pad_clear_status(); - // Check which pads triggered - for (int i = 0; i < TOUCH_PAD_MAX; i++) { - if ((pad_intr >> i) & 0x01) { - touch_pad_t pad = static_cast(i); - TouchPadEvent event; - event.pad = pad; - // Read value in ISR using HAL function (safe for all variants) - // touch_pad_read() and touch_pad_read_raw_data() use semaphores and cannot be called from ISR - event.value = touch_ll_read_raw_data(pad); - // Send to queue from ISR - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - xQueueSendFromISR(component->touch_queue_, &event, &xHigherPriorityTaskWoken); - if (xHigherPriorityTaskWoken) { - portYIELD_FROM_ISR(); - } + // pad_status contains the current trigger state of all pads + // Send status update for all configured pads + for (auto *child : component->children_) { + touch_pad_t pad = child->get_touch_pad(); + TouchPadEvent event; + event.pad = pad; + // Check if this pad is currently triggered (1) or not (0) + event.triggered = (pad_status >> pad) & 0x01; + // Read current value using HAL function (safe for all variants) + event.value = touch_ll_read_raw_data(pad); + + // Send to queue from ISR + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xQueueSendFromISR(component->touch_queue_, &event, &xHigherPriorityTaskWoken); + if (xHigherPriorityTaskWoken) { + portYIELD_FROM_ISR(); } } } diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 7b863c9b23..1aca72d623 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -20,6 +20,7 @@ class ESP32TouchBinarySensor; struct TouchPadEvent { touch_pad_t pad; uint32_t value; + bool triggered; // Whether this pad is currently in triggered state }; class ESP32TouchComponent : public Component { From eae4bd222ac17d21d4750075d97afdaf4ad39b0c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 11 Jun 2025 23:29:00 -0500 Subject: [PATCH 05/79] track pads --- .../components/esp32_touch/esp32_touch.cpp | 34 ++++++++++++------- esphome/components/esp32_touch/esp32_touch.h | 1 + 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 0e864773e7..64aacfcefd 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -404,22 +404,30 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { uint32_t pad_status = touch_pad_get_status(); touch_pad_clear_status(); - // pad_status contains the current trigger state of all pads - // Send status update for all configured pads + // Find which pads have changed state + uint32_t changed_pads = pad_status ^ component->last_touch_status_; + component->last_touch_status_ = pad_status; + + // Only process pads that have actually changed state for (auto *child : component->children_) { touch_pad_t pad = child->get_touch_pad(); - TouchPadEvent event; - event.pad = pad; - // Check if this pad is currently triggered (1) or not (0) - event.triggered = (pad_status >> pad) & 0x01; - // Read current value using HAL function (safe for all variants) - event.value = touch_ll_read_raw_data(pad); - // Send to queue from ISR - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - xQueueSendFromISR(component->touch_queue_, &event, &xHigherPriorityTaskWoken); - if (xHigherPriorityTaskWoken) { - portYIELD_FROM_ISR(); + // Check if this pad has changed + if ((changed_pads >> pad) & 0x01) { + bool is_touched = (pad_status >> pad) & 0x01; + + TouchPadEvent event; + event.pad = pad; + event.triggered = is_touched; + // Read current value using HAL function (safe for all variants) + event.value = touch_ll_read_raw_data(pad); + + // Send to queue from ISR + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xQueueSendFromISR(component->touch_queue_, &event, &xHigherPriorityTaskWoken); + if (xHigherPriorityTaskWoken) { + portYIELD_FROM_ISR(); + } } } } diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 1aca72d623..824e44a7ac 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -68,6 +68,7 @@ class ESP32TouchComponent : public Component { static void touch_isr_handler(void *arg); QueueHandle_t touch_queue_{nullptr}; + uint32_t last_touch_status_{0}; // Track last interrupt status to detect changes #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) bool filter_configured_() const { return (this->filter_mode_ != TOUCH_PAD_FILTER_MAX) && (this->smooth_level_ != TOUCH_PAD_SMOOTH_MAX); From 463a581ab96928af8ff36dbc6d5c6ffeb07ff3d1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 00:56:42 -0500 Subject: [PATCH 06/79] DEBUG! --- .../components/esp32_touch/esp32_touch.cpp | 225 ++++++++++++++++-- esphome/components/esp32_touch/esp32_touch.h | 6 +- 2 files changed, 204 insertions(+), 27 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 64aacfcefd..e18b9aa362 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -5,10 +5,15 @@ #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 ISR-safe printing +#include "rom/ets_sys.h" +// Include for RTC clock frequency +#include "soc/rtc.h" namespace esphome { namespace esp32_touch { @@ -17,6 +22,14 @@ static const char *const TAG = "esp32_touch"; void ESP32TouchComponent::setup() { ESP_LOGCONFIG(TAG, "Running setup"); + ESP_LOGI(TAG, "Number of touch pads configured: %d", this->children_.size()); + + if (this->children_.empty()) { + ESP_LOGE(TAG, "No touch pads configured!"); + this->mark_failed(); + return; + } + touch_pad_init(); // Create queue for touch events - size based on number of touch pads @@ -26,6 +39,9 @@ void ESP32TouchComponent::setup() { if (queue_size < 8) queue_size = 8; // Minimum queue size + // QUEUE SIZE likely doesn't make sense if its really ratelimited + // to 1 per second, but this is a good starting point + 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); @@ -70,9 +86,11 @@ void ESP32TouchComponent::setup() { #endif #if ESP_IDF_VERSION_MAJOR >= 5 && defined(USE_ESP32_VARIANT_ESP32) + ESP_LOGD(TAG, "Setting measurement_clock_cycles=%u, measurement_interval=%u", this->meas_cycle_, this->sleep_cycle_); touch_pad_set_measurement_clock_cycles(this->meas_cycle_); touch_pad_set_measurement_interval(this->sleep_cycle_); #else + ESP_LOGD(TAG, "Setting meas_time: sleep_cycle=%u, meas_cycle=%u", this->sleep_cycle_, this->meas_cycle_); 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_); @@ -88,9 +106,23 @@ void ESP32TouchComponent::setup() { touch_pad_config(child->get_touch_pad(), child->get_threshold()); #endif } + #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); touch_pad_fsm_start(); +#else + // For ESP32, we'll use software mode with manual triggering + // Timer mode seems to break touch measurements completely + touch_pad_set_fsm_mode(TOUCH_FSM_MODE_SW); + + // Set trigger mode and source + touch_pad_set_trigger_mode(TOUCH_TRIGGER_BELOW); + touch_pad_set_trigger_source(TOUCH_TRIGGER_SOURCE_BOTH); + // Clear any pending interrupts before starting + touch_pad_clear_status(); + + // Do an initial measurement + touch_pad_sw_start(); #endif // Register ISR handler @@ -103,9 +135,75 @@ void ESP32TouchComponent::setup() { 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(); + +#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + // For S2/S3, 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 + } +#else + // For ESP32 in software mode, we're triggering manually + // Since we're triggering every 1 second in the debug loop, use 1500ms timeout + this->release_timeout_ms_ = 1500; // 1.5 seconds +#endif + + // Calculate check interval + this->release_check_interval_ms_ = std::min(this->release_timeout_ms_ / 4, (uint32_t) 50); + + // Read back the actual configuration to verify + uint16_t actual_sleep_cycle = 0; + uint16_t actual_meas_cycle = 0; +#if ESP_IDF_VERSION_MAJOR >= 5 && defined(USE_ESP32_VARIANT_ESP32) + touch_pad_get_measurement_interval(&actual_sleep_cycle); + touch_pad_get_measurement_clock_cycles(&actual_meas_cycle); +#else + touch_pad_get_meas_time(&actual_sleep_cycle, &actual_meas_cycle); +#endif + + ESP_LOGI(TAG, "Touch timing config - requested: sleep=%u, meas=%u | actual: sleep=%u, meas=%u", this->sleep_cycle_, + this->meas_cycle_, actual_sleep_cycle, actual_meas_cycle); + ESP_LOGI(TAG, "Touch release timeout: %u ms, check interval: %u ms (RTC freq: %u Hz)", this->release_timeout_ms_, + this->release_check_interval_ms_, rtc_freq); + // Enable touch pad interrupt touch_pad_intr_enable(); ESP_LOGI(TAG, "Touch pad interrupts enabled"); + + // Check FSM state for debugging + touch_fsm_mode_t fsm_mode; + touch_pad_get_fsm_mode(&fsm_mode); + ESP_LOGI(TAG, "FSM mode: %s", fsm_mode == TOUCH_FSM_MODE_TIMER ? "TIMER" : "SW"); + + ESP_LOGI(TAG, "Initial touch status: 0x%04x", touch_pad_get_status()); + + // Log which pads are configured and initialize their state + ESP_LOGI(TAG, "Configured touch pads:"); + for (auto *child : this->children_) { + uint32_t value = this->component_touch_pad_read(child->get_touch_pad()); + ESP_LOGI(TAG, " Touch Pad %d: threshold=%d, current value=%d", (int) child->get_touch_pad(), + (int) child->get_threshold(), (int) value); + + // Initialize the sensor state based on current value +#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 + + child->last_state_ = is_touched; + child->publish_initial_state(is_touched); + + if (is_touched) { + this->last_touch_time_[child->get_touch_pad()] = App.get_loop_component_start_time(); + } + } + + ESP_LOGI(TAG, "ESP32 Touch setup complete"); } void ESP32TouchComponent::dump_config() { @@ -327,28 +425,59 @@ uint32_t ESP32TouchComponent::component_touch_pad_read(touch_pad_t tp) { 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; + bool should_print = now - this->setup_mode_last_log_print_ > 1000; // Log every second + + // Always check touch status periodically + if (should_print) { + uint32_t current_status = touch_pad_get_status(); + uint32_t hal_status; + touch_ll_read_trigger_status_mask(&hal_status); + + // Check if FSM is still in timer mode + touch_fsm_mode_t fsm_mode; + touch_pad_get_fsm_mode(&fsm_mode); + + ESP_LOGD(TAG, "Current touch status: 0x%04x (HAL: 0x%04x), FSM: %s", current_status, hal_status, + fsm_mode == TOUCH_FSM_MODE_TIMER ? "TIMER" : "SW"); + + // Try a manual software trigger to see if measurements are working at all + if (current_status == 0 && hal_status == 0) { + ESP_LOGD(TAG, "No touch status, trying manual trigger..."); + touch_pad_sw_start(); + } - // In setup mode, also read values directly for calibration - if (this->setup_mode_ && should_print) { for (auto *child : this->children_) { uint32_t value = this->component_touch_pad_read(child->get_touch_pad()); - ESP_LOGD(TAG, "Touch Pad '%s' (T%" PRIu32 "): %" PRIu32, child->get_name().c_str(), - (uint32_t) child->get_touch_pad(), value); + // Touch detection logic differs between ESP32 variants +#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 + ESP_LOGD(TAG, "Touch Pad '%s' (T%" PRIu32 "): value=%" PRIu32 ", threshold=%" PRIu32 ", touched=%s", + child->get_name().c_str(), (uint32_t) child->get_touch_pad(), value, child->get_threshold(), + is_touched ? "YES" : "NO"); } this->setup_mode_last_log_print_ = now; } // Process any queued touch events from interrupts TouchPadEvent event; + uint32_t processed_pads = 0; // Bitmask of pads we processed events for while (xQueueReceive(this->touch_queue_, &event, 0) == pdTRUE) { + processed_pads |= (1 << event.pad); // 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 triggered state directly - bool new_state = event.triggered; + // 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_) { @@ -361,6 +490,36 @@ void ESP32TouchComponent::loop() { } } } + + // 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(); + + // Skip if we just processed an event for this pad + if ((processed_pads >> pad) & 0x01) { + continue; + } + + if (child->last_state_) { + uint32_t last_time = this->last_touch_time_[pad]; + uint32_t time_diff = now - last_time; + + // Check if we haven't seen this pad recently + if (last_time == 0 || 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] = 0; + ESP_LOGD(TAG, "Touch Pad '%s' state: OFF (timeout)", child->get_name().c_str()); + } + } + } } void ESP32TouchComponent::on_shutdown() { @@ -401,33 +560,49 @@ void ESP32TouchComponent::on_shutdown() { void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { ESP32TouchComponent *component = static_cast(arg); + + // Log that ISR was called + ets_printf("Touch ISR triggered!\n"); + uint32_t pad_status = touch_pad_get_status(); touch_pad_clear_status(); - // Find which pads have changed state - uint32_t changed_pads = pad_status ^ component->last_touch_status_; - component->last_touch_status_ = pad_status; + // Always log the status + ets_printf("Touch ISR: raw status=0x%04x\n", pad_status); - // Only process pads that have actually changed state + // 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(); - // Check if this pad has changed - if ((changed_pads >> pad) & 0x01) { - bool is_touched = (pad_status >> pad) & 0x01; + // Read current value + uint32_t value = touch_ll_read_raw_data(pad); - TouchPadEvent event; - event.pad = pad; - event.triggered = is_touched; - // Read current value using HAL function (safe for all variants) - event.value = touch_ll_read_raw_data(pad); + // Skip pads with 0 value - they haven't been measured in this cycle + if (value == 0) { + continue; + } - // Send to queue from ISR - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - xQueueSendFromISR(component->touch_queue_, &event, &xHigherPriorityTaskWoken); - if (xHigherPriorityTaskWoken) { - portYIELD_FROM_ISR(); - } + // 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 + + ets_printf(" Pad %d: value=%d, threshold=%d, touched=%d\n", pad, value, child->get_threshold(), is_touched); + + // 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(); } } } diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 824e44a7ac..130b5affba 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -20,7 +20,7 @@ class ESP32TouchBinarySensor; struct TouchPadEvent { touch_pad_t pad; uint32_t value; - bool triggered; // Whether this pad is currently in triggered state + bool is_touched; // Whether this pad is currently touched }; class ESP32TouchComponent : public Component { @@ -68,7 +68,9 @@ class ESP32TouchComponent : public Component { static void touch_isr_handler(void *arg); QueueHandle_t touch_queue_{nullptr}; - uint32_t last_touch_status_{0}; // Track last interrupt status to detect changes + uint32_t last_touch_time_[SOC_TOUCH_SENSOR_NUM] = {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) bool filter_configured_() const { return (this->filter_mode_ != TOUCH_PAD_FILTER_MAX) && (this->smooth_level_ != TOUCH_PAD_SMOOTH_MAX); From d322d83745b42b512fb67cc3a1d81849cc6ac716 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 09:20:49 -0500 Subject: [PATCH 07/79] fixes --- .../components/esp32_touch/esp32_touch.cpp | 32 ++++--------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index e18b9aa362..378f638d0d 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -32,6 +32,12 @@ void ESP32TouchComponent::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 events queued (press/release) // Use 4x the number of pads to handle burst events @@ -96,35 +102,10 @@ void ESP32TouchComponent::setup() { touch_pad_set_voltage(this->high_voltage_reference_, this->low_voltage_reference_, this->voltage_attenuation_); for (auto *child : this->children_) { -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - touch_pad_config(child->get_touch_pad()); - if (child->get_threshold() > 0) { - touch_pad_set_thresh(child->get_touch_pad(), child->get_threshold()); - } -#else // Set interrupt threshold touch_pad_config(child->get_touch_pad(), child->get_threshold()); -#endif } -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); - touch_pad_fsm_start(); -#else - // For ESP32, we'll use software mode with manual triggering - // Timer mode seems to break touch measurements completely - touch_pad_set_fsm_mode(TOUCH_FSM_MODE_SW); - - // Set trigger mode and source - touch_pad_set_trigger_mode(TOUCH_TRIGGER_BELOW); - touch_pad_set_trigger_source(TOUCH_TRIGGER_SOURCE_BOTH); - // Clear any pending interrupts before starting - touch_pad_clear_status(); - - // Do an initial measurement - touch_pad_sw_start(); -#endif - // Register ISR handler esp_err_t err = touch_pad_isr_register(touch_isr_handler, this); if (err != ESP_OK) { @@ -576,6 +557,7 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { touch_pad_t pad = child->get_touch_pad(); // Read current value + // We should be using touch_pad_read_filtered here uint32_t value = touch_ll_read_raw_data(pad); // Skip pads with 0 value - they haven't been measured in this cycle From bd89a88e346df9092ebe14c2c59dee516d795529 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 09:23:38 -0500 Subject: [PATCH 08/79] fixes --- esphome/components/esp32_touch/esp32_touch.cpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 378f638d0d..8318a9e1fb 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -426,19 +426,6 @@ void ESP32TouchComponent::loop() { ESP_LOGD(TAG, "No touch status, trying manual trigger..."); touch_pad_sw_start(); } - - for (auto *child : this->children_) { - uint32_t value = this->component_touch_pad_read(child->get_touch_pad()); - // Touch detection logic differs between ESP32 variants -#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 - ESP_LOGD(TAG, "Touch Pad '%s' (T%" PRIu32 "): value=%" PRIu32 ", threshold=%" PRIu32 ", touched=%s", - child->get_name().c_str(), (uint32_t) child->get_touch_pad(), value, child->get_threshold(), - is_touched ? "YES" : "NO"); - } this->setup_mode_last_log_print_ = now; } From dbdac3707b47afedecc79766507504b493718428 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 10:00:49 -0500 Subject: [PATCH 09/79] fixes --- .../components/esp32_touch/esp32_touch.cpp | 120 ++++++------------ esphome/components/esp32_touch/esp32_touch.h | 6 +- 2 files changed, 41 insertions(+), 85 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 8318a9e1fb..f7c4818396 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -10,8 +10,6 @@ // Include HAL for ISR-safe touch reading on all variants #include "hal/touch_sensor_ll.h" -// Include for ISR-safe printing -#include "rom/ets_sys.h" // Include for RTC clock frequency #include "soc/rtc.h" @@ -22,13 +20,6 @@ static const char *const TAG = "esp32_touch"; void ESP32TouchComponent::setup() { ESP_LOGCONFIG(TAG, "Running setup"); - ESP_LOGI(TAG, "Number of touch pads configured: %d", this->children_.size()); - - if (this->children_.empty()) { - ESP_LOGE(TAG, "No touch pads configured!"); - this->mark_failed(); - return; - } touch_pad_init(); @@ -39,15 +30,12 @@ void ESP32TouchComponent::setup() { #endif // Create queue for touch events - size based on number of touch pads - // Each pad can have at most a few events queued (press/release) + // 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 - // QUEUE SIZE likely doesn't make sense if its really ratelimited - // to 1 per second, but this is a good starting point - 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); @@ -121,46 +109,17 @@ void ESP32TouchComponent::setup() { // Get actual RTC clock frequency uint32_t rtc_freq = rtc_clk_slow_freq_get_hz(); -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - // For S2/S3, calculate based on actual sleep cycle since they use timer mode + // 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 } -#else - // For ESP32 in software mode, we're triggering manually - // Since we're triggering every 1 second in the debug loop, use 1500ms timeout - this->release_timeout_ms_ = 1500; // 1.5 seconds -#endif // Calculate check interval this->release_check_interval_ms_ = std::min(this->release_timeout_ms_ / 4, (uint32_t) 50); - // Read back the actual configuration to verify - uint16_t actual_sleep_cycle = 0; - uint16_t actual_meas_cycle = 0; -#if ESP_IDF_VERSION_MAJOR >= 5 && defined(USE_ESP32_VARIANT_ESP32) - touch_pad_get_measurement_interval(&actual_sleep_cycle); - touch_pad_get_measurement_clock_cycles(&actual_meas_cycle); -#else - touch_pad_get_meas_time(&actual_sleep_cycle, &actual_meas_cycle); -#endif - - ESP_LOGI(TAG, "Touch timing config - requested: sleep=%u, meas=%u | actual: sleep=%u, meas=%u", this->sleep_cycle_, - this->meas_cycle_, actual_sleep_cycle, actual_meas_cycle); - ESP_LOGI(TAG, "Touch release timeout: %u ms, check interval: %u ms (RTC freq: %u Hz)", this->release_timeout_ms_, - this->release_check_interval_ms_, rtc_freq); - // Enable touch pad interrupt touch_pad_intr_enable(); - ESP_LOGI(TAG, "Touch pad interrupts enabled"); - - // Check FSM state for debugging - touch_fsm_mode_t fsm_mode; - touch_pad_get_fsm_mode(&fsm_mode); - ESP_LOGI(TAG, "FSM mode: %s", fsm_mode == TOUCH_FSM_MODE_TIMER ? "TIMER" : "SW"); - - ESP_LOGI(TAG, "Initial touch status: 0x%04x", touch_pad_get_status()); // Log which pads are configured and initialize their state ESP_LOGI(TAG, "Configured touch pads:"); @@ -183,17 +142,9 @@ void ESP32TouchComponent::setup() { this->last_touch_time_[child->get_touch_pad()] = App.get_loop_component_start_time(); } } - - ESP_LOGI(TAG, "ESP32 Touch setup complete"); } void ESP32TouchComponent::dump_config() { - ESP_LOGCONFIG(TAG, - "Config for ESP32 Touch Hub:\n" - " Meas cycle: %.2fms\n" - " Sleep cycle: %.2fms", - this->meas_cycle_ / (8000000.0f / 1000.0f), this->sleep_cycle_ / (150000.0f / 1000.0f)); - const char *lv_s; switch (this->low_voltage_reference_) { case TOUCH_LVOLT_0V5: @@ -212,7 +163,6 @@ void ESP32TouchComponent::dump_config() { lv_s = "UNKNOWN"; break; } - ESP_LOGCONFIG(TAG, " Low Voltage Reference: %s", lv_s); const char *hv_s; switch (this->high_voltage_reference_) { @@ -232,7 +182,6 @@ void ESP32TouchComponent::dump_config() { hv_s = "UNKNOWN"; break; } - ESP_LOGCONFIG(TAG, " High Voltage Reference: %s", hv_s); const char *atten_s; switch (this->voltage_attenuation_) { @@ -252,7 +201,18 @@ void ESP32TouchComponent::dump_config() { atten_s = "UNKNOWN"; break; } - ESP_LOGCONFIG(TAG, " Voltage Attenuation: %s", atten_s); + 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_()) { @@ -406,25 +366,13 @@ uint32_t ESP32TouchComponent::component_touch_pad_read(touch_pad_t tp) { void ESP32TouchComponent::loop() { const uint32_t now = App.get_loop_component_start_time(); - bool should_print = now - this->setup_mode_last_log_print_ > 1000; // Log every second + bool should_print = this->setup_mode_ && now - this->setup_mode_last_log_print_ > 250; - // Always check touch status periodically + // Print debug info for all pads in setup mode if (should_print) { - uint32_t current_status = touch_pad_get_status(); - uint32_t hal_status; - touch_ll_read_trigger_status_mask(&hal_status); - - // Check if FSM is still in timer mode - touch_fsm_mode_t fsm_mode; - touch_pad_get_fsm_mode(&fsm_mode); - - ESP_LOGD(TAG, "Current touch status: 0x%04x (HAL: 0x%04x), FSM: %s", current_status, hal_status, - fsm_mode == TOUCH_FSM_MODE_TIMER ? "TIMER" : "SW"); - - // Try a manual software trigger to see if measurements are working at all - if (current_status == 0 && hal_status == 0) { - ESP_LOGD(TAG, "No touch status, trying manual trigger..."); - touch_pad_sw_start(); + 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; } @@ -529,23 +477,33 @@ void ESP32TouchComponent::on_shutdown() { void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { ESP32TouchComponent *component = static_cast(arg); - // Log that ISR was called - ets_printf("Touch ISR triggered!\n"); - uint32_t pad_status = touch_pad_get_status(); touch_pad_clear_status(); - // Always log the status - ets_printf("Touch ISR: raw status=0x%04x\n", pad_status); - // 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 - // We should be using touch_pad_read_filtered here - uint32_t value = touch_ll_read_raw_data(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_read_raw_data(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_raw_data(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) { @@ -559,8 +517,6 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { bool is_touched = value < child->get_threshold(); #endif - ets_printf(" Pad %d: value=%d, threshold=%d, touched=%d\n", pad, value, child->get_threshold(), is_touched); - // Always send the current state - the main loop will filter for changes TouchPadEvent event; event.pad = pad; diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 130b5affba..218ac26453 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 { static void touch_isr_handler(void *arg); QueueHandle_t touch_queue_{nullptr}; - uint32_t last_touch_time_[SOC_TOUCH_SENSOR_NUM] = {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 + 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) bool filter_configured_() const { return (this->filter_mode_ != TOUCH_PAD_FILTER_MAX) && (this->smooth_level_ != TOUCH_PAD_SMOOTH_MAX); From 478e2e726b7f3980b8950eea92f16b44206cc0ef Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 10:01:35 -0500 Subject: [PATCH 10/79] fixes --- esphome/components/esp32_touch/esp32_touch.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index f7c4818396..ba441c2406 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -80,11 +80,9 @@ void ESP32TouchComponent::setup() { #endif #if ESP_IDF_VERSION_MAJOR >= 5 && defined(USE_ESP32_VARIANT_ESP32) - ESP_LOGD(TAG, "Setting measurement_clock_cycles=%u, measurement_interval=%u", this->meas_cycle_, this->sleep_cycle_); touch_pad_set_measurement_clock_cycles(this->meas_cycle_); touch_pad_set_measurement_interval(this->sleep_cycle_); #else - ESP_LOGD(TAG, "Setting meas_time: sleep_cycle=%u, meas_cycle=%u", this->sleep_cycle_, this->meas_cycle_); 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_); From e5d12d346aa4737498707d3c6c01147cfee49639 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 10:08:29 -0500 Subject: [PATCH 11/79] fixes --- esphome/components/esp32_touch/esp32_touch.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index ba441c2406..c79c751155 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -487,19 +487,19 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { uint32_t value; #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) if (component->filter_configured_()) { - touch_pad_read_raw_data(pad, &value); + 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); + touch_pad_read_raw_data(pad, &value); } #else if (component->iir_filter_enabled_()) { uint16_t temp_value = 0; - touch_pad_read_raw_data(pad, &temp_value); + 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); + touch_pad_read_raw_data(pad, &value); } #endif From da0f3c6cceebafcd0aa065f15534c05b094eb6c4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 10:12:56 -0500 Subject: [PATCH 12/79] fixes --- esphome/components/esp32_touch/esp32_touch.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index c79c751155..7e9bf1b2a9 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -490,7 +490,7 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { touch_pad_filter_read_smooth(pad, &value); } else { // Use low-level HAL function when filter is not configured - touch_pad_read_raw_data(pad, &value); + value = touch_ll_read_raw_data(pad); } #else if (component->iir_filter_enabled_()) { @@ -499,7 +499,7 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { value = temp_value; } else { // Use low-level HAL function when filter is not enabled - touch_pad_read_raw_data(pad, &value); + value = touch_ll_read_raw_data(pad); } #endif From c6ed88073256689f32d6719ffa49472d554eed64 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 10:19:25 -0500 Subject: [PATCH 13/79] fixes --- .../components/esp32_touch/esp32_touch.cpp | 43 +++---------------- 1 file changed, 5 insertions(+), 38 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 7e9bf1b2a9..2c9d94f5be 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -119,26 +119,12 @@ void ESP32TouchComponent::setup() { // Enable touch pad interrupt touch_pad_intr_enable(); - // Log which pads are configured and initialize their state - ESP_LOGI(TAG, "Configured touch pads:"); + // Initialize all sensors as not touched + // The ISR will immediately update with actual state for (auto *child : this->children_) { - uint32_t value = this->component_touch_pad_read(child->get_touch_pad()); - ESP_LOGI(TAG, " Touch Pad %d: threshold=%d, current value=%d", (int) child->get_touch_pad(), - (int) child->get_threshold(), (int) value); - - // Initialize the sensor state based on current value -#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 - - child->last_state_ = is_touched; - child->publish_initial_state(is_touched); - - if (is_touched) { - this->last_touch_time_[child->get_touch_pad()] = App.get_loop_component_start_time(); - } + // Initialize as not touched + child->last_state_ = false; + child->publish_initial_state(false); } } @@ -343,25 +329,6 @@ void ESP32TouchComponent::dump_config() { } } -uint32_t ESP32TouchComponent::component_touch_pad_read(touch_pad_t tp) { -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) - uint32_t value = 0; - if (this->filter_configured_()) { - touch_pad_filter_read_smooth(tp, &value); - } else { - touch_pad_read_raw_data(tp, &value); - } -#else - uint16_t value = 0; - if (this->iir_filter_enabled_()) { - touch_pad_read_filtered(tp, &value); - } else { - touch_pad_read(tp, &value); - } -#endif - return value; -} - 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; From 0bd4c333bdf432842ef9180dea175f598e27205c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 10:21:41 -0500 Subject: [PATCH 14/79] cleanup --- esphome/components/esp32_touch/esp32_touch.cpp | 8 -------- esphome/components/esp32_touch/esp32_touch.h | 2 -- 2 files changed, 10 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 2c9d94f5be..15b7d64109 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -118,14 +118,6 @@ void ESP32TouchComponent::setup() { // Enable touch pad interrupt touch_pad_intr_enable(); - - // Initialize all sensors as not touched - // The ISR will immediately update with actual state - for (auto *child : this->children_) { - // Initialize as not touched - child->last_state_ = false; - child->publish_initial_state(false); - } } void ESP32TouchComponent::dump_config() { diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 218ac26453..22a7db45ca 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -55,8 +55,6 @@ class ESP32TouchComponent : public Component { void set_iir_filter(uint32_t iir_filter) { this->iir_filter_ = iir_filter; } #endif - uint32_t component_touch_pad_read(touch_pad_t tp); - void setup() override; void dump_config() override; void loop() override; From 5fca1be44ddf00d964c558d5d6734703be129535 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 10:27:22 -0500 Subject: [PATCH 15/79] fixes --- esphome/components/esp32_touch/esp32_touch.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 15b7d64109..f2ae585d24 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -379,12 +379,19 @@ void ESP32TouchComponent::loop() { continue; } - if (child->last_state_) { - uint32_t last_time = this->last_touch_time_[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_LOGD(TAG, "Touch Pad '%s' state: OFF (initial)", child->get_name().c_str()); + } else if (child->last_state_) { uint32_t time_diff = now - last_time; // Check if we haven't seen this pad recently - if (last_time == 0 || time_diff > this->release_timeout_ms_) { + 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); From ce701d3c31b3c3862bac9060459c64db78781fe8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 10:29:11 -0500 Subject: [PATCH 16/79] fixes --- esphome/components/esp32_touch/esp32_touch.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index f2ae585d24..dafd1e3f28 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -336,9 +336,7 @@ void ESP32TouchComponent::loop() { // Process any queued touch events from interrupts TouchPadEvent event; - uint32_t processed_pads = 0; // Bitmask of pads we processed events for while (xQueueReceive(this->touch_queue_, &event, 0) == pdTRUE) { - processed_pads |= (1 << event.pad); // Find the corresponding sensor for (auto *child : this->children_) { if (child->get_touch_pad() == event.pad) { @@ -373,12 +371,6 @@ void ESP32TouchComponent::loop() { for (auto *child : this->children_) { touch_pad_t pad = child->get_touch_pad(); - - // Skip if we just processed an event for this pad - if ((processed_pads >> pad) & 0x01) { - continue; - } - 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 From 5ab78ec4616607dd6223346c08e7ac39c76b357e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 10:30:58 -0500 Subject: [PATCH 17/79] fixes --- esphome/components/esp32_touch/esp32_touch.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index dafd1e3f28..6acab9dd7a 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -354,8 +354,8 @@ void ESP32TouchComponent::loop() { if (new_state != child->last_state_) { child->last_state_ = new_state; child->publish_state(new_state); - ESP_LOGD(TAG, "Touch Pad '%s' state: %s (value: %" PRIu32 ", threshold: %" PRIu32 ")", - child->get_name().c_str(), new_state ? "ON" : "OFF", event.value, child->get_threshold()); + ESP_LOGD(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 ", threshold: %" PRIu32 ")", + child->get_name().c_str(), event.value, child->get_threshold()); } break; } From 1332e24a2c45614b9f1f69bb858fb2e4c710d476 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 10:31:13 -0500 Subject: [PATCH 18/79] fixes --- esphome/components/esp32_touch/esp32_touch.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 6acab9dd7a..dafd1e3f28 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -354,8 +354,8 @@ void ESP32TouchComponent::loop() { if (new_state != child->last_state_) { child->last_state_ = new_state; child->publish_state(new_state); - ESP_LOGD(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 ", threshold: %" PRIu32 ")", - child->get_name().c_str(), event.value, child->get_threshold()); + ESP_LOGD(TAG, "Touch Pad '%s' state: %s (value: %" PRIu32 ", threshold: %" PRIu32 ")", + child->get_name().c_str(), new_state ? "ON" : "OFF", event.value, child->get_threshold()); } break; } From 74e70278e282d00e6ccf631873806ef4505e5b45 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 10:34:59 -0500 Subject: [PATCH 19/79] fixes --- esphome/components/esp32_touch/esp32_touch.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index dafd1e3f28..4191ad5c2d 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -354,8 +354,10 @@ void ESP32TouchComponent::loop() { if (new_state != child->last_state_) { child->last_state_ = new_state; child->publish_state(new_state); - ESP_LOGD(TAG, "Touch Pad '%s' state: %s (value: %" PRIu32 ", threshold: %" PRIu32 ")", - child->get_name().c_str(), new_state ? "ON" : "OFF", event.value, child->get_threshold()); + // 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 + ESP_LOGD(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 ", threshold: %" PRIu32 ")", + child->get_name().c_str(), event.value, child->get_threshold()); } break; } @@ -379,7 +381,7 @@ void ESP32TouchComponent::loop() { child->publish_state(false); this->last_touch_time_[pad] = 1; // Mark as "initial state published" ESP_LOGD(TAG, "Touch Pad '%s' state: OFF (initial)", child->get_name().c_str()); - } else if (child->last_state_) { + } 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 @@ -387,7 +389,7 @@ void ESP32TouchComponent::loop() { // Haven't seen this pad recently, assume it's released child->last_state_ = false; child->publish_state(false); - this->last_touch_time_[pad] = 0; + this->last_touch_time_[pad] = 1; // Reset to "initial published" state ESP_LOGD(TAG, "Touch Pad '%s' state: OFF (timeout)", child->get_name().c_str()); } } From a16d321e1a925e5e4123256bd45e302a330c1c6f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 10:38:47 -0500 Subject: [PATCH 20/79] downgrade logging --- esphome/components/esp32_touch/esp32_touch.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 4191ad5c2d..76532704ad 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -356,7 +356,7 @@ void ESP32TouchComponent::loop() { 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 - ESP_LOGD(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 ", threshold: %" PRIu32 ")", + ESP_LOGV(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 ", threshold: %" PRIu32 ")", child->get_name().c_str(), event.value, child->get_threshold()); } break; @@ -380,7 +380,7 @@ void ESP32TouchComponent::loop() { 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_LOGD(TAG, "Touch Pad '%s' state: OFF (initial)", child->get_name().c_str()); + 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; @@ -390,7 +390,7 @@ void ESP32TouchComponent::loop() { child->last_state_ = false; child->publish_state(false); this->last_touch_time_[pad] = 1; // Reset to "initial published" state - ESP_LOGD(TAG, "Touch Pad '%s' state: OFF (timeout)", child->get_name().c_str()); + ESP_LOGV(TAG, "Touch Pad '%s' state: OFF (timeout)", child->get_name().c_str()); } } } From 8b6aa319bfa43ab3aa7f2f4d3d5e6c73a54b1ceb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 10:57:46 -0500 Subject: [PATCH 21/79] 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 22/79] 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 23/79] 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 24/79] 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 25/79] 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 26/79] 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 27/79] 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 28/79] 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 29/79] 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 30/79] 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 31/79] 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 32/79] 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 33/79] 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 34/79] 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 35/79] 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 36/79] 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 37/79] 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 38/79] 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 39/79] 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 40/79] 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 41/79] 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 42/79] 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 43/79] 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 44/79] 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 45/79] 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 46/79] 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 47/79] 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 48/79] 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 49/79] 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 50/79] 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 51/79] 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 52/79] 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 53/79] 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 54/79] 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 55/79] 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 56/79] 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 57/79] 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 58/79] 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 59/79] 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 60/79] 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 61/79] 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 62/79] 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 63/79] 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 64/79] 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 65/79] 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 66/79] 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 67/79] 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 68/79] 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 69/79] 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 70/79] 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) { From d1e6b8dd10463181210b3e653daf8ce6f860a284 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 18:33:27 -0500 Subject: [PATCH 71/79] comment --- esphome/components/esp32_touch/esp32_touch_v1.cpp | 2 +- esphome/components/esp32_touch/esp32_touch_v2.cpp | 4 ++-- 2 files 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 b5e8e2c0c9..6cdfe5e43a 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -179,7 +179,7 @@ void ESP32TouchComponent::loop() { void ESP32TouchComponent::on_shutdown() { touch_pad_intr_disable(); touch_pad_isr_deregister(touch_isr_handler, this); - this->cleanup_touch_queue(); + this->cleanup_touch_queue_(); if (this->iir_filter_enabled_()) { touch_pad_filter_stop(); diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index d5c7b9db9b..9e7c219ca4 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -76,7 +76,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)); - this->cleanup_touch_queue(); + this->cleanup_touch_queue_(); this->mark_failed(); return; } @@ -299,7 +299,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); - this->cleanup_touch_queue(); + this->cleanup_touch_queue_(); // Configure wakeup pads if any are set this->configure_wakeup_pads(); From d1edb1e32ad9af4ce857e48dd8f75f1b3e2ed827 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 18:34:00 -0500 Subject: [PATCH 72/79] fix --- esphome/components/esp32_touch/esp32_touch_v2.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 9e7c219ca4..39e5d38ea0 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -12,7 +12,7 @@ static const char *const TAG = "esp32_touch"; void ESP32TouchComponent::setup() { // Create queue for touch events first - if (!this->create_touch_queue()) { + if (!this->create_touch_queue_()) { return; } @@ -302,7 +302,7 @@ void ESP32TouchComponent::on_shutdown() { this->cleanup_touch_queue_(); // Configure wakeup pads if any are set - this->configure_wakeup_pads(); + this->configure_wakeup_pads_(); } void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { From ee6b2ba6c6046f6db0ffa03542f8e74a6d59340f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 19:56:12 -0500 Subject: [PATCH 73/79] fixes --- .../components/esp32_touch/esp32_touch_v2.cpp | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 39e5d38ea0..331021feed 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -260,6 +260,27 @@ void ESP32TouchComponent::loop() { if (event.intr_mask & TOUCH_PAD_INTR_MASK_TIMEOUT) { // Resume measurement after timeout touch_pad_timeout_resume(); + + // For timeout events, we should check if the pad is actually touched + // Timeout occurs when a pad stays above threshold for too long + for (auto *child : this->children_) { + if (child->get_touch_pad() != event.pad) { + continue; + } + + // Read current value to determine actual state + uint32_t value = this->read_touch_value(event.pad); + bool is_touched = value > child->get_threshold(); + + // Update state if changed + if (child->last_state_ != is_touched) { + child->last_state_ = is_touched; + child->publish_state(is_touched); + ESP_LOGD(TAG, "Touch Pad '%s' %s via timeout (value: %d %s threshold: %d)", child->get_name().c_str(), + is_touched ? "touched" : "released", value, is_touched ? ">" : "<=", child->get_threshold()); + } + break; + } continue; } From 599e28e1cb7bc11e662449ff6701487ba4deabad Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 20:02:39 -0500 Subject: [PATCH 74/79] fixes --- esphome/components/esp32_touch/esp32_touch.h | 3 + .../components/esp32_touch/esp32_touch_v2.cpp | 68 ++++++------------- 2 files changed, 25 insertions(+), 46 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 041549c519..d7b1a8068f 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -160,6 +160,9 @@ class ESP32TouchComponent : public Component { // 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; + + // Helper to read touch value and update state for a given child + void check_and_update_touch_state_(ESP32TouchBinarySensor *child); #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 331021feed..dd009ada0f 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -10,6 +10,22 @@ namespace esp32_touch { static const char *const TAG = "esp32_touch"; +// Helper to read touch value and update state for a given child +void ESP32TouchComponent::check_and_update_touch_state_(ESP32TouchBinarySensor *child) { + // Read current touch value + uint32_t value = this->read_touch_value(child->get_touch_pad()); + + // ESP32-S2/S3 v2: Touch is detected when value > threshold + bool is_touched = value > child->get_threshold(); + + if (child->last_state_ != is_touched) { + child->last_state_ = is_touched; + child->publish_state(is_touched); + ESP_LOGD(TAG, "Touch Pad '%s' %s (value: %d %s threshold: %d)", child->get_name().c_str(), + is_touched ? "touched" : "released", value, is_touched ? ">" : "<=", child->get_threshold()); + } +} + void ESP32TouchComponent::setup() { // Create queue for touch events first if (!this->create_touch_queue_()) { @@ -103,15 +119,11 @@ void ESP32TouchComponent::setup() { // Read current 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 - // Therefore: touched = (value > threshold) - // This is opposite to original ESP32 v1 where touched = (value < threshold) + // Set initial state and publish 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()); } @@ -260,56 +272,20 @@ void ESP32TouchComponent::loop() { if (event.intr_mask & TOUCH_PAD_INTR_MASK_TIMEOUT) { // Resume measurement after timeout touch_pad_timeout_resume(); - - // For timeout events, we should check if the pad is actually touched - // Timeout occurs when a pad stays above threshold for too long - for (auto *child : this->children_) { - if (child->get_touch_pad() != event.pad) { - continue; - } - - // Read current value to determine actual state - uint32_t value = this->read_touch_value(event.pad); - bool is_touched = value > child->get_threshold(); - - // Update state if changed - if (child->last_state_ != is_touched) { - child->last_state_ = is_touched; - child->publish_state(is_touched); - ESP_LOGD(TAG, "Touch Pad '%s' %s via timeout (value: %d %s threshold: %d)", child->get_name().c_str(), - is_touched ? "touched" : "released", value, is_touched ? ">" : "<=", child->get_threshold()); - } - break; - } + // For timeout events, always check the current state + } else if (!(event.intr_mask & (TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE))) { + // Skip if not an active/inactive/timeout event continue; } - // Skip if not an active/inactive event - if (!(event.intr_mask & (TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE))) { - continue; - } - - bool is_touch_event = (event.intr_mask & TOUCH_PAD_INTR_MASK_ACTIVE) != 0; - // 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 = this->read_touch_value(event.pad); - - child->last_state_ = is_touch_event; - child->publish_state(is_touch_event); - // 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()); + // Check and update state + this->check_and_update_touch_state_(child); break; } } From bc6b72a4226583a84a5c72ed17f0492589ddb522 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Jun 2025 20:14:51 -0500 Subject: [PATCH 75/79] tweaks --- esphome/components/esp32_touch/esp32_touch.h | 3 ++ .../components/esp32_touch/esp32_touch_v2.cpp | 46 ++++++++++++------- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index d7b1a8068f..42424c472c 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -161,6 +161,9 @@ class ESP32TouchComponent : public Component { // based on the filter configuration uint32_t read_touch_value(touch_pad_t pad) const; + // Helper to update touch state with a known state + void update_touch_state_(ESP32TouchBinarySensor *child, bool is_touched); + // Helper to read touch value and update state for a given child void check_and_update_touch_state_(ESP32TouchBinarySensor *child); #endif diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index dd009ada0f..1aa21db5a7 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -10,7 +10,20 @@ namespace esp32_touch { static const char *const TAG = "esp32_touch"; -// Helper to read touch value and update state for a given child +// Helper to update touch state with a known state +void ESP32TouchComponent::update_touch_state_(ESP32TouchBinarySensor *child, bool is_touched) { + if (child->last_state_ != is_touched) { + // Read value for logging + uint32_t value = this->read_touch_value(child->get_touch_pad()); + + child->last_state_ = is_touched; + child->publish_state(is_touched); + ESP_LOGD(TAG, "Touch Pad '%s' %s (value: %d %s threshold: %d)", child->get_name().c_str(), + is_touched ? "touched" : "released", value, is_touched ? ">" : "<=", child->get_threshold()); + } +} + +// Helper to read touch value and update state for a given child (used for timeout events) void ESP32TouchComponent::check_and_update_touch_state_(ESP32TouchBinarySensor *child) { // Read current touch value uint32_t value = this->read_touch_value(child->get_touch_pad()); @@ -18,12 +31,7 @@ void ESP32TouchComponent::check_and_update_touch_state_(ESP32TouchBinarySensor * // ESP32-S2/S3 v2: Touch is detected when value > threshold bool is_touched = value > child->get_threshold(); - if (child->last_state_ != is_touched) { - child->last_state_ = is_touched; - child->publish_state(is_touched); - ESP_LOGD(TAG, "Touch Pad '%s' %s (value: %d %s threshold: %d)", child->get_name().c_str(), - is_touched ? "touched" : "released", value, is_touched ? ">" : "<=", child->get_threshold()); - } + this->update_touch_state_(child, is_touched); } void ESP32TouchComponent::setup() { @@ -97,6 +105,13 @@ void ESP32TouchComponent::setup() { return; } + // Set thresholds for each pad BEFORE starting FSM + for (auto *child : this->children_) { + if (child->get_threshold() != 0) { + touch_pad_set_thresh(child->get_touch_pad(), child->get_threshold()); + } + } + // Enable interrupts touch_pad_intr_enable(static_cast(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE | TOUCH_PAD_INTR_MASK_TIMEOUT)); @@ -107,13 +122,6 @@ 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()); - } - } - // Read initial states after all hardware is initialized for (auto *child : this->children_) { // Read current value @@ -284,8 +292,14 @@ void ESP32TouchComponent::loop() { continue; } - // Check and update state - this->check_and_update_touch_state_(child); + if (event.intr_mask & TOUCH_PAD_INTR_MASK_TIMEOUT) { + // For timeout events, we need to read the value to determine state + this->check_and_update_touch_state_(child); + } else { + // For ACTIVE/INACTIVE events, the interrupt tells us the state + bool is_touched = (event.intr_mask & TOUCH_PAD_INTR_MASK_ACTIVE) != 0; + this->update_touch_state_(child, is_touched); + } break; } } From 82518b351d4249b0f39403014963ece90fa59f2a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 13 Jun 2025 10:11:38 -0500 Subject: [PATCH 76/79] lint --- esphome/components/esp32_touch/esp32_touch_common.cpp | 2 +- esphome/components/esp32_touch/esp32_touch_v2.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_common.cpp b/esphome/components/esp32_touch/esp32_touch_common.cpp index 0119e28acf..39769ed37a 100644 --- a/esphome/components/esp32_touch/esp32_touch_common.cpp +++ b/esphome/components/esp32_touch/esp32_touch_common.cpp @@ -47,7 +47,7 @@ bool ESP32TouchComponent::create_touch_queue_() { #endif if (this->touch_queue_ == nullptr) { - ESP_LOGE(TAG, "Failed to create touch event queue of size %d", queue_size); + ESP_LOGE(TAG, "Failed to create touch event queue of size %" PRIu32, (uint32_t) queue_size); this->mark_failed(); return false; } diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index 1aa21db5a7..a34353e22a 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -18,7 +18,7 @@ void ESP32TouchComponent::update_touch_state_(ESP32TouchBinarySensor *child, boo child->last_state_ = is_touched; child->publish_state(is_touched); - ESP_LOGD(TAG, "Touch Pad '%s' %s (value: %d %s threshold: %d)", child->get_name().c_str(), + ESP_LOGD(TAG, "Touch Pad '%s' %s (value: %" PRIu32 " %s threshold: %" PRIu32 ")", child->get_name().c_str(), is_touched ? "touched" : "released", value, is_touched ? ">" : "<=", child->get_threshold()); } } From e6dc10a4408c9def725772bf5fb4fd99f2ac80ec Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 14 Jun 2025 21:34:21 -0500 Subject: [PATCH 77/79] address review comments --- esphome/components/esp32_touch/esp32_touch.h | 8 ++++- .../components/esp32_touch/esp32_touch_v1.cpp | 35 ++++++++++--------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 42424c472c..70de25cdfa 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -19,6 +19,11 @@ namespace esp32_touch { // - 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. +// +// INTERRUPT BEHAVIOR: +// - ESP32 v1: Interrupts fire when ANY pad is touched and continue while touched. +// Releases are detected by timeout since hardware doesn't generate release interrupts. +// - ESP32-S2/S3 v2: Interrupts can be configured per-pad with both touch and release events. static const uint32_t SETUP_MODE_LOG_INTERVAL_MS = 250; @@ -105,11 +110,12 @@ class ESP32TouchComponent : public Component { 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) + // 2. ISR only writes timestamps, main loop only reads // 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}; + bool initial_state_published_[TOUCH_PAD_MAX] = {false}; uint32_t release_timeout_ms_{1500}; uint32_t release_check_interval_ms_{50}; uint32_t iir_filter_{0}; diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index 6cdfe5e43a..5a7b2cec4b 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -147,29 +147,25 @@ void ESP32TouchComponent::loop() { for (auto *child : this->children_) { 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_) { - 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 - uint32_t time_diff = now - last_time; + // Handle initial state publication after startup + if (!this->initial_state_published_[pad]) { + // Check if enough time has passed since startup + if (now > this->release_timeout_ms_) { + child->publish_initial_state(false); + this->initial_state_published_[pad] = true; + ESP_LOGV(TAG, "Touch Pad '%s' state: OFF (initial)", child->get_name().c_str()); + } + } else if (child->last_state_) { + // Pad is currently in touched state - check for release timeout + // Using subtraction handles 32-bit rollover correctly + uint32_t time_diff = now - this->last_touch_time_[pad]; // 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()); } } @@ -195,6 +191,13 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { touch_pad_clear_status(); + // INTERRUPT BEHAVIOR: On ESP32 v1 hardware, the interrupt fires when ANY configured + // touch pad detects a touch (value goes below threshold). The hardware does NOT + // generate interrupts on release - only on touch events. + // The interrupt will continue to fire periodically (based on sleep_cycle) as long + // as any pad remains touched. This allows us to detect both new touches and + // continued touches, but releases must be detected by timeout in the main loop. + // 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 From f576e8f6351caf0dc5f56a359190fc8869def072 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 14 Jun 2025 21:40:16 -0500 Subject: [PATCH 78/79] remove cap --- esphome/components/esp32_touch/esp32_touch_v1.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index 5a7b2cec4b..e805bf5f4c 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -68,8 +68,11 @@ void ESP32TouchComponent::setup() { 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); + // Check for releases at 1/4 the timeout interval + // Since the ESP32 v1 hardware doesn't generate release interrupts, we must poll + // for releases in the main loop. Checking at 1/4 the timeout interval provides + // a good balance between responsiveness and efficiency. + this->release_check_interval_ms_ = this->release_timeout_ms_ / 4; // Enable touch pad interrupt touch_pad_intr_enable(); From 6cbd1479c6bdb351799b327423ecff4a99cf2dc2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 30 Jun 2025 13:46:47 -0500 Subject: [PATCH 79/79] loop --- .../components/esp32_touch/esp32_touch_v1.cpp | 17 +++++++++++++++++ .../components/esp32_touch/esp32_touch_v2.cpp | 10 ++++++++++ 2 files changed, 27 insertions(+) diff --git a/esphome/components/esp32_touch/esp32_touch_v1.cpp b/esphome/components/esp32_touch/esp32_touch_v1.cpp index e805bf5f4c..7b46cd9280 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -148,6 +148,7 @@ void ESP32TouchComponent::loop() { } last_release_check = now; + size_t pads_off = 0; for (auto *child : this->children_) { touch_pad_t pad = child->get_touch_pad(); @@ -158,6 +159,7 @@ void ESP32TouchComponent::loop() { child->publish_initial_state(false); this->initial_state_published_[pad] = true; ESP_LOGV(TAG, "Touch Pad '%s' state: OFF (initial)", child->get_name().c_str()); + pads_off++; } } else if (child->last_state_) { // Pad is currently in touched state - check for release timeout @@ -170,9 +172,23 @@ void ESP32TouchComponent::loop() { child->last_state_ = false; child->publish_state(false); ESP_LOGV(TAG, "Touch Pad '%s' state: OFF (timeout)", child->get_name().c_str()); + pads_off++; } + } else { + // Pad is already off + pads_off++; } } + + // Disable the loop to save CPU cycles when all pads are off and not in setup mode. + // The loop will be re-enabled by the ISR when any touch pad is touched. + // v1 hardware limitations require us to check all pads are off because: + // - v1 only generates interrupts on touch events (not releases) + // - We must poll for release timeouts in the main loop + // - We can only safely disable when no pads need timeout monitoring + if (pads_off == this->children_.size() && !this->setup_mode_) { + this->disable_loop(); + } } void ESP32TouchComponent::on_shutdown() { @@ -242,6 +258,7 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { // 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); + component->enable_loop_soon_any_context(); if (x_higher_priority_task_woken) { portYIELD_FROM_ISR(); } diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index a34353e22a..b9e3da52c4 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -303,6 +303,15 @@ void ESP32TouchComponent::loop() { break; } } + if (!this->setup_mode_) { + // Disable the loop to save CPU cycles when not in setup mode. + // The loop will be re-enabled by the ISR when any touch event occurs. + // Unlike v1, we don't need to check if all pads are off because: + // - v2 hardware generates interrupts for both touch AND release events + // - We don't need to poll for timeouts or releases + // - All state changes are interrupt-driven + this->disable_loop(); + } } void ESP32TouchComponent::on_shutdown() { @@ -327,6 +336,7 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { // Send event to queue for processing in main loop xQueueSendFromISR(component->touch_queue_, &event, &x_higher_priority_task_woken); + component->enable_loop_soon_any_context(); if (x_higher_priority_task_woken) { portYIELD_FROM_ISR();