From f76ce5d3bbeb6b76c2e7f89df3efcf5876d2bf7b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 30 Jun 2025 14:28:31 -0500 Subject: [PATCH] dry --- esphome/components/esp32_touch/esp32_touch.h | 21 +++--- .../esp32_touch/esp32_touch_common.cpp | 68 +++++++++++++++++++ .../components/esp32_touch/esp32_touch_v1.cpp | 44 +++--------- .../components/esp32_touch/esp32_touch_v2.cpp | 32 +++------ 4 files changed, 98 insertions(+), 67 deletions(-) diff --git a/esphome/components/esp32_touch/esp32_touch.h b/esphome/components/esp32_touch/esp32_touch.h index 27a18526c4..be92f9a8ea 100644 --- a/esphome/components/esp32_touch/esp32_touch.h +++ b/esphome/components/esp32_touch/esp32_touch.h @@ -79,10 +79,21 @@ class ESP32TouchComponent : public Component { void cleanup_touch_queue_(); void configure_wakeup_pads_(); + // Helper methods for loop() logic + void process_setup_mode_logging_(uint32_t now); + bool should_check_for_releases_(uint32_t now); + void publish_initial_state_if_needed_(ESP32TouchBinarySensor *child, uint32_t now); + void check_and_disable_loop_if_all_released_(size_t pads_off); + void calculate_release_timeout_(); + // Common members std::vector children_; bool setup_mode_{false}; uint32_t setup_mode_last_log_print_{0}; + uint32_t last_release_check_{0}; + uint32_t release_timeout_ms_{1500}; + uint32_t release_check_interval_ms_{50}; + bool initial_state_published_[TOUCH_PAD_MAX] = {false}; // Common configuration parameters uint16_t sleep_cycle_{4095}; @@ -117,9 +128,6 @@ class ESP32TouchComponent : public Component { // 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}; bool iir_filter_enabled_() const { return this->iir_filter_ > 0; } @@ -129,10 +137,6 @@ class ESP32TouchComponent : public Component { static void touch_isr_handler(void *arg); QueueHandle_t touch_queue_{nullptr}; - // Timeout-based release detection (like v1) - uint32_t release_timeout_ms_{1500}; - uint32_t release_check_interval_ms_{50}; - private: // Touch event structure for ESP32 v2 (S2/S3) // Contains touch pad and interrupt mask for queue communication @@ -141,9 +145,8 @@ class ESP32TouchComponent : public Component { uint32_t intr_mask; }; - // Track last touch time and initial state for timeout-based release detection + // Track last touch time for timeout-based release detection uint32_t last_touch_time_[TOUCH_PAD_MAX] = {0}; - bool initial_state_published_[TOUCH_PAD_MAX] = {false}; protected: // Filter configuration diff --git a/esphome/components/esp32_touch/esp32_touch_common.cpp b/esphome/components/esp32_touch/esp32_touch_common.cpp index 39769ed37a..fd2cdfcbad 100644 --- a/esphome/components/esp32_touch/esp32_touch_common.cpp +++ b/esphome/components/esp32_touch/esp32_touch_common.cpp @@ -4,6 +4,8 @@ #include "esphome/core/log.h" #include +#include "soc/rtc.h" + namespace esphome { namespace esp32_touch { @@ -85,6 +87,72 @@ void ESP32TouchComponent::configure_wakeup_pads_() { } } +void ESP32TouchComponent::process_setup_mode_logging_(uint32_t now) { + if (this->setup_mode_ && now - this->setup_mode_last_log_print_ > SETUP_MODE_LOG_INTERVAL_MS) { + for (auto *child : this->children_) { +#ifdef USE_ESP32_VARIANT_ESP32 + ESP_LOGD(TAG, "Touch Pad '%s' (T%" PRIu32 "): %" PRIu32, child->get_name().c_str(), + (uint32_t) child->get_touch_pad(), child->value_); +#else + // 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); +#endif + } + this->setup_mode_last_log_print_ = now; + } +} + +bool ESP32TouchComponent::should_check_for_releases_(uint32_t now) { + if (now - this->last_release_check_ < this->release_check_interval_ms_) { + return false; + } + this->last_release_check_ = now; + return true; +} + +void ESP32TouchComponent::publish_initial_state_if_needed_(ESP32TouchBinarySensor *child, uint32_t now) { + touch_pad_t pad = child->get_touch_pad(); + 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()); + } + } +} + +void ESP32TouchComponent::check_and_disable_loop_if_all_released_(size_t pads_off) { + // Disable the loop to save CPU cycles when all pads are off and not in setup mode. + if (pads_off == this->children_.size() && !this->setup_mode_) { + this->disable_loop(); + } +} + +void ESP32TouchComponent::calculate_release_timeout_() { + // Calculate release timeout based on sleep cycle + // Design note: Hardware limitation - interrupts only fire reliably 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 + // Per ESP-IDF docs: t_sleep = sleep_cycle / SOC_CLK_RC_SLOW_FREQ_APPROX + + uint32_t rtc_freq = rtc_clk_slow_freq_get_hz(); + + // Calculate timeout as 3 sleep cycles + this->release_timeout_ms_ = (this->sleep_cycle_ * 1000 * 3) / rtc_freq; + + 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 + // Since hardware doesn't generate reliable 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; +} + } // 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 7b46cd9280..18d0739f47 100644 --- a/esphome/components/esp32_touch/esp32_touch_v1.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v1.cpp @@ -10,8 +10,6 @@ // 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 { @@ -59,20 +57,7 @@ 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 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 - // 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; + this->calculate_release_timeout_(); // Enable touch pad interrupt touch_pad_intr_enable(); @@ -98,13 +83,7 @@ void ESP32TouchComponent::loop() { const uint32_t now = App.get_loop_component_start_time(); // Print debug info for all pads in setup mode - 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_); - } - this->setup_mode_last_log_print_ = now; - } + this->process_setup_mode_logging_(now); // Process any queued touch events from interrupts // Note: Events are only sent by ISR for pads that were measured in that cycle (value != 0) @@ -142,25 +121,20 @@ 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_) { + if (!this->should_check_for_releases_(now)) { return; } - last_release_check = now; size_t pads_off = 0; for (auto *child : this->children_) { touch_pad_t pad = child->get_touch_pad(); // Handle initial state publication after startup + this->publish_initial_state_if_needed_(child, now); + 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()); - pads_off++; - } + // Not yet published, don't count as off + continue; } else if (child->last_state_) { // Pad is currently in touched state - check for release timeout // Using subtraction handles 32-bit rollover correctly @@ -186,9 +160,7 @@ void ESP32TouchComponent::loop() { // - 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(); - } + this->check_and_disable_loop_if_all_released_(pads_off); } void ESP32TouchComponent::on_shutdown() { diff --git a/esphome/components/esp32_touch/esp32_touch_v2.cpp b/esphome/components/esp32_touch/esp32_touch_v2.cpp index ee012bd878..0b629203fb 100644 --- a/esphome/components/esp32_touch/esp32_touch_v2.cpp +++ b/esphome/components/esp32_touch/esp32_touch_v2.cpp @@ -135,6 +135,9 @@ void ESP32TouchComponent::setup() { // Start FSM touch_pad_fsm_start(); + // Calculate release timeout based on sleep cycle + this->calculate_release_timeout_(); + // Initialize tracking arrays for (size_t i = 0; i < TOUCH_PAD_MAX; i++) { this->last_touch_time_[i] = 0; @@ -279,15 +282,7 @@ void ESP32TouchComponent::loop() { // This prevents false releases if we missed interrupts // 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; - } + this->process_setup_mode_logging_(now); // Process any queued touch events from interrupts TouchPadEventV2 event; @@ -320,25 +315,20 @@ void ESP32TouchComponent::loop() { } // Check for released pads periodically (like v1) - static uint32_t last_release_check = 0; - if (now - last_release_check < this->release_check_interval_ms_) { + if (!this->should_check_for_releases_(now)) { return; } - last_release_check = now; size_t pads_off = 0; for (auto *child : this->children_) { touch_pad_t pad = child->get_touch_pad(); // Handle initial state publication after startup + this->publish_initial_state_if_needed_(child, now); + 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()); - pads_off++; - } + // Not yet published, don't count as off + continue; } else if (child->last_state_) { // Pad is currently in touched state - check for release timeout // Using subtraction handles 32-bit rollover correctly @@ -369,9 +359,7 @@ void ESP32TouchComponent::loop() { // Disable the loop when all pads are off and not in setup mode (like v1) // We need to keep checking for timeouts, so only disable when all pads are confirmed off - if (pads_off == this->children_.size() && !this->setup_mode_) { - this->disable_loop(); - } + this->check_and_disable_loop_if_all_released_(pads_off); } void ESP32TouchComponent::on_shutdown() {