Files
TX-Ultimate-Easy/ESPHome/TX-Ultimate-Easy-ESPHome_core_hw_buttons.yaml
Edward Firmo 5c4581563e 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.
2024-12-21 21:14:41 +01:00

273 lines
9.5 KiB
YAML

####################################################################################################
##### TX Ultimate Easy for ESPHome #####
##### Repository: https://github.com/edwardtfn/TX-Ultimate-Easy #####
####################################################################################################
##### Purpose: ESPHome Core - Hardware - Buttons #####
####################################################################################################
##### Author: edwardtfn - https://github.com/edwardtfn - https://buymeacoffee.com/edwardfirmo #####
####################################################################################################
##### NOTE: #####
##### - Make changes ONLY if absolutely necessary and you have the required knowledge. #####
##### - For normal system use, modifications to this file are NOT required. #####
####################################################################################################
---
substitutions:
invalid_cooldown: 100ms
BUTTON_ACTION_NONE_TEXT: "None"
BUTTON_1_ACTION_TEXT: "Relay 1 (toggle)"
BUTTON_2_ACTION_TEXT: "Relay 2 (toggle)"
BUTTON_3_ACTION_TEXT: "Relay 3 (toggle)"
BUTTON_4_ACTION_TEXT: "Relay 4 (toggle)"
BUTTON_CLICK_MIN_LENGTH: '50' # The minimum duration the click should last, in msec
BUTTON_CLICK_MAX_LENGTH: '350' # The maximum duration the click should last, in msec
BUTTON_MULTI_CLICK_DELAY: '500' # The time to wait for another click, in msec
BUTTON_PRESS_TIMEOUT: '10000' # Ignore if buttor is pressed for longer than this time, in msec
BUTTON_LONG_PRESS_DELAY: '800' # The time to wait to consider a long press, in msec
binary_sensor:
- &binary_sensor_button_base
id: bs_button_2
name: Button 2
icon: mdi:gesture-tap-box
platform: template
internal: true
- id: bs_button_3
name: Button 3
<<: *binary_sensor_button_base
- id: bs_button_4
name: Button 4
<<: *binary_sensor_button_base
- id: bs_button_1
name: Button 1
internal: false
<<: *binary_sensor_button_base
globals:
- id: button_press_button
type: uint8_t
restore_value: false
initial_value: '0'
- id: button_press_position
type: uint8_t
restore_value: false
initial_value: '0'
- id: button_press_start_time
type: uint32_t
restore_value: false
initial_value: '0'
- id: click_counter
type: uint8_t
restore_value: false
initial_value: '0'
script:
- id: !extend boot_initialize
then:
- script.execute: boot_initialize_buttons
- id: boot_initialize_buttons
mode: restart
then:
- wait_until:
condition:
- lambda: return sl_tx_model_gang->active_index().has_value();
- lambda: |-
const uint8_t num_gangs = (sl_tx_model_gang->active_index().has_value()) ?
(sl_tx_model_gang->active_index().value() + 1) : 0;
if (num_gangs < 1 || num_gangs > 4) {
ESP_LOGE("core_hw_buttons", "Invalid number of gangs: %" PRIu8, num_gangs);
return;
}
bs_button_1->publish_state(false);
bs_button_1->set_internal(num_gangs < 1);
sl_button_1_action->set_internal(num_gangs < 1);
bs_button_2->publish_state(false);
bs_button_2->set_internal(num_gangs < 2);
sl_button_2_action->set_internal(num_gangs < 2);
bs_button_3->publish_state(false);
bs_button_3->set_internal(num_gangs < 3);
sl_button_3_action->set_internal(num_gangs < 3);
bs_button_4->publish_state(false);
bs_button_4->set_internal(num_gangs < 4);
sl_button_4_action->set_internal(num_gangs < 4);
- id: button_action
mode: parallel
parameters:
component: string
event: string
then:
# Extended by:
# - 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
mode: restart
then:
- lambda: |-
std::vector<binary_sensor::BinarySensor*> buttons = {
bs_button_1, bs_button_2, bs_button_3, bs_button_4
};
for (auto* button : buttons) {
if (button->state) {
button->publish_state(false);
}
}
- id: !extend touch_on_multi_touch_release
then:
- script.execute: buttons_release
- id: !extend touch_on_press
then:
- script.execute:
id: touch_on_press_buttons
touch_x: !lambda return touch_x;
- id: touch_on_press_buttons
mode: restart
parameters:
touch_x: uint8_t
then:
- 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();
if (model_index.has_value()) {
const uint8_t model_idx = model_index.value() + 1; // Increment for 1-based indexing
if (model_idx == 1) {
button = 1; // Single button, always 1
} else {
const uint8_t step = 10 / model_idx; // Calculate step size for regions
button = (touch_x / step) + 1; // Determine button region
if (button > model_idx)
button = model_idx; // Clamp to max button count
}
}
// Update binary sensor
switch (button) {
case 1:
bs_button_1->publish_state(true);
break;
case 2:
bs_button_2->publish_state(true);
break;
case 3:
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
then:
- 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
then:
- script.execute: buttons_release
- id: !extend touch_swipe_right
then:
- script.execute: buttons_release
select:
- &button_select_action_base
id: sl_button_1_action
name: Button 1 action
platform: template
options:
- "${BUTTON_ACTION_NONE_TEXT}"
- "${BUTTON_1_ACTION_TEXT}"
initial_option: "${BUTTON_1_ACTION_TEXT}"
optimistic: true
restore_value: true
internal: true
entity_category: config
disabled_by_default: false
icon: mdi:dip-switch
- id: sl_button_2_action
name: Button 2 action
platform: template
options:
- "${BUTTON_ACTION_NONE_TEXT}"
- "${BUTTON_2_ACTION_TEXT}"
initial_option: "${BUTTON_2_ACTION_TEXT}"
<<: *button_select_action_base
- id: sl_button_3_action
name: Button 3 action
platform: template
options:
- "${BUTTON_ACTION_NONE_TEXT}"
- "${BUTTON_3_ACTION_TEXT}"
initial_option: "${BUTTON_3_ACTION_TEXT}"
<<: *button_select_action_base
- id: sl_button_4_action
name: Button 4 action
platform: template
options:
- "${BUTTON_ACTION_NONE_TEXT}"
- "${BUTTON_4_ACTION_TEXT}"
initial_option: "${BUTTON_4_ACTION_TEXT}"
<<: *button_select_action_base
...