# Touch Button Sensor
`touch_button_sensor` combines `touch_sensor_lowlevel` v1.0 sampling with
`touch_sensor_fsm` state detection. The component owns the low-level
instance by default and reports debounced active/inactive changes through a
callback.
This README is the application-facing contract: it documents the public API,
configuration ranges/defaults, units, ownership, and lifecycle. The
[touch button design guide](../../design_guide/touch_button_sensor_design.md)
documents the adapter pipeline, execution contexts, diagnostic record schemas,
and visualization boundary; it does not add public fields to this component.
## Features
- Runtime discovery of the effective sample count and sample period
- Multi-channel and multi-frequency touch processing
- Debounced active/inactive callbacks
- Touch amplitude and current state queries
- Optional debug snapshots for noise and EMI diagnostics
- Optional reuse of an existing lowlevel instance
## Configuration reference
`touch_button_config_t` is intentionally smaller than the lower-layer
configuration. The adapter resolves the effective lowlevel topology first and
then creates the FSM in user-push mode.
| Field | Valid input | Default or normalization | Notes |
| --- | --- | --- | --- |
| `channel_num` | `1..SOC_TOUCH_SENSOR_NUM` | Required | Must match the number of entries in both arrays below. |
| `channel_list` | Supported, unique touch channel IDs | Required; copied by `create()` | Order is preserved; the first channel is the lowlevel startup-alignment reference when this component owns lowlevel. |
| `thresholds` | One value per channel, `1..UINT32_MAX - 1` normalized fused counts | Required; no implicit threshold | This is an absolute FSM decision-domain threshold, not a raw count or permille ratio. |
| `sample_period_ms` | `0` or a positive full-scan period in milliseconds | `0` selects lowlevel's 11 ms (`<=8` channels) or 17 ms (`>8` channels) default | Ignored when `skip_lowlevel_init` is true. A requested period must fit the complete scan. |
| `sample_cfg_num` | `0` or a lowlevel-supported sample count | `0` selects lowlevel's target default; the effective value must be `1..FSM_MAX_FREQ` | Ignored when `skip_lowlevel_init` is true. The button adapter rejects an effective count larger than `FSM_MAX_FREQ` (`3`). |
| `debounce_active` | Non-negative frame count | `0` selects the FSM one-frame default | Number of consecutive active observations required before publishing active. |
| `debounce_inactive` | Non-negative frame count | `0` selects the FSM one-frame default | Number of consecutive release observations required before publishing inactive. |
| `skip_lowlevel_init` | `false` or `true` | `false` | `false` transfers lowlevel ownership to the button component; `true` reuses an already-created, started ordinary-touch instance. |
The channel and threshold arrays are copied by `create()` and may be released
after it returns. `touch_button_sensor_create()` returns an error before the
handle is published when channels are duplicated, thresholds are invalid, the
lowlevel instance is unavailable, or the effective sample count is outside
the FSM range.
The default configuration is therefore suitable for a normal component user:
set the channel list and tune one absolute normalized threshold per channel;
leave the sample and debounce fields at zero unless the board needs an explicit
override. Use `touch_button_sensor_get_data()` on the target to observe the
decision-domain amplitude while tuning a threshold.
## Basic Usage
```c
static void button_callback(touch_button_handle_t handle, uint32_t channel,
touch_state_t state, void *user_data)
{
uint32_t amplitude = 0;
ESP_ERROR_CHECK(touch_button_sensor_get_data(handle, channel, &litude));
printf("channel=%" PRIu32 " state=%d amplitude=%" PRIu32 "\n",
channel, state, amplitude);
}
uint32_t channels[] = {1, 2, 3, 4};
uint32_t thresholds[] = {70, 70, 70, 70};
touch_button_config_t config = {
.channel_num = 4,
.channel_list = channels,
.thresholds = thresholds,
.sample_period_ms = 20,
.sample_cfg_num = 0,
.debounce_active = 0,
.debounce_inactive = 0,
};
touch_button_handle_t button = NULL;
ESP_ERROR_CHECK(touch_button_sensor_create(&config, &button,
button_callback, NULL));
while (true) {
ESP_ERROR_CHECK(touch_button_sensor_handle_events(button));
vTaskDelay(pdMS_TO_TICKS(10));
}
```
`thresholds` contains an absolute normalized trigger threshold per channel.
An initial value near 1.5% of the 5000 startup reference is approximately 70
decision-domain counts; use `touch_button_sensor_get_data()` while tuning a board and
electrode. A zero sample count lets lowlevel select its target-specific default; the
effective count and period are passed to the FSM automatically. Zero debounce
counts select the FSM defaults. Keep the first configured electrode untouched
and the board stable while the low-level startup alignment and initial state
setup run.
When the FSM limiter fields are left at zero, the automatic positive and
negative limiter is `1.5 * threshold_active` for one frequency and
`3 * threshold_active` for multiple frequencies; it is not `2 * threshold`.
With the ESP32-S31 default of three sample configurations and a threshold of
500, the effective limiter is therefore 1500 normalized counts.
On hardware, lowlevel `start()` aligns the first configured channel to
approximately 5000 before normal sampling. A stability check may reject an
unstable startup window, but it cannot guarantee detection of a finger held on
the electrode during startup. If alignment cannot complete, its `esp_err_t` is
returned by lowlevel `start()`. Linux replay keeps supplied raw values
unchanged and starts without hardware alignment.
When `skip_lowlevel_init` is true, the application must create and start the
lowlevel singleton itself. Every configured button channel must already exist
in that instance. The button component registers only its callbacks and does
not stop or delete the external lowlevel instance.
## Public API and data contract
| API | Result | Availability and lifetime |
| --- | --- | --- |
| `touch_button_sensor_create()` | Creates the adapter, starts owned lowlevel/FSM, and registers per-channel callbacks | The configuration arrays are copied. The handle is returned only after the lower layers are ready. |
| `touch_button_sensor_handle_events()` | Processes queued complete frames and may invoke the user callback | Call periodically from task context; it is the FSM processing boundary. |
| `touch_button_sensor_get_data()` | Returns `max(fused_filtered, 0)` in normalized decision-domain counts | Valid for configured channels after creation; negative FSM amplitudes are reported as zero. |
| `touch_button_sensor_get_state()` | Returns `TOUCH_STATE_ACTIVE` or `TOUCH_STATE_INACTIVE` | Returns the last callback-published stable state; release debounce does not appear as an early inactive result. |
| `touch_button_sensor_delete()` | Stops/deletes the FSM and releases lowlevel only when owned by the adapter | Stop the application event loop before delete. A `NULL` handle is accepted as a no-op. |
The callback reports only stable state transitions. Its `handle`, channel,
state, and user argument are valid for the callback duration; query the
current positive amplitude with `touch_button_sensor_get_data()` if needed.
The adapter does not expose raw frames, baselines, residuals, fusion
intermediates, queue occupancy, debounce counters, calibration trials, or
driver registers. Those values remain in their owning component's debug/test
path.
The published button component does not expose `TOUCH_BUTTON_SENSOR_DEBUG` or
select an FSM debug build. It uses only the public `touch_sensor_fsm` interface,
so it does not include or depend on `private_include/touch_sensor_fsm_private.h`.
Raw, baseline, noise and FSM event snapshots are available only in the
repository's `examples/fsm` diagnostic application, which explicitly enables
the FSM debug option and adds the private include path for that application.
See [`examples/touch/touch_button_sensor`](https://github.com/espressif/esp-iot-solution/tree/master/examples/touch/touch_button_sensor) for a complete
application.
## Dependencies
- ESP-IDF 5.5, 6.0, or 6.1 (5.5/6.0: ESP32-S2, ESP32-S3, ESP32-P4; 6.1
also adds ESP32-H4 and ESP32-S31)
- `touch_sensor_fsm` `^0.9.0`
- `touch_sensor_lowlevel` `^1.0.1`
idf.py add-dependency "espressif/touch_button_sensor^0.3.0"