mirror of
https://github.com/esphome/esphome.git
synced 2025-09-15 01:32:19 +01:00
preen
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
#include <driver/touch_sensor.h>
|
#include <driver/touch_sensor.h>
|
||||||
#include <freertos/FreeRTOS.h>
|
#include <freertos/FreeRTOS.h>
|
||||||
#include <freertos/queue.h>
|
#include <freertos/queue.h>
|
||||||
|
#include <freertos/ringbuf.h>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace esp32_touch {
|
namespace esp32_touch {
|
||||||
@@ -83,13 +84,16 @@ class ESP32TouchComponent : public Component {
|
|||||||
static constexpr uint32_t MINIMUM_RELEASE_TIME_MS = 100;
|
static constexpr uint32_t MINIMUM_RELEASE_TIME_MS = 100;
|
||||||
|
|
||||||
static void touch_isr_handler(void *arg);
|
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:
|
// Design note: last_touch_time_ does not require synchronization primitives because:
|
||||||
// 1. ESP32 guarantees atomic 32-bit aligned reads/writes
|
// 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 (except sentinel value 1)
|
||||||
// 3. Timing tolerance allows for occasional stale reads (50ms check interval)
|
// 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
|
// Using atomic/critical sections would add overhead without meaningful benefit
|
||||||
uint32_t last_touch_time_[TOUCH_PAD_MAX] = {0};
|
uint32_t last_touch_time_[TOUCH_PAD_MAX] = {0};
|
||||||
uint32_t release_timeout_ms_{1500};
|
uint32_t release_timeout_ms_{1500};
|
||||||
|
@@ -12,16 +12,19 @@
|
|||||||
#include "hal/touch_sensor_ll.h"
|
#include "hal/touch_sensor_ll.h"
|
||||||
// Include for RTC clock frequency
|
// Include for RTC clock frequency
|
||||||
#include "soc/rtc.h"
|
#include "soc/rtc.h"
|
||||||
|
// Include FreeRTOS ring buffer
|
||||||
|
#include "freertos/ringbuf.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace esp32_touch {
|
namespace esp32_touch {
|
||||||
|
|
||||||
static const char *const TAG = "esp32_touch";
|
static const char *const TAG = "esp32_touch";
|
||||||
|
|
||||||
struct TouchPadEventV1 {
|
// Structure for a single pad's state in the ring buffer
|
||||||
touch_pad_t pad;
|
struct TouchPadState {
|
||||||
uint32_t value;
|
uint8_t pad; // touch_pad_t
|
||||||
bool is_touched;
|
uint32_t value; // Current reading
|
||||||
|
bool is_touched; // Touch state
|
||||||
};
|
};
|
||||||
|
|
||||||
void ESP32TouchComponent::setup() {
|
void ESP32TouchComponent::setup() {
|
||||||
@@ -30,14 +33,19 @@ void ESP32TouchComponent::setup() {
|
|||||||
touch_pad_init();
|
touch_pad_init();
|
||||||
touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER);
|
touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER);
|
||||||
|
|
||||||
// Create queue for touch events
|
// Create ring buffer for touch events
|
||||||
size_t queue_size = this->children_.size() * 4;
|
// Size calculation: We need space for multiple snapshots
|
||||||
if (queue_size < 8)
|
// Each snapshot contains: array of TouchPadState structures
|
||||||
queue_size = 8;
|
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));
|
// Allow for 4 snapshots in the buffer to handle normal operation and bursts
|
||||||
if (this->touch_queue_ == nullptr) {
|
size_t buffer_size = snapshot_size * 4;
|
||||||
ESP_LOGE(TAG, "Failed to create touch event queue of size %d", queue_size);
|
|
||||||
|
// 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();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -65,8 +73,8 @@ void ESP32TouchComponent::setup() {
|
|||||||
esp_err_t err = touch_pad_isr_register(touch_isr_handler, this);
|
esp_err_t err = touch_pad_isr_register(touch_isr_handler, this);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "Failed to register touch ISR: %s", esp_err_to_name(err));
|
ESP_LOGE(TAG, "Failed to register touch ISR: %s", esp_err_to_name(err));
|
||||||
vQueueDelete(this->touch_queue_);
|
vRingbufferDelete(this->ring_buffer_handle_);
|
||||||
this->touch_queue_ = nullptr;
|
this->ring_buffer_handle_ = nullptr;
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -114,33 +122,44 @@ void ESP32TouchComponent::loop() {
|
|||||||
this->setup_mode_last_log_print_ = now;
|
this->setup_mode_last_log_print_ = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process any queued touch events from interrupts
|
// Process ring buffer entries
|
||||||
TouchPadEventV1 event;
|
size_t item_size;
|
||||||
while (xQueueReceive(this->touch_queue_, &event, 0) == pdTRUE) {
|
TouchPadState *pad_states;
|
||||||
// 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
|
// Receive all available items from ring buffer (non-blocking)
|
||||||
bool new_state = event.is_touched;
|
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
|
// Process each pad in the snapshot
|
||||||
if (new_state) {
|
for (size_t i = 0; i < num_pads; i++) {
|
||||||
this->last_touch_time_[event.pad] = now;
|
const TouchPadState &pad_state = pad_states[i];
|
||||||
|
|
||||||
|
// Find the corresponding sensor
|
||||||
|
for (auto *child : this->children_) {
|
||||||
|
if (child->get_touch_pad() == static_cast<touch_pad_t>(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
|
// Check for released pads periodically
|
||||||
@@ -184,8 +203,10 @@ void ESP32TouchComponent::loop() {
|
|||||||
void ESP32TouchComponent::on_shutdown() {
|
void ESP32TouchComponent::on_shutdown() {
|
||||||
touch_pad_intr_disable();
|
touch_pad_intr_disable();
|
||||||
touch_pad_isr_deregister(touch_isr_handler, this);
|
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;
|
bool is_wakeup_source = false;
|
||||||
@@ -218,7 +239,23 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) {
|
|||||||
|
|
||||||
touch_pad_clear_status();
|
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_) {
|
for (auto *child : component->children_) {
|
||||||
touch_pad_t pad = child->get_touch_pad();
|
touch_pad_t pad = child->get_touch_pad();
|
||||||
|
|
||||||
@@ -238,21 +275,24 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store pad state
|
||||||
|
pad_states[pad_index].pad = static_cast<uint8_t>(pad);
|
||||||
|
pad_states[pad_index].value = value;
|
||||||
// For original ESP32, lower value means touched
|
// 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
|
pad_index++;
|
||||||
TouchPadEventV1 event;
|
}
|
||||||
event.pad = pad;
|
|
||||||
event.value = value;
|
|
||||||
event.is_touched = is_touched;
|
|
||||||
|
|
||||||
// Send to queue from ISR
|
// Adjust size if we skipped any pads
|
||||||
BaseType_t x_higher_priority_task_woken = pdFALSE;
|
size_t actual_size = pad_index * sizeof(TouchPadState);
|
||||||
xQueueSendFromISR(component->touch_queue_, &event, &x_higher_priority_task_woken);
|
|
||||||
if (x_higher_priority_task_woken) {
|
// Send the item
|
||||||
portYIELD_FROM_ISR();
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user