ESPHome 2026.3.0-dev
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, "START min=%" PRIu32 " max=%" PRIu32, evt.min_length, evt.max_length);
33 ESP_LOGV(TAG, "Multi Click: Starting multi click action!");
34 this->at_index_ = 1;
35 if (this->timing_.size() == 1 && evt.max_length == 4294967294UL) {
36 this->set_timeout(MULTICLICK_TRIGGER_ID, evt.min_length, [this]() { this->trigger_(); });
37 } else {
40 }
41 } else {
42 ESP_LOGV(TAG, "Multi Click: action not started because first level does not match!");
43 }
44
45 return;
46 }
47
48 if (!this->is_valid_) {
49 this->schedule_cooldown_();
50 return;
51 }
52
53 if (*this->at_index_ == this->timing_.size()) {
54 this->trigger_();
55 return;
56 }
57
58 MultiClickTriggerEvent evt = this->timing_[*this->at_index_];
59
60 if (evt.max_length != 4294967294UL) {
61 ESP_LOGV(TAG, "A i=%zu min=%" PRIu32 " max=%" PRIu32, *this->at_index_, evt.min_length, evt.max_length); // NOLINT
64 } else if (*this->at_index_ + 1 != this->timing_.size()) {
65 ESP_LOGV(TAG, "B i=%zu min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
66 this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
68 } else {
69 ESP_LOGV(TAG, "C i=%zu min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
70 this->is_valid_ = false;
71 this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
72 this->set_timeout(MULTICLICK_TRIGGER_ID, evt.min_length, [this]() { this->trigger_(); });
73 }
74
75 *this->at_index_ = *this->at_index_ + 1;
76}
78 ESP_LOGV(TAG, "Multi Click: Invalid length of press, starting cooldown of %" PRIu32 " ms", this->invalid_cooldown_);
79 this->is_in_cooldown_ = true;
80 this->set_timeout(MULTICLICK_COOLDOWN_ID, this->invalid_cooldown_, [this]() {
81 ESP_LOGV(TAG, "Multi Click: Cooldown ended, matching is now enabled again.");
82 this->is_in_cooldown_ = false;
83 });
84 this->at_index_.reset();
85 this->cancel_timeout(MULTICLICK_TRIGGER_ID);
86 this->cancel_timeout(MULTICLICK_IS_VALID_ID);
87 this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
88}
89void MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
90 if (min_length == 0) {
91 this->is_valid_ = true;
92 return;
93 }
94 this->is_valid_ = false;
95 this->set_timeout(MULTICLICK_IS_VALID_ID, min_length, [this]() {
96 ESP_LOGV(TAG, "Multi Click: You can now %s the button.", this->parent_->state ? "RELEASE" : "PRESS");
97 this->is_valid_ = true;
98 });
99}
101 this->set_timeout(MULTICLICK_IS_NOT_VALID_ID, max_length, [this]() {
102 ESP_LOGV(TAG, "Multi Click: You waited too long to %s.", this->parent_->state ? "RELEASE" : "PRESS");
103 this->is_valid_ = false;
104 this->schedule_cooldown_();
105 });
106}
108 ESP_LOGV(TAG, "Multi Click: Sequence explicitly cancelled.");
109 this->is_valid_ = false;
110 this->schedule_cooldown_();
111}
113 ESP_LOGV(TAG, "Multi Click: Hooray, multi click is valid. Triggering!");
114 this->at_index_.reset();
115 this->cancel_timeout(MULTICLICK_TRIGGER_ID);
116 this->cancel_timeout(MULTICLICK_IS_VALID_ID);
117 this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
118 this->trigger();
119}
120
121bool match_interval(uint32_t min_length, uint32_t max_length, uint32_t length) {
122 if (max_length == 0) {
123 return length >= min_length;
124 } else {
125 return length >= min_length && length <= max_length;
126 }
127}
128} // 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:443
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:465
void trigger(const Ts &...x)
Definition automation.h:325
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