ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
esp32_touch.cpp
Go to the documentation of this file.
1#ifdef USE_ESP32
2
3#include "esp32_touch.h"
5#include "esphome/core/log.h"
6#include "esphome/core/hal.h"
7
8#include <cinttypes>
9
11
12template<size_t N> static const char *lookup_str(const char *const (&table)[N], size_t index) {
13 return (index < N) ? table[index] : "UNKNOWN";
14}
15
16ESPHOME_LOG_TAG(TAG, "esp32_touch");
17
18static constexpr uint32_t SETUP_MODE_LOG_INTERVAL_MS = 250;
19static constexpr uint32_t INITIAL_STATE_DELAY_MS = 1500;
20static constexpr uint32_t ONESHOT_SCAN_COUNT = 3;
21static constexpr uint32_t ONESHOT_SCAN_TIMEOUT_MS = 2000;
22
23// V1: called from esp_timer context (software filter)
24// V2/V3: called from ISR context
25// xQueueSendFromISR is safe from both contexts.
26
27bool IRAM_ATTR ESP32TouchComponent::on_active_cb(touch_sensor_handle_t handle, const touch_active_event_data_t *event,
28 void *ctx) {
29 auto *comp = static_cast<ESP32TouchComponent *>(ctx);
30 TouchEvent te{event->chan_id, true};
31 BaseType_t higher = pdFALSE;
32 xQueueSendFromISR(comp->touch_queue_, &te, &higher);
33 comp->enable_loop_soon_any_context();
34 return higher == pdTRUE;
35}
36
37bool IRAM_ATTR ESP32TouchComponent::on_inactive_cb(touch_sensor_handle_t handle,
38 const touch_inactive_event_data_t *event, void *ctx) {
39 auto *comp = static_cast<ESP32TouchComponent *>(ctx);
40 TouchEvent te{event->chan_id, false};
41 BaseType_t higher = pdFALSE;
42 xQueueSendFromISR(comp->touch_queue_, &te, &higher);
43 comp->enable_loop_soon_any_context();
44 return higher == pdTRUE;
45}
46
48 if (!this->create_touch_queue_()) {
49 return;
50 }
51
52 // Create sample config - differs per hardware version
53#ifdef USE_ESP32_VARIANT_ESP32
54 touch_sensor_sample_config_t sample_cfg = TOUCH_SENSOR_V1_DEFAULT_SAMPLE_CONFIG(
56#elif defined(USE_ESP32_VARIANT_ESP32P4)
57 // div_num=8 (data scaling divisor), coarse_freq_tune=2, fine_freq_tune=2
58 touch_sensor_sample_config_t sample_cfg = TOUCH_SENSOR_V3_DEFAULT_SAMPLE_CONFIG(8, 2, 2);
59 sample_cfg.charge_times = this->charge_times_;
60#else
61 // ESP32-S2/S3 (V2)
62 touch_sensor_sample_config_t sample_cfg = TOUCH_SENSOR_V2_DEFAULT_SAMPLE_CONFIG(
64#endif
65
66 // Create controller
67 touch_sensor_config_t sens_cfg = TOUCH_SENSOR_DEFAULT_BASIC_CONFIG(1, &sample_cfg);
68 sens_cfg.meas_interval_us = this->meas_interval_us_;
69#ifndef USE_ESP32_VARIANT_ESP32
70 sens_cfg.max_meas_time_us = 0; // Disable measurement timeout (V2/V3 only)
71#endif
72
73 esp_err_t err = touch_sensor_new_controller(&sens_cfg, &this->sens_handle_);
74 if (err != ESP_OK) {
75 ESP_LOGE(TAG, "Failed to create touch controller: %s", esp_err_to_name(err));
77 this->mark_failed();
78 return;
79 }
80
81 // Create channels for all children
82 for (auto *child : this->children_) {
83 touch_channel_config_t chan_cfg = {};
84#ifdef USE_ESP32_VARIANT_ESP32
85 chan_cfg.abs_active_thresh[0] = child->get_threshold();
86 chan_cfg.charge_speed = TOUCH_CHARGE_SPEED_7;
87 chan_cfg.init_charge_volt = TOUCH_INIT_CHARGE_VOLT_DEFAULT;
88 chan_cfg.group = TOUCH_CHAN_TRIG_GROUP_BOTH;
89#elif defined(USE_ESP32_VARIANT_ESP32P4)
90 chan_cfg.active_thresh[0] = child->get_threshold();
91#else
92 // ESP32-S2/S3 (V2)
93 chan_cfg.active_thresh[0] = child->get_threshold();
94 chan_cfg.charge_speed = TOUCH_CHARGE_SPEED_7;
95 chan_cfg.init_charge_volt = TOUCH_INIT_CHARGE_VOLT_DEFAULT;
96#endif
97
98 err = touch_sensor_new_channel(this->sens_handle_, child->get_channel_id(), &chan_cfg, &child->chan_handle_);
99 if (err != ESP_OK) {
100 ESP_LOGE(TAG, "Failed to create touch channel %d: %s", child->get_channel_id(), esp_err_to_name(err));
101 this->cleanup_touch_queue_();
102 this->mark_failed();
103 return;
104 }
105 }
106
107 // Configure filter
108#ifdef USE_ESP32_VARIANT_ESP32
109 // Software filter is REQUIRED for V1 on_active/on_inactive callbacks
110 {
111 touch_sensor_filter_config_t filter_cfg = TOUCH_SENSOR_DEFAULT_FILTER_CONFIG();
112 if (this->iir_filter_enabled_()) {
113 filter_cfg.interval_ms = this->iir_filter_;
114 }
115 err = touch_sensor_config_filter(this->sens_handle_, &filter_cfg);
116 if (err != ESP_OK) {
117 ESP_LOGE(TAG, "Failed to configure filter: %s", esp_err_to_name(err));
118 this->cleanup_touch_queue_();
119 this->mark_failed();
120 return;
121 }
122 }
123#else
124 // V2/V3: Hardware benchmark filter
125 {
126 touch_sensor_filter_config_t filter_cfg = TOUCH_SENSOR_DEFAULT_FILTER_CONFIG();
127 if (this->filter_configured_) {
128 filter_cfg.benchmark.filter_mode = this->filter_mode_;
129 filter_cfg.benchmark.jitter_step = this->jitter_step_;
130 filter_cfg.benchmark.denoise_lvl = this->noise_threshold_;
131 filter_cfg.data.smooth_filter = this->smooth_level_;
132 filter_cfg.data.debounce_cnt = this->debounce_count_;
133 }
134 err = touch_sensor_config_filter(this->sens_handle_, &filter_cfg);
135 if (err != ESP_OK) {
136 ESP_LOGW(TAG, "Failed to configure filter: %s", esp_err_to_name(err));
137 }
138 }
139#endif
140
141#if SOC_TOUCH_SUPPORT_DENOISE_CHAN
142 if (this->denoise_configured_) {
143 touch_denoise_chan_config_t denoise_cfg = {};
144 denoise_cfg.charge_speed = TOUCH_CHARGE_SPEED_7;
145 denoise_cfg.init_charge_volt = TOUCH_INIT_CHARGE_VOLT_DEFAULT;
146 denoise_cfg.ref_cap = this->denoise_cap_level_;
147 denoise_cfg.resolution = this->denoise_grade_;
148 err = touch_sensor_config_denoise_channel(this->sens_handle_, &denoise_cfg);
149 if (err != ESP_OK) {
150 ESP_LOGW(TAG, "Failed to configure denoise: %s", esp_err_to_name(err));
151 }
152 }
153#endif
154
155#if SOC_TOUCH_SUPPORT_WATERPROOF
156 if (this->waterproof_configured_) {
157 touch_channel_handle_t guard_chan = nullptr;
158 for (auto *child : this->children_) {
159 if (child->get_channel_id() == this->waterproof_guard_ring_pad_) {
160 guard_chan = child->chan_handle_;
161 break;
162 }
163 }
164
165 touch_channel_handle_t shield_chan = nullptr;
166 touch_channel_config_t shield_cfg = {};
167#ifdef USE_ESP32_VARIANT_ESP32P4
168 shield_cfg.active_thresh[0] = 0;
169 err = touch_sensor_new_channel(this->sens_handle_, SOC_TOUCH_MAX_CHAN_ID, &shield_cfg, &shield_chan);
170#else
171 shield_cfg.active_thresh[0] = 0;
172 shield_cfg.charge_speed = TOUCH_CHARGE_SPEED_7;
173 shield_cfg.init_charge_volt = TOUCH_INIT_CHARGE_VOLT_DEFAULT;
174 err = touch_sensor_new_channel(this->sens_handle_, TOUCH_SHIELD_CHAN_ID, &shield_cfg, &shield_chan);
175#endif
176 if (err == ESP_OK) {
177 touch_waterproof_config_t wp_cfg = {};
178 wp_cfg.guard_chan = guard_chan;
179 wp_cfg.shield_chan = shield_chan;
180 wp_cfg.shield_drv = this->waterproof_shield_driver_;
181 wp_cfg.flags.immersion_proof = 1;
182 err = touch_sensor_config_waterproof(this->sens_handle_, &wp_cfg);
183 if (err != ESP_OK) {
184 ESP_LOGW(TAG, "Failed to configure waterproof: %s", esp_err_to_name(err));
185 }
186 } else {
187 ESP_LOGW(TAG, "Failed to create shield channel: %s", esp_err_to_name(err));
188 }
189 }
190#endif
191
192 // Configure wakeup pads before enabling (must be done in INIT state)
194
195 // Register callbacks
196 touch_event_callbacks_t cbs = {};
197 cbs.on_active = on_active_cb;
198 cbs.on_inactive = on_inactive_cb;
199 err = touch_sensor_register_callbacks(this->sens_handle_, &cbs, this);
200 if (err != ESP_OK) {
201 ESP_LOGE(TAG, "Failed to register callbacks: %s", esp_err_to_name(err));
202 this->cleanup_touch_queue_();
203 this->mark_failed();
204 return;
205 }
206
207 // Enable and start scanning
208 err = touch_sensor_enable(this->sens_handle_);
209 if (err != ESP_OK) {
210 ESP_LOGE(TAG, "Failed to enable touch sensor: %s", esp_err_to_name(err));
211 this->cleanup_touch_queue_();
212 this->mark_failed();
213 return;
214 }
215
216 // Do initial oneshot scans to populate baseline values
217 for (uint32_t i = 0; i < ONESHOT_SCAN_COUNT; i++) {
218 err = touch_sensor_trigger_oneshot_scanning(this->sens_handle_, ONESHOT_SCAN_TIMEOUT_MS);
219 App.feed_wdt(); // 3 scans with 2s timeout might exceed WDT, so feed it here to be safe
220 if (err != ESP_OK) {
221 ESP_LOGW(TAG, "Oneshot scan %" PRIu32 " failed: %s", i, esp_err_to_name(err));
222 }
223 }
224
225 err = touch_sensor_start_continuous_scanning(this->sens_handle_);
226 if (err != ESP_OK) {
227 ESP_LOGE(TAG, "Failed to start continuous scanning: %s", esp_err_to_name(err));
228 this->mark_failed();
229 return;
230 }
231}
232
234#if !defined(USE_ESP32_VARIANT_ESP32P4)
235 static constexpr const char *LV_STRS[] = {"0.5V", "0.6V", "0.7V", "0.8V"};
236 static constexpr const char *HV_STRS[] = {"0.9V", "1.0V", "1.1V", "1.2V", "1.4V", "1.5V", "1.6V", "1.7V",
237 "1.9V", "2.0V", "2.1V", "2.2V", "2.4V", "2.5V", "2.6V", "2.7V"};
238 const char *lv_s = lookup_str(LV_STRS, this->low_voltage_reference_);
239 const char *hv_s = lookup_str(HV_STRS, this->high_voltage_reference_);
240
241 ESP_LOGCONFIG(TAG,
242 "Config for ESP32 Touch Hub:\n"
243 " Measurement interval: %.1fus\n"
244 " Low Voltage Reference: %s\n"
245 " High Voltage Reference: %s",
246 this->meas_interval_us_, lv_s, hv_s);
247#else
248 ESP_LOGCONFIG(TAG,
249 "Config for ESP32 Touch Hub:\n"
250 " Measurement interval: %.1fus",
251 this->meas_interval_us_);
252#endif
253
254#ifdef USE_ESP32_VARIANT_ESP32
255 if (this->iir_filter_enabled_()) {
256 ESP_LOGCONFIG(TAG, " IIR Filter: %" PRIu32 "ms", this->iir_filter_);
257 } else {
258 ESP_LOGCONFIG(TAG, " IIR Filter: 10ms (default)");
259 }
260#else
261 if (this->filter_configured_) {
262 // TOUCH_BM_IIR_FILTER_256 only exists on V2, shifting JITTER's position
263 static constexpr const char *FILTER_STRS[] = {
264 "IIR_4", "IIR_8", "IIR_16", "IIR_32", "IIR_64", "IIR_128",
265#if SOC_TOUCH_SENSOR_VERSION == 2
266 "IIR_256",
267#endif
268 "JITTER",
269 };
270 static constexpr const char *SMOOTH_STRS[] = {"OFF", "IIR_2", "IIR_4", "IIR_8"};
271 const char *filter_s = lookup_str(FILTER_STRS, this->filter_mode_);
272 const char *smooth_s = lookup_str(SMOOTH_STRS, this->smooth_level_);
273 ESP_LOGCONFIG(TAG,
274 " Filter mode: %s\n"
275 " Debounce count: %" PRIu32 "\n"
276 " Noise threshold coefficient: %" PRIu32 "\n"
277 " Jitter filter step size: %" PRIu32 "\n"
278 " Smooth level: %s",
279 filter_s, this->debounce_count_, this->noise_threshold_, this->jitter_step_, smooth_s);
280 }
281
282#if SOC_TOUCH_SUPPORT_DENOISE_CHAN
283 if (this->denoise_configured_) {
284 static constexpr const char *GRADE_STRS[] = {"BIT12", "BIT10", "BIT8", "BIT4"};
285 static constexpr const char *CAP_STRS[] = {"5pF", "6.4pF", "7.8pF", "9.2pF", "10.6pF", "12pF", "13.4pF", "14.8pF"};
286 const char *grade_s = lookup_str(GRADE_STRS, this->denoise_grade_);
287 const char *cap_s = lookup_str(CAP_STRS, this->denoise_cap_level_);
288 ESP_LOGCONFIG(TAG,
289 " Denoise grade: %s\n"
290 " Denoise capacitance level: %s",
291 grade_s, cap_s);
292 }
293#endif
294#endif // !USE_ESP32_VARIANT_ESP32
295
296 if (this->setup_mode_) {
297 ESP_LOGCONFIG(TAG, " Setup Mode ENABLED");
298 }
299
300 for (auto *child : this->children_) {
301 LOG_BINARY_SENSOR(" ", "Touch Pad", child);
302 ESP_LOGCONFIG(TAG,
303 " Channel: %d\n"
304 " Threshold: %" PRIu32 "\n"
305 " Benchmark: %" PRIu32,
306 child->channel_id_, child->threshold_, child->benchmark_);
307 }
308}
309
312
313 // In setup mode, periodically log all pad values
315
316 // Process queued touch events from callbacks
317 TouchEvent event;
318 while (xQueueReceive(this->touch_queue_, &event, 0) == pdTRUE) {
319 for (auto *child : this->children_) {
320 if (child->get_channel_id() != event.chan_id) {
321 continue;
322 }
323
324 // Read current smooth value
325 uint32_t value = 0;
326 touch_channel_read_data(child->chan_handle_, TOUCH_CHAN_DATA_TYPE_SMOOTH, &value);
327 child->value_ = value;
328
329#ifndef USE_ESP32_VARIANT_ESP32
330 // V2/V3: also read benchmark
331 uint32_t benchmark = 0;
332 touch_channel_read_data(child->chan_handle_, TOUCH_CHAN_DATA_TYPE_BENCHMARK, &benchmark);
333 child->benchmark_ = benchmark;
334#endif
335
336 bool new_state = event.is_active;
337
338 if (new_state != child->last_state_) {
339 child->initial_state_published_ = true;
340 child->last_state_ = new_state;
341 child->publish_state(new_state);
342#ifdef USE_ESP32_VARIANT_ESP32
343 ESP_LOGV(TAG, "Touch Pad '%s' state: %s (value: %" PRIu32 ", threshold: %" PRIu32 ")",
344 LOG_STR_ARG(child->get_log_name()), ONOFF(new_state), value, child->get_threshold());
345#else
346 if (new_state) {
347 ESP_LOGV(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 ", benchmark: %" PRIu32 ", threshold: %" PRIu32 ")",
348 LOG_STR_ARG(child->get_log_name()), value, benchmark, child->get_threshold());
349 } else {
350 ESP_LOGV(TAG, "Touch Pad '%s' state: OFF", LOG_STR_ARG(child->get_log_name()));
351 }
352#endif
353 }
354 break;
355 }
356 }
357
358 // Publish initial OFF state for sensors that haven't received events yet
359 bool all_initial_published = true;
360 for (auto *child : this->children_) {
361 this->publish_initial_state_if_needed_(child, now);
362 if (!child->initial_state_published_) {
363 all_initial_published = false;
364 }
365 }
366
367 // Only disable loop once all initial states are published
368 if (!this->setup_mode_ && all_initial_published) {
369 this->disable_loop();
370 }
371}
372
374 if (this->sens_handle_ == nullptr)
375 return;
376
377 touch_sensor_stop_continuous_scanning(this->sens_handle_);
378 touch_sensor_disable(this->sens_handle_);
379
380 for (auto *child : this->children_) {
381 if (child->chan_handle_ != nullptr) {
382 touch_sensor_del_channel(child->chan_handle_);
383 child->chan_handle_ = nullptr;
384 }
385 }
386
387 touch_sensor_del_controller(this->sens_handle_);
388 this->sens_handle_ = nullptr;
389
390 this->cleanup_touch_queue_();
391}
392
394 size_t queue_size = this->children_.size() * 4;
395 if (queue_size < 8)
396 queue_size = 8;
397
398 this->touch_queue_ = xQueueCreate(queue_size, sizeof(TouchEvent));
399
400 if (this->touch_queue_ == nullptr) {
401 ESP_LOGE(TAG, "Failed to create touch event queue of size %" PRIu32, (uint32_t) queue_size);
402 this->mark_failed();
403 return false;
404 }
405 return true;
406}
407
409 if (this->touch_queue_) {
410 vQueueDelete(this->touch_queue_);
411 this->touch_queue_ = nullptr;
412 }
413}
414
416#if SOC_TOUCH_SUPPORT_SLEEP_WAKEUP
417 bool has_wakeup = false;
418 for (auto *child : this->children_) {
419 if (child->get_wakeup_threshold() != 0) {
420 has_wakeup = true;
421 break;
422 }
423 }
424
425 if (!has_wakeup)
426 return;
427
428#ifdef USE_ESP32_VARIANT_ESP32
429 // V1: Simple sleep config - threshold is set via channel config's abs_active_thresh
430 touch_sleep_config_t sleep_cfg = TOUCH_SENSOR_DEFAULT_DSLP_CONFIG();
431 sleep_cfg.deep_slp_sens_cfg = nullptr;
432 esp_err_t err = touch_sensor_config_sleep_wakeup(this->sens_handle_, &sleep_cfg);
433 if (err != ESP_OK) {
434 ESP_LOGW(TAG, "Failed to configure touch sleep wakeup: %s", esp_err_to_name(err));
435 }
436#else
437 // V2/V3: Need to specify a deep sleep channel and threshold
438 touch_channel_handle_t wakeup_chan = nullptr;
439 uint32_t wakeup_thresh = 0;
440 for (auto *child : this->children_) {
441 if (child->get_wakeup_threshold() != 0) {
442 wakeup_chan = child->chan_handle_;
443 wakeup_thresh = child->get_wakeup_threshold();
444 break; // Only one deep sleep wakeup channel is supported
445 }
446 }
447
448 if (wakeup_chan != nullptr) {
449 touch_sleep_config_t sleep_cfg = TOUCH_SENSOR_DEFAULT_DSLP_CONFIG();
450 sleep_cfg.deep_slp_chan = wakeup_chan;
451 sleep_cfg.deep_slp_thresh[0] = wakeup_thresh;
452 sleep_cfg.deep_slp_sens_cfg = nullptr;
453 esp_err_t err = touch_sensor_config_sleep_wakeup(this->sens_handle_, &sleep_cfg);
454 if (err != ESP_OK) {
455 ESP_LOGW(TAG, "Failed to configure touch sleep wakeup: %s", esp_err_to_name(err));
456 }
457 }
458#endif
459#endif // SOC_TOUCH_SUPPORT_SLEEP_WAKEUP
460}
461
463 if (this->setup_mode_ && now - this->setup_mode_last_log_print_ > SETUP_MODE_LOG_INTERVAL_MS) {
464 for (auto *child : this->children_) {
465 if (child->chan_handle_ == nullptr)
466 continue;
467
468 uint32_t smooth_value = 0;
469 touch_channel_read_data(child->chan_handle_, TOUCH_CHAN_DATA_TYPE_SMOOTH, &smooth_value);
470 child->value_ = smooth_value;
471
472#ifdef USE_ESP32_VARIANT_ESP32
473 ESP_LOGD(TAG, "Touch Pad '%s' (Ch%d): %" PRIu32, LOG_STR_ARG(child->get_log_name()), child->channel_id_,
474 smooth_value);
475#else
476 uint32_t benchmark = 0;
477 touch_channel_read_data(child->chan_handle_, TOUCH_CHAN_DATA_TYPE_BENCHMARK, &benchmark);
478 child->benchmark_ = benchmark;
479 int32_t difference = static_cast<int32_t>(smooth_value) - static_cast<int32_t>(benchmark);
480 ESP_LOGD(TAG,
481 "Touch Pad '%s' (Ch%d): value=%" PRIu32 ", benchmark=%" PRIu32 ", difference=%" PRId32
482 " (set threshold < %" PRId32 " to detect touch)",
483 LOG_STR_ARG(child->get_log_name()), child->channel_id_, smooth_value, benchmark, difference, difference);
484#endif
485 }
486 this->setup_mode_last_log_print_ = now;
487 }
488}
489
491 if (!child->initial_state_published_) {
492 if (now > INITIAL_STATE_DELAY_MS) {
493 child->publish_initial_state(false);
494 child->initial_state_published_ = true;
495 ESP_LOGV(TAG, "Touch Pad '%s' state: OFF (initial)", LOG_STR_ARG(child->get_log_name()));
496 }
497 }
498}
499
500} // namespace esphome::esp32_touch
501
502#endif // USE_ESP32
void feed_wdt()
Feed the task watchdog.
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
void mark_failed()
Mark this component as failed.
void disable_loop()
Disable this component's loop.
const LogString * get_log_name() const
Get the name for a "%s" log argument (LOG_STR_ARG).
void publish_initial_state(bool new_state)
Publish the initial state, this will not make the callback manager send callbacks and is meant only f...
Simple helper class to expose a touch pad value as a binary sensor.
touch_smooth_filter_mode_t smooth_level_
static bool on_inactive_cb(touch_sensor_handle_t handle, const touch_inactive_event_data_t *event, void *ctx)
touch_benchmark_filter_mode_t filter_mode_
void publish_initial_state_if_needed_(ESP32TouchBinarySensor *child, uint32_t now)
touch_denoise_chan_resolution_t denoise_grade_
static bool on_active_cb(touch_sensor_handle_t handle, const touch_active_event_data_t *event, void *ctx)
touch_denoise_chan_cap_t denoise_cap_level_
std::vector< ESP32TouchBinarySensor * > children_
ESPHOME_LOG_TAG(TAG, "esp32_touch")
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t
spi_device_handle_t handle