Skip to content

Commit 6beceb1

Browse files
authored
Merge pull request #157 from jakub-k-dev/bugfix/fix-button-click-detection
Bugfix/fix button click detection
2 parents 647363c + 4be50bc commit 6beceb1

File tree

1 file changed

+114
-36
lines changed

1 file changed

+114
-36
lines changed

ESPHome/TX-Ultimate-Easy-ESPHome_hw_buttons.yaml

Lines changed: 114 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,15 @@ substitutions:
2424
BUTTON_3_ACTION_FAILSAFE_TEXT: "Relay 3 (API failsafe only)"
2525
BUTTON_4_ACTION_FAILSAFE_TEXT: "Relay 4 (API failsafe only)"
2626

27-
BUTTON_CLICK_MIN_LENGTH: '50' # The minimum duration the click should last, in msec
28-
BUTTON_CLICK_MAX_LENGTH: '350' # The maximum duration the click should last, in msec
29-
BUTTON_MULTI_CLICK_DELAY: '250' # The time to wait for another click, in msec
30-
BUTTON_PRESS_TIMEOUT: '10000' # Ignore if button is pressed for longer than this time, in msec
31-
BUTTON_LONG_PRESS_DELAY: '800' # The time to wait to consider a long press, in msec
27+
BUTTON_MULTI_CLICK_DELAY: "250" # The time to wait for another click, in msec
28+
BUTTON_PRESS_TIMEOUT: "10000" # Ignore if button is pressed for longer than this time, in msec
29+
BUTTON_LONG_PRESS_DELAY: "500" # The time to wait to consider a long press, in msec
3230

3331
# Button numbers constants
34-
BUTTON_1_ID: '1'
35-
BUTTON_2_ID: '2'
36-
BUTTON_3_ID: '3'
37-
BUTTON_4_ID: '4'
32+
BUTTON_1_ID: "1"
33+
BUTTON_2_ID: "2"
34+
BUTTON_3_ID: "3"
35+
BUTTON_4_ID: "4"
3836

3937
TAG_HW_BUTTONS: tx_ultimate_easy.hw.buttons
4038

@@ -69,29 +67,36 @@ globals:
6967
- id: button_actions_packed
7068
type: uint16_t
7169
restore_value: true
72-
initial_value: '4369' # Binary: 0001000100010001 = all buttons set to option 1 (default action)
70+
initial_value: "4369" # Binary: 0001000100010001 = all buttons set to option 1 (default action)
7371
# Bit layout: [button4(4)][button3(4)][button2(4)][button1(4)]
7472
# Each button uses 4 bits: 0=None, 1=Toggle, 2=Failsafe, 3-15=Reserved for future options
7573

7674
- id: button_press_button
7775
type: uint8_t
7876
restore_value: false
79-
initial_value: '0'
77+
initial_value: "0"
8078

8179
- id: button_press_position
8280
type: uint8_t
8381
restore_value: false
84-
initial_value: '0'
82+
initial_value: "0"
8583

8684
- id: button_press_start_time
8785
type: uint32_t
8886
restore_value: false
89-
initial_value: '0'
87+
initial_value: "0"
88+
# Used for: current press duration (press → release)
89+
90+
- id: last_release_time
91+
type: uint32_t
92+
restore_value: false
93+
initial_value: "0"
94+
# Used for: inter-click gap timing (time since last release)
9095

9196
- id: click_counter
9297
type: uint8_t
9398
restore_value: false
94-
initial_value: '0'
99+
initial_value: "0"
95100

96101
script:
97102
- id: !extend boot_initialize
@@ -180,6 +185,7 @@ script:
180185
id(button_press_button) = 0;
181186
id(button_press_position) = 0;
182187
id(button_press_start_time) = 0;
188+
id(last_release_time) = 0;
183189
id(click_counter) = 0;
184190
buttons_release->execute();
185191
@@ -210,6 +216,18 @@ script:
210216
}
211217
}
212218
219+
- id: buttons_reset_state
220+
mode: restart
221+
then:
222+
- lambda: |-
223+
// Reset all button-related global state
224+
id(button_press_start_time) = 0;
225+
id(last_release_time) = 0;
226+
id(click_counter) = 0;
227+
id(button_press_button) = 0;
228+
id(button_press_position) = 0;
229+
buttons_release->execute();
230+
213231
- id: !extend dump_config
214232
then:
215233
- lambda: |-
@@ -275,7 +293,7 @@ script:
275293

276294
- id: !extend touch_on_multi_touch_release
277295
then:
278-
- script.execute: buttons_release
296+
- script.execute: buttons_reset_state
279297

280298
- id: !extend touch_on_press
281299
then:
@@ -291,15 +309,46 @@ script:
291309
position: uint8_t
292310
then:
293311
- lambda: |-
294-
id(button_press_start_time) = millis();
295-
id(button_press_button) = button;
296-
// Update counters
297-
if (id(button_press_position) == position) {
298-
id(click_counter)++;
299-
} else {
312+
uint32_t current_time = millis();
313+
// Check if we should start a new click sequence:
314+
// 1. Previous cycle completed (start_time was reset to 0)
315+
// 2. Position changed
316+
// 3. Enough time has passed since last release (more than multi-click delay)
317+
bool start_new_sequence = false;
318+
if (id(button_press_start_time) == 0) {
319+
// Previous cycle completed
320+
start_new_sequence = true;
321+
} else if (id(button_press_position) != position) {
322+
// Position changed
323+
start_new_sequence = true;
324+
} else if (id(last_release_time) > 0) {
325+
// Check if enough time has passed since last release (for inter-click gap)
326+
// Use runtime value from HA instead of compile-time constant
327+
uint32_t gap_window = (uint32_t) nr_button_multi_click_delay->state;
328+
uint32_t time_since_last_release;
329+
// Handle millis() overflow (wraps after ~49 days)
330+
if (current_time >= id(last_release_time)) {
331+
time_since_last_release = current_time - id(last_release_time);
332+
} else {
333+
// Overflow occurred, calculate correctly
334+
time_since_last_release = (UINT32_MAX - id(last_release_time)) + current_time + 1;
335+
}
336+
if (time_since_last_release > gap_window) {
337+
// Too much time passed since last release, start fresh
338+
start_new_sequence = true;
339+
}
340+
}
341+
342+
if (start_new_sequence) {
300343
id(click_counter) = 1;
301344
id(button_press_position) = position;
345+
} else {
346+
// Same position and within multi-click window, increment counter
347+
id(click_counter)++;
302348
}
349+
// Set press start time for this press (for duration calculation)
350+
id(button_press_start_time) = current_time;
351+
id(button_press_button) = button;
303352
// Update binary sensor
304353
switch (button) {
305354
case 1:
@@ -321,34 +370,64 @@ script:
321370
- lambda: |-
322371
uint32_t current_time = millis();
323372
buttons_release->execute();
324-
if (id(button_press_start_time) > 0 and
325-
id(button_press_start_time) < current_time) {
326-
uint32_t press_duration = current_time - id(button_press_start_time);
327-
// Handle overflow (optional, since it's unlikely to happen here)
373+
if (id(button_press_start_time) > 0) {
374+
uint32_t press_duration;
375+
// Handle millis() overflow (wraps after ~49 days)
376+
if (current_time >= id(button_press_start_time)) {
377+
press_duration = current_time - id(button_press_start_time);
378+
} else {
379+
// Overflow occurred, calculate correctly
380+
press_duration = (UINT32_MAX - id(button_press_start_time)) + current_time + 1;
381+
}
328382
ESP_LOGI("${TAG_HW_BUTTONS}", "Button press duration: %" PRIu32 " ms", press_duration);
329-
if (press_duration < ${BUTTON_CLICK_MIN_LENGTH}) {
330-
ESP_LOGW("${TAG_HW_BUTTONS}", "Ignoring button press (too short)");
331-
} else if (press_duration >= ${BUTTON_CLICK_MIN_LENGTH} and
332-
press_duration <= ${BUTTON_CLICK_MAX_LENGTH}) { // Short/normal click
333-
button_click_event->execute(id(button_press_button), id(click_counter));
334-
} else if (press_duration >= ${BUTTON_LONG_PRESS_DELAY} and press_duration <= ${BUTTON_PRESS_TIMEOUT}) {
335-
button_action->execute(id(button_press_button), "long_press", 1);
383+
// Update last release time for inter-click gap detection
384+
id(last_release_time) = current_time;
385+
if (press_duration <= ${BUTTON_LONG_PRESS_DELAY}) { // Short/normal click
386+
// Validate button is still valid (in case swipe/multi-touch happened)
387+
if (id(button_press_button) > 0 && id(button_press_button) <= 4) {
388+
// Don't reset start_time here - let button_action reset it after multi-click delay
389+
// This allows subsequent presses during the delay to increment the counter
390+
button_click_event->execute(id(button_press_button), id(click_counter));
391+
} else {
392+
ESP_LOGW("${TAG_HW_BUTTONS}", "Invalid button state, resetting");
393+
id(button_press_start_time) = 0;
394+
id(last_release_time) = 0;
395+
id(click_counter) = 0;
396+
id(button_press_button) = 0;
397+
}
398+
} else if (press_duration > ${BUTTON_LONG_PRESS_DELAY} and press_duration <= ${BUTTON_PRESS_TIMEOUT}) {
399+
// Long press: anything longer than BUTTON_LONG_PRESS_DELAY (and within timeout) is a long press
400+
// Validate button is still valid (in case swipe/multi-touch happened)
401+
if (id(button_press_button) > 0 && id(button_press_button) <= 4) {
402+
button_action->execute(id(button_press_button), "long_press", 1);
403+
} else {
404+
ESP_LOGW("${TAG_HW_BUTTONS}", "Invalid button state during long press, resetting");
405+
id(button_press_start_time) = 0;
406+
id(last_release_time) = 0;
407+
id(click_counter) = 0;
408+
id(button_press_button) = 0;
409+
}
336410
} else if (press_duration > ${BUTTON_PRESS_TIMEOUT}) { // Timeout or invalid
337411
ESP_LOGW("${TAG_HW_BUTTONS}",
338412
"Button press cancelled or timed out after ${BUTTON_PRESS_TIMEOUT} ms");
413+
id(button_press_start_time) = 0;
414+
id(last_release_time) = 0;
415+
id(click_counter) = 0;
339416
}
340417
} else {
341418
ESP_LOGW("${TAG_HW_BUTTONS}", "Press event timestamp not recorded yet");
419+
id(button_press_start_time) = 0;
420+
id(last_release_time) = 0;
421+
id(click_counter) = 0;
342422
}
343-
id(button_press_start_time) = 0;
344423
345424
- id: !extend touch_swipe_${ID_SWIPE_LEFT_OR_UP_TEXT}
346425
then:
347-
- script.execute: buttons_release
426+
- script.execute: buttons_reset_state
348427

349428
- id: !extend touch_swipe_${ID_SWIPE_RIGHT_OR_DOWN_TEXT}
350429
then:
351-
- script.execute: buttons_release
430+
- script.execute: buttons_reset_state
352431

353432
select:
354433
- &button_select_action_base
@@ -416,4 +495,3 @@ select:
416495
- lambda: |-
417496
id(button_actions_packed) = (id(button_actions_packed) & ~(0x0F << 12)) |
418497
((id(sl_button_4_action).active_index().value_or(1) & 0x0F) << 12);
419-
...

0 commit comments

Comments
 (0)