ESPHome 2026.2.3
Loading...
Searching...
No Matches
automation.cpp
Go to the documentation of this file.
1#include "automation.h"
2#include "esphome/core/log.h"
3
5
6static const char *const TAG = "binary_sensor.automation";
7
8// MultiClickTrigger timeout IDs.
9// MultiClickTrigger is its own Component instance, so the scheduler scopes
10// IDs by component pointer — no risk of collisions between instances.
11constexpr uint32_t MULTICLICK_TRIGGER_ID = 0;
12constexpr uint32_t MULTICLICK_COOLDOWN_ID = 1;
13constexpr uint32_t MULTICLICK_IS_VALID_ID = 2;
14constexpr uint32_t MULTICLICK_IS_NOT_VALID_ID = 3;
15
17 // Handle duplicate events
18 if (state == this->last_state_) {
19 return;
20 }
21 this->last_state_ = state;
22
23 // Cooldown: Do not immediately try matching after having invalid timing
24 if (this->is_in_cooldown_) {
25 return;
26 }
27
28 if (!this->at_index_.has_value()) {
29 // Start matching
30 MultiClickTriggerEvent evt = this->timing_[0];
31 if (evt.state == state) {
32 ESP_LOGV(TAG,
33 "START min=%" PRIu32 " max=%" PRIu32 "\n"
34 "Multi Click: Starting multi click action!",
35 evt.min_length, evt.max_length);
36 this->at_index_ = 1;
37 if (this->timing_.size() == 1 && evt.max_length == 4294967294UL) {
38 this->set_timeout(MULTICLICK_TRIGGER_ID, evt.min_length, [this]() { this->trigger_(); });
39 } else {
42 }
43 } else {
44 ESP_LOGV(TAG, "Multi Click: action not started because first level does not match!");
45 }
46
47 return;
48 }
49
50 if (!this->is_valid_) {
51 this->schedule_cooldown_();
52 return;
53 }
54
55 if (*this->at_index_ == this->timing_.size()) {
56 this->trigger_();
57 return;
58 }
59
60 MultiClickTriggerEvent evt = this->timing_[*this->at_index_];
61
62 if (evt.max_length != 4294967294UL) {
63 ESP_LOGV(TAG, "A i=%zu min=%" PRIu32 " max=%" PRIu32, *this->at_index_, evt.min_length, evt.max_length); // NOLINT
66 } else if (*this->at_index_ + 1 != this->timing_.size()) {
67 ESP_LOGV(TAG, "B i=%zu min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
68 this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
70 } else {
71 ESP_LOGV(TAG, "C i=%zu min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
72 this->is_valid_ = false;
73 this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
74 this->set_timeout(MULTICLICK_TRIGGER_ID, evt.min_length, [this]() { this->trigger_(); });
75 }
76
77 *this->at_index_ = *this->at_index_ + 1;
78}
80 ESP_LOGV(TAG, "Multi Click: Invalid length of press, starting cooldown of %" PRIu32 " ms", this->invalid_cooldown_);
81 this->is_in_cooldown_ = true;
82 this->set_timeout(MULTICLICK_COOLDOWN_ID, this->invalid_cooldown_, [this]() {
83 ESP_LOGV(TAG, "Multi Click: Cooldown ended, matching is now enabled again.");
84 this->is_in_cooldown_ = false;
85 });
86 this->at_index_.reset();
87 this->cancel_timeout(MULTICLICK_TRIGGER_ID);
88 this->cancel_timeout(MULTICLICK_IS_VALID_ID);
89 this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
90}
91void MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
92 if (min_length == 0) {
93 this->is_valid_ = true;
94 return;
95 }
96 this->is_valid_ = false;
97 this->set_timeout(MULTICLICK_IS_VALID_ID, min_length, [this]() {
98 ESP_LOGV(TAG, "Multi Click: You can now %s the button.", this->parent_->state ? "RELEASE" : "PRESS");
99 this->is_valid_ = true;
100 });
101}
103 this->set_timeout(MULTICLICK_IS_NOT_VALID_ID, max_length, [this]() {
104 ESP_LOGV(TAG, "Multi Click: You waited too long to %s.", this->parent_->state ? "RELEASE" : "PRESS");
105 this->is_valid_ = false;
106 this->schedule_cooldown_();
107 });
108}
110 ESP_LOGV(TAG, "Multi Click: Sequence explicitly cancelled.");
111 this->is_valid_ = false;
112 this->schedule_cooldown_();
113}
115 ESP_LOGV(TAG, "Multi Click: Hooray, multi click is valid. Triggering!");
116 this->at_index_.reset();
117 this->cancel_timeout(MULTICLICK_TRIGGER_ID);
118 this->cancel_timeout(MULTICLICK_IS_VALID_ID);
119 this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
120 this->trigger();
121}
122
123bool match_interval(uint32_t min_length, uint32_t max_length, uint32_t length) {
124 if (max_length == 0) {
125 return length >= min_length;
126 } else {
127 return length >= min_length && length <= max_length;
128 }
129}
130} // namespace esphome::binary_sensor
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") void set_timeout(const std voi set_timeout)(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
Definition component.h:429
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") bool cancel_timeout(const std boo cancel_timeout)(const char *name)
Cancel a timeout function.
Definition component.h:451
void trigger(const Ts &...x)
Definition automation.h:279
FixedVector< MultiClickTriggerEvent > timing_
Definition automation.h:117
void schedule_is_not_valid_(uint32_t max_length)
void schedule_is_valid_(uint32_t min_length)
bool has_value() const
Definition optional.h:92
bool state
Definition fan.h:2
constexpr uint32_t MULTICLICK_COOLDOWN_ID
constexpr uint32_t MULTICLICK_IS_NOT_VALID_ID
constexpr uint32_t MULTICLICK_IS_VALID_ID
bool match_interval(uint32_t min_length, uint32_t max_length, uint32_t length)
constexpr uint32_t MULTICLICK_TRIGGER_ID
uint16_t length
Definition tt21100.cpp:0