This commit is contained in:
J. Nick Koston 2025-06-30 14:28:31 -05:00
parent 0df454481e
commit f76ce5d3bb
No known key found for this signature in database
4 changed files with 98 additions and 67 deletions

View File

@ -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<ESP32TouchBinarySensor *> 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

View File

@ -4,6 +4,8 @@
#include "esphome/core/log.h"
#include <cinttypes>
#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

View File

@ -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() {

View File

@ -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() {