ESPHome 2025.5.2
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Modules Pages
feedback_cover.cpp
Go to the documentation of this file.
1#include "feedback_cover.h"
2#include "esphome/core/hal.h"
3#include "esphome/core/log.h"
5
6namespace esphome {
7namespace feedback {
8
9static const char *const TAG = "feedback.cover";
10
11using namespace esphome::cover;
12
14 auto restore = this->restore_state_();
15
16 if (restore.has_value()) {
17 restore->apply(this);
18 } else {
19 // if no other information, assume half open
20 this->position = 0.5f;
21 }
23
24#ifdef USE_BINARY_SENSOR
25 // if available, get position from endstop sensors
26 if (this->open_endstop_ != nullptr && this->open_endstop_->state) {
27 this->position = COVER_OPEN;
28 } else if (this->close_endstop_ != nullptr && this->close_endstop_->state) {
29 this->position = COVER_CLOSED;
30 }
31
32 // if available, get moving state from sensors
33 if (this->open_feedback_ != nullptr && this->open_feedback_->state) {
35 } else if (this->close_feedback_ != nullptr && this->close_feedback_->state) {
37 }
38#endif
39
41}
42
44 auto traits = CoverTraits();
45 traits.set_supports_stop(true);
46 traits.set_supports_position(true);
47 traits.set_supports_toggle(true);
48 traits.set_is_assumed_state(this->assumed_state_);
49 return traits;
50}
51
53 LOG_COVER("", "Endstop Cover", this);
54 ESP_LOGCONFIG(TAG, " Open Duration: %.1fs", this->open_duration_ / 1e3f);
55#ifdef USE_BINARY_SENSOR
56 LOG_BINARY_SENSOR(" ", "Open Endstop", this->open_endstop_);
57 LOG_BINARY_SENSOR(" ", "Open Feedback", this->open_feedback_);
58 LOG_BINARY_SENSOR(" ", "Open Obstacle", this->open_obstacle_);
59#endif
60 ESP_LOGCONFIG(TAG, " Close Duration: %.1fs", this->close_duration_ / 1e3f);
61#ifdef USE_BINARY_SENSOR
62 LOG_BINARY_SENSOR(" ", "Close Endstop", this->close_endstop_);
63 LOG_BINARY_SENSOR(" ", "Close Feedback", this->close_feedback_);
64 LOG_BINARY_SENSOR(" ", "Close Obstacle", this->close_obstacle_);
65#endif
66 if (this->has_built_in_endstop_) {
67 ESP_LOGCONFIG(TAG, " Has builtin endstop: YES");
68 }
69 if (this->infer_endstop_) {
70 ESP_LOGCONFIG(TAG, " Infer endstop from movement: YES");
71 }
72 if (this->max_duration_ < UINT32_MAX) {
73 ESP_LOGCONFIG(TAG, " Max Duration: %.1fs", this->max_duration_ / 1e3f);
74 }
76 ESP_LOGCONFIG(TAG, " Direction change wait time: %.1fs", *this->direction_change_waittime_ / 1e3f);
77 }
78 if (this->acceleration_wait_time_) {
79 ESP_LOGCONFIG(TAG, " Acceleration wait time: %.1fs", this->acceleration_wait_time_ / 1e3f);
80 }
81#ifdef USE_BINARY_SENSOR
82 if (this->obstacle_rollback_ && (this->open_obstacle_ != nullptr || this->close_obstacle_ != nullptr)) {
83 ESP_LOGCONFIG(TAG, " Obstacle rollback: %.1f%%", this->obstacle_rollback_ * 100);
84 }
85#endif
86}
87
88#ifdef USE_BINARY_SENSOR
89
91 this->open_feedback_ = open_feedback;
92
93 // setup callbacks to react to sensor changes
94 open_feedback->add_on_state_callback([this](bool state) {
95 ESP_LOGD(TAG, "'%s' - Open feedback '%s'.", this->name_.c_str(), state ? "STARTED" : "ENDED");
96 this->recompute_position_();
98 this->endstop_reached_(true);
99 }
101 });
102}
103
105 this->close_feedback_ = close_feedback;
106
107 close_feedback->add_on_state_callback([this](bool state) {
108 ESP_LOGD(TAG, "'%s' - Close feedback '%s'.", this->name_.c_str(), state ? "STARTED" : "ENDED");
109 this->recompute_position_();
110 if (!state && this->infer_endstop_ && this->current_trigger_operation_ == COVER_OPERATION_CLOSING) {
111 this->endstop_reached_(false);
112 }
113
115 });
116}
117
119 this->open_endstop_ = open_endstop;
120 open_endstop->add_on_state_callback([this](bool state) {
121 if (state) {
122 this->endstop_reached_(true);
123 }
124 });
125}
126
128 this->close_endstop_ = close_endstop;
129 close_endstop->add_on_state_callback([this](bool state) {
130 if (state) {
131 this->endstop_reached_(false);
132 }
133 });
134}
135#endif
136
137void FeedbackCover::endstop_reached_(bool open_endstop) {
138 const uint32_t now = millis();
139
140 this->position = open_endstop ? COVER_OPEN : COVER_CLOSED;
141
142 // only act if endstop activated while moving in the right direction, in case we are coming back
143 // from a position slightly past the endpoint
145 float dur = (now - this->start_dir_time_) / 1e3f;
146 ESP_LOGD(TAG, "'%s' - %s endstop reached. Took %.1fs.", this->name_.c_str(), open_endstop ? "Open" : "Close", dur);
147
148 // if there is no external mechanism, stop the cover
149 if (!this->has_built_in_endstop_) {
151 } else {
153 }
154 }
155
156 // always sync position and publish
157 this->publish_state();
158 this->last_publish_time_ = now;
159}
160
162 if (is_triggered) {
163 this->current_trigger_operation_ = operation;
164 }
165
166 // if it is setting the actual operation (not triggered one) or
167 // if we don't have moving sensor, we operate in optimistic mode, assuming actions take place immediately
168 // thus, triggered operation always sets current operation.
169 // otherwise, current operation comes from sensor, and may differ from requested operation
170 // this might be from delays or complex actions, or because the movement was not trigger by the component
171 // but initiated externally
172
173#ifdef USE_BINARY_SENSOR
174 if (!is_triggered || (this->open_feedback_ == nullptr || this->close_feedback_ == nullptr))
175#endif
176 {
177 auto now = millis();
178 this->current_operation = operation;
179 this->start_dir_time_ = this->last_recompute_time_ = now;
180 this->publish_state();
181 this->last_publish_time_ = now;
182 }
183}
184
185#ifdef USE_BINARY_SENSOR
187 this->close_obstacle_ = close_obstacle;
188
189 close_obstacle->add_on_state_callback([this](bool state) {
192 ESP_LOGD(TAG, "'%s' - Close obstacle detected.", this->name_.c_str());
194
195 if (this->obstacle_rollback_) {
198 }
199 }
200 });
201}
202
204 this->open_obstacle_ = open_obstacle;
205
206 open_obstacle->add_on_state_callback([this](bool state) {
209 ESP_LOGD(TAG, "'%s' - Open obstacle detected.", this->name_.c_str());
211
212 if (this->obstacle_rollback_) {
215 }
216 }
217 });
218}
219#endif
220
223 return;
224 const uint32_t now = App.get_loop_component_start_time();
225
226 // Recompute position every loop cycle
227 this->recompute_position_();
228
229 // if we initiated the move, check if we reached position or max time
230 // (stoping from endstop sensor is handled in callback)
232 if (this->is_at_target_()) {
233 if (this->has_built_in_endstop_ &&
235 // Don't trigger stop, let the cover stop by itself.
237 } else {
239 }
240 } else if (now - this->start_dir_time_ > this->max_duration_) {
241 ESP_LOGD(TAG, "'%s' - Max duration reached. Stopping cover.", this->name_.c_str());
243 }
244 }
245
246 // update current position at requested interval, regardless of who started the movement
247 // so that we also update UI if there was an external movement
248 // don't save intermediate positions
249 if (now - this->last_publish_time_ > this->update_interval_) {
250 this->publish_state(false);
251 this->last_publish_time_ = now;
252 }
253}
254
256 // stop action logic
257 if (call.get_stop()) {
259 } else if (call.get_toggle().has_value()) {
260 // toggle action logic: OPEN - STOP - CLOSE
263 } else {
267 } else {
270 }
271 }
272 } else if (call.get_position().has_value()) {
273 // go to position action
274 auto pos = *call.get_position();
275 if (pos == this->position) {
276 // already at target,
277
278 // for covers with built in end stop, if we don't have sensors we should send the command again
279 // to make sure the assumed state is not wrong
280 if (this->has_built_in_endstop_ && ((pos == COVER_OPEN
281#ifdef USE_BINARY_SENSOR
282 && this->open_endstop_ == nullptr
283#endif
284 && !this->infer_endstop_) ||
285 (pos == COVER_CLOSED
286#ifdef USE_BINARY_SENSOR
287 && this->close_endstop_ == nullptr
288#endif
289 && !this->infer_endstop_))) {
290 this->target_position_ = pos;
292 } else if (this->current_operation != COVER_OPERATION_IDLE ||
294 // if we are moving, stop
296 }
297 } else {
298 this->target_position_ = pos;
300 }
301 }
302}
303
306 this->cancel_timeout("direction_change");
307 }
308 if (this->prev_command_trigger_ != nullptr) {
310 this->prev_command_trigger_ = nullptr;
311 }
312}
313
315 // if initiated externally, current operation might be different from
316 // operation that was triggered, thus evaluate position against what was asked
317
318 switch (this->current_trigger_operation_) {
320 return this->position >= this->target_position_;
322 return this->position <= this->target_position_;
325 default:
326 return true;
327 }
328}
330 Trigger<> *trig;
331
332#ifdef USE_BINARY_SENSOR
333 binary_sensor::BinarySensor *obstacle{nullptr};
334#endif
335
336 switch (dir) {
338 trig = this->stop_trigger_;
339 break;
341 this->last_operation_ = dir;
342 trig = this->open_trigger_;
343#ifdef USE_BINARY_SENSOR
344 obstacle = this->open_obstacle_;
345#endif
346 break;
348 this->last_operation_ = dir;
349 trig = this->close_trigger_;
350#ifdef USE_BINARY_SENSOR
351 obstacle = this->close_obstacle_;
352#endif
353 break;
354 default:
355 return;
356 }
357
358 this->stop_prev_trigger_();
359
360#ifdef USE_BINARY_SENSOR
361 // check if there is an obstacle to start the new operation -> abort without any change
362 // the case when an obstacle appears while moving is handled in the callback
363 if (obstacle != nullptr && obstacle->state) {
364 ESP_LOGD(TAG, "'%s' - %s obstacle detected. Action not started.", this->name_.c_str(),
365 dir == COVER_OPERATION_OPENING ? "Open" : "Close");
366 return;
367 }
368#endif
369
370 // if we are moving and need to move in the opposite direction
371 // check if we have a wait time
373 this->current_operation != COVER_OPERATION_IDLE && dir != this->current_operation) {
374 ESP_LOGD(TAG, "'%s' - Reversing direction.", this->name_.c_str());
376
377 this->set_timeout("direction_change", *this->direction_change_waittime_,
378 [this, dir]() { this->start_direction_(dir); });
379
380 } else {
381 this->set_current_operation_(dir, true);
382 this->prev_command_trigger_ = trig;
383 ESP_LOGD(TAG, "'%s' - Firing '%s' trigger.", this->name_.c_str(),
384 dir == COVER_OPERATION_OPENING ? "OPEN"
385 : dir == COVER_OPERATION_CLOSING ? "CLOSE"
386 : "STOP");
387 trig->trigger();
388 }
389}
390
393 return;
394
395 const uint32_t now = millis();
396 float dir;
397 float action_dur;
398 float min_pos;
399 float max_pos;
400
401 // endstop sensors update position from their callbacks, and sets the fully open/close value
402 // If we have endstop, estimation never reaches the fully open/closed state.
403 // but if movement continues past corresponding endstop (inertia), keep the fully open/close state
404
405 switch (this->current_operation) {
407 dir = 1.0f;
408 action_dur = this->open_duration_;
409 min_pos = COVER_CLOSED;
410 max_pos = (
411#ifdef USE_BINARY_SENSOR
412 this->open_endstop_ != nullptr ||
413#endif
414 this->infer_endstop_) &&
415 this->position < COVER_OPEN
416 ? 0.99f
417 : COVER_OPEN;
418 break;
420 dir = -1.0f;
421 action_dur = this->close_duration_;
422 min_pos = (
423#ifdef USE_BINARY_SENSOR
424 this->close_endstop_ != nullptr ||
425#endif
426 this->infer_endstop_) &&
427 this->position > COVER_CLOSED
428 ? 0.01f
429 : COVER_CLOSED;
430 max_pos = COVER_OPEN;
431 break;
432 default:
433 return;
434 }
435
436 // check if we have an acceleration_wait_time, and remove from position computation
437 if (now > (this->start_dir_time_ + this->acceleration_wait_time_)) {
438 this->position +=
439 dir * (now - std::max(this->start_dir_time_ + this->acceleration_wait_time_, this->last_recompute_time_)) /
440 (action_dur - this->acceleration_wait_time_);
441 this->position = clamp(this->position, min_pos, max_pos);
442 }
443 this->last_recompute_time_ = now;
444}
445
446} // namespace feedback
447} // namespace esphome
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
bool cancel_timeout(const std::string &name)
Cancel a timeout function.
Definition component.cpp:76
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
Definition component.cpp:72
constexpr const char * c_str() const
Definition string_ref.h:69
void stop_action()
Stop any action connected to this trigger.
Definition automation.h:104
void trigger(Ts... x)
Inform the parent automation that the event has triggered.
Definition automation.h:96
Base class for all binary_sensor-type classes.
bool state
The current reported state of the binary sensor.
void add_on_state_callback(std::function< void(bool)> &&callback)
Add a callback to be notified of state changes.
const optional< bool > & get_toggle() const
Definition cover.cpp:99
const optional< float > & get_position() const
Definition cover.cpp:97
CoverOperation current_operation
The current operation of the cover (idle, opening, closing).
Definition cover.h:116
optional< CoverRestoreState > restore_state_()
Definition cover.cpp:200
void publish_state(bool save=true)
Publish the current state of the cover.
Definition cover.cpp:166
float position
The position of the cover from 0.0 (fully closed) to 1.0 (fully open).
Definition cover.h:122
cover::CoverTraits get_traits() override
cover::CoverOperation current_trigger_operation_
binary_sensor::BinarySensor * open_endstop_
void start_direction_(cover::CoverOperation dir)
void set_open_obstacle_sensor(binary_sensor::BinarySensor *open_obstacle)
void set_open_sensor(binary_sensor::BinarySensor *open_feedback)
void set_current_operation_(cover::CoverOperation operation, bool is_triggered)
binary_sensor::BinarySensor * close_endstop_
void set_close_endstop(binary_sensor::BinarySensor *close_endstop)
optional< uint32_t > direction_change_waittime_
binary_sensor::BinarySensor * close_obstacle_
void set_close_obstacle_sensor(binary_sensor::BinarySensor *close_obstacle)
void control(const cover::CoverCall &call) override
binary_sensor::BinarySensor * open_feedback_
void endstop_reached_(bool open_endstop)
void set_open_endstop(binary_sensor::BinarySensor *open_endstop)
cover::CoverOperation last_operation_
binary_sensor::BinarySensor * open_obstacle_
binary_sensor::BinarySensor * close_feedback_
void set_close_sensor(binary_sensor::BinarySensor *close_feedback)
bool has_value() const
Definition optional.h:87
bool state
Definition fan.h:0
const float COVER_CLOSED
Definition cover.cpp:10
const float COVER_OPEN
Definition cover.cpp:9
CoverOperation
Enum encoding the current operation of a cover.
Definition cover.h:80
@ COVER_OPERATION_OPENING
The cover is currently opening.
Definition cover.h:84
@ COVER_OPERATION_CLOSING
The cover is currently closing.
Definition cover.h:86
@ COVER_OPERATION_IDLE
The cover is currently idle (not moving)
Definition cover.h:82
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:28
Application App
Global storage of Application pointer - only one Application can exist.
constexpr const T & clamp(const T &v, const T &lo, const T &hi, Compare comp)
Definition helpers.h:102