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_) {