Rebuild Button click engine

The native engine from ESPHome cannot be used here as a button press event is triggered on touch, which would trigger the click even on swipe.
Other projects solved this by tracking only single click and only at release, so we decided to implement this in a different way, so we can handle multiple or long-clicks also.
This should finally solve #14, but some test is required to ensure nothing else was broken.
This commit is contained in:
Edward Firmo
2024-12-21 21:14:41 +01:00
parent 7f1c362a50
commit 5c4581563e
2 changed files with 119 additions and 124 deletions

View File

@@ -20,111 +20,53 @@ substitutions:
BUTTON_3_ACTION_TEXT: "Relay 3 (toggle)" BUTTON_3_ACTION_TEXT: "Relay 3 (toggle)"
BUTTON_4_ACTION_TEXT: "Relay 4 (toggle)" BUTTON_4_ACTION_TEXT: "Relay 4 (toggle)"
binary_sensor: BUTTON_CLICK_MIN_LENGTH: '50' # The minimum duration the click should last, in msec
- id: bs_button_1 BUTTON_CLICK_MAX_LENGTH: '350' # The maximum duration the click should last, in msec
name: Button 1 BUTTON_MULTI_CLICK_DELAY: '500' # The time to wait for another click, in msec
icon: mdi:gesture-tap-box BUTTON_PRESS_TIMEOUT: '10000' # Ignore if buttor is pressed for longer than this time, in msec
internal: false BUTTON_LONG_PRESS_DELAY: '800' # The time to wait to consider a long press, in msec
platform: template
on_click:
then:
- script.execute:
id: button_action
component: bs_button_1
event: click
on_double_click:
then:
- script.execute:
id: button_action
component: bs_button_1
event: double_click
on_multi_click:
- timing: &long_click-timing
- ON for at least 0.8s
invalid_cooldown: ${invalid_cooldown}
then:
- script.execute:
id: button_action
component: bs_button_1
event: long_click
- id: bs_button_2 binary_sensor:
- &binary_sensor_button_base
id: bs_button_2
name: Button 2 name: Button 2
icon: mdi:gesture-tap-box icon: mdi:gesture-tap-box
internal: true
platform: template platform: template
on_click: internal: true
then:
- script.execute:
id: button_action
component: bs_button_2
event: click
on_double_click:
then:
- script.execute:
id: button_action
component: bs_button_2
event: double_click
on_multi_click:
- timing: *long_click-timing
invalid_cooldown: ${invalid_cooldown}
then:
- script.execute:
id: button_action
component: bs_button_2
event: long_click
- id: bs_button_3 - id: bs_button_3
name: Button 3 name: Button 3
icon: mdi:gesture-tap-box <<: *binary_sensor_button_base
internal: true
platform: template
on_click:
then:
- script.execute:
id: button_action
component: bs_button_3
event: click
on_double_click:
then:
- script.execute:
id: button_action
component: bs_button_3
event: double_click
on_multi_click:
- timing: *long_click-timing
invalid_cooldown: ${invalid_cooldown}
then:
- script.execute:
id: button_action
component: bs_button_3
event: long_click
- id: bs_button_4 - id: bs_button_4
name: Button 4 name: Button 4
icon: mdi:gesture-tap-box <<: *binary_sensor_button_base
internal: true
platform: template - id: bs_button_1
on_click: name: Button 1
then: internal: false
- script.execute: <<: *binary_sensor_button_base
id: button_action
component: bs_button_4 globals:
event: click - id: button_press_button
on_double_click: type: uint8_t
then: restore_value: false
- script.execute: initial_value: '0'
id: button_action
component: bs_button_4 - id: button_press_position
event: double_click type: uint8_t
on_multi_click: restore_value: false
- timing: *long_click-timing initial_value: '0'
invalid_cooldown: ${invalid_cooldown}
then: - id: button_press_start_time
- script.execute: type: uint32_t
id: button_action restore_value: false
component: bs_button_4 initial_value: '0'
event: long_click
- id: click_counter
type: uint8_t
restore_value: false
initial_value: '0'
script: script:
- id: !extend boot_initialize - id: !extend boot_initialize
@@ -162,9 +104,15 @@ script:
parameters: parameters:
component: string component: string
event: string event: string
then: # There's nothing here so far then:
# Extended by: # Extended by:
# - core_api # - core_api
- lambda: |-
ESP_LOGI("core_hw_buttons", "Button '%s' action: '%s'", component.c_str(), event.c_str());
id(button_press_button) = 0;
id(click_counter) = 0;
id(button_press_start_time) = 0;
buttons_release->execute();
- id: buttons_release - id: buttons_release
mode: restart mode: restart
@@ -195,34 +143,81 @@ script:
touch_x: uint8_t touch_x: uint8_t
then: then:
- lambda: |- - lambda: |-
id(button_press_start_time) = millis();
id(button_press_position) = touch_x;
uint8_t button = 0;
auto model_index = sl_tx_model_gang->active_index(); auto model_index = sl_tx_model_gang->active_index();
if (model_index.has_value()) { if (model_index.has_value()) {
uint8_t model_idx = model_index.value() + 1; const uint8_t model_idx = model_index.value() + 1; // Increment for 1-based indexing
switch (model_idx) { if (model_idx == 1) {
case 1: // 1 Gang button = 1; // Single button, always 1
bs_button_1->publish_state(true); } else {
break; const uint8_t step = 10 / model_idx; // Calculate step size for regions
case 2: // 2 Gang button = (touch_x / step) + 1; // Determine button region
if (touch_x <= 5) bs_button_1->publish_state(true); if (button > model_idx)
else bs_button_2->publish_state(true); button = model_idx; // Clamp to max button count
break; }
case 3: // 3 Gang }
if (touch_x <= 3) bs_button_1->publish_state(true); // Update binary sensor
else if (touch_x <= 7) bs_button_2->publish_state(true); switch (button) {
else bs_button_3->publish_state(true); case 1:
break; bs_button_1->publish_state(true);
case 4: // 4 Gang break;
if (touch_x <= 2) bs_button_1->publish_state(true); case 2:
else if (touch_x <= 5) bs_button_2->publish_state(true); bs_button_2->publish_state(true);
else if (touch_x <= 8) bs_button_3->publish_state(true); break;
else bs_button_4->publish_state(true); case 3:
break; bs_button_3->publish_state(true);
} break;
case 4:
bs_button_4->publish_state(true);
break;
}
// Update counters
if (id(button_press_button) == button) {
id(click_counter)++;
} else {
id(click_counter) = 1;
id(button_press_button) = button;
} }
- id: !extend touch_on_release - id: !extend touch_on_release
then: then:
- script.execute: buttons_release - lambda: |-
uint32_t current_time = millis();
buttons_release->execute();
if (id(button_press_start_time) > 0 and
id(button_press_start_time) < current_time) {
uint32_t press_duration = current_time - id(button_press_start_time);
// Handle overflow (optional, since it's unlikely to happen here)
ESP_LOGI("core_hw_buttons", "Button press duration: %" PRIu32 " ms", press_duration);
if (press_duration < ${BUTTON_CLICK_MIN_LENGTH}) {
ESP_LOGW("core_hw_buttons", "Ignoring button press (too short)");
} else if (press_duration >= ${BUTTON_CLICK_MIN_LENGTH} and
press_duration <= ${BUTTON_CLICK_MAX_LENGTH}) { // Short/normal click
button_click_event->execute();
} else if (press_duration >= ${BUTTON_LONG_PRESS_DELAY} and press_duration <= ${BUTTON_PRESS_TIMEOUT}) {
button_action->execute(("bs_button_" + std::to_string(id(button_press_button))).c_str(), "long_click");
} else if (press_duration > ${BUTTON_PRESS_TIMEOUT}) { // Timeout or invalid
ESP_LOGW("core_hw_buttons", "Button press cancelled or timed out after ${BUTTON_PRESS_TIMEOUT} ms");
}
} else {
ESP_LOGW("core_hw_buttons", "Press event timestamp not recorded yet");
}
id(button_press_start_time) = 0;
- id: button_click_event
mode: restart
then:
- delay:
milliseconds: ${BUTTON_MULTI_CLICK_DELAY}
- lambda: |-
button_action->execute(
("bs_button_" + std::to_string(id(button_press_button))).c_str(),
(id(click_counter) == 1 ? "click" :
(id(click_counter) == 2 ? "double_click" :
(std::to_string(id(click_counter)) + "_click").c_str()))
);
- id: !extend touch_swipe_left - id: !extend touch_swipe_left
then: then:

View File

@@ -233,8 +233,8 @@ tx_ultimate_easy:
ESP_LOGD("tx_ultimate_easy", " Position: %i", touch.x); ESP_LOGD("tx_ultimate_easy", " Position: %i", touch.x);
uart: uart:
id: uart_touch - id: uart_touch
tx_pin: GPIO19 tx_pin: GPIO19
rx_pin: GPIO22 rx_pin: GPIO22
baud_rate: 115200 baud_rate: 115200
... ...