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.
273 lines
9.5 KiB
YAML
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
|
|
...
|