ESPHome  2024.12.2
current_based_cover.cpp
Go to the documentation of this file.
1 #include "current_based_cover.h"
2 #include "esphome/core/hal.h"
3 #include "esphome/core/log.h"
4 #include <cfloat>
5 
6 namespace esphome {
7 namespace current_based {
8 
9 static const char *const TAG = "current_based.cover";
10 
11 using namespace esphome::cover;
12 
14  auto traits = CoverTraits();
15  traits.set_supports_stop(true);
16  traits.set_supports_position(true);
17  traits.set_supports_toggle(true);
18  traits.set_is_assumed_state(false);
19  return traits;
20 }
22  if (call.get_stop()) {
23  this->direction_idle_();
24  }
25  if (call.get_toggle().has_value()) {
26  if (this->current_operation != COVER_OPERATION_IDLE) {
27  this->start_direction_(COVER_OPERATION_IDLE);
28  this->publish_state();
29  } else {
30  if (this->position == COVER_CLOSED || this->last_operation_ == COVER_OPERATION_CLOSING) {
31  this->target_position_ = COVER_OPEN;
32  this->start_direction_(COVER_OPERATION_OPENING);
33  } else {
34  this->target_position_ = COVER_CLOSED;
35  this->start_direction_(COVER_OPERATION_CLOSING);
36  }
37  }
38  }
39  if (call.get_position().has_value()) {
40  auto pos = *call.get_position();
41  if (fabsf(this->position - pos) < 0.01) {
42  // already at target
43  } else {
45  this->target_position_ = pos;
46  this->start_direction_(op);
47  }
48  }
49 }
51  auto restore = this->restore_state_();
52  if (restore.has_value()) {
53  restore->apply(this);
54  } else {
55  this->position = 0.5f;
56  }
57 }
58 
60  if (this->current_operation == COVER_OPERATION_IDLE)
61  return;
62 
63  const uint32_t now = millis();
64 
65  if (this->current_operation == COVER_OPERATION_OPENING) {
66  if (this->malfunction_detection_ && this->is_closing_()) { // Malfunction
67  this->direction_idle_();
68  this->malfunction_trigger_->trigger();
69  ESP_LOGI(TAG, "'%s' - Malfunction detected during opening. Current flow detected in close circuit",
70  this->name_.c_str());
71  } else if (this->is_opening_blocked_()) { // Blocked
72  ESP_LOGD(TAG, "'%s' - Obstacle detected during opening.", this->name_.c_str());
73  this->direction_idle_();
74  if (this->obstacle_rollback_ != 0) {
75  this->set_timeout("rollback", 300, [this]() {
76  ESP_LOGD(TAG, "'%s' - Rollback.", this->name_.c_str());
77  this->target_position_ = clamp(this->position - this->obstacle_rollback_, 0.0F, 1.0F);
78  this->start_direction_(COVER_OPERATION_CLOSING);
79  });
80  }
81  } else if (this->is_initial_delay_finished_() && !this->is_opening_()) { // End reached
82  auto dur = (now - this->start_dir_time_) / 1e3f;
83  ESP_LOGD(TAG, "'%s' - Open position reached. Took %.1fs.", this->name_.c_str(), dur);
84  this->direction_idle_(COVER_OPEN);
85  }
86  } else if (this->current_operation == COVER_OPERATION_CLOSING) {
87  if (this->malfunction_detection_ && this->is_opening_()) { // Malfunction
88  this->direction_idle_();
89  this->malfunction_trigger_->trigger();
90  ESP_LOGI(TAG, "'%s' - Malfunction detected during closing. Current flow detected in open circuit",
91  this->name_.c_str());
92  } else if (this->is_closing_blocked_()) { // Blocked
93  ESP_LOGD(TAG, "'%s' - Obstacle detected during closing.", this->name_.c_str());
94  this->direction_idle_();
95  if (this->obstacle_rollback_ != 0) {
96  this->set_timeout("rollback", 300, [this]() {
97  ESP_LOGD(TAG, "'%s' - Rollback.", this->name_.c_str());
98  this->target_position_ = clamp(this->position + this->obstacle_rollback_, 0.0F, 1.0F);
99  this->start_direction_(COVER_OPERATION_OPENING);
100  });
101  }
102  } else if (this->is_initial_delay_finished_() && !this->is_closing_()) { // End reached
103  auto dur = (now - this->start_dir_time_) / 1e3f;
104  ESP_LOGD(TAG, "'%s' - Close position reached. Took %.1fs.", this->name_.c_str(), dur);
105  this->direction_idle_(COVER_CLOSED);
106  }
107  }
108  if (now - this->start_dir_time_ > this->max_duration_) {
109  ESP_LOGD(TAG, "'%s' - Max duration reached. Stopping cover.", this->name_.c_str());
110  this->direction_idle_();
111  }
112 
113  // Recompute position every loop cycle
114  this->recompute_position_();
115 
116  if (this->current_operation != COVER_OPERATION_IDLE && this->is_at_target_()) {
117  this->direction_idle_();
118  }
119 
120  // Send current position every second
121  if (this->current_operation != COVER_OPERATION_IDLE && now - this->last_publish_time_ > 1000) {
122  this->publish_state(false);
123  this->last_publish_time_ = now;
124  }
125 }
126 
127 void CurrentBasedCover::direction_idle_(float new_position) {
128  this->start_direction_(COVER_OPERATION_IDLE);
129  if (new_position != FLT_MAX) {
130  this->position = new_position;
131  }
132  this->publish_state();
133 }
134 
136  LOG_COVER("", "Endstop Cover", this);
137  LOG_SENSOR(" ", "Open Sensor", this->open_sensor_);
138  ESP_LOGCONFIG(TAG, " Open moving current threshold: %.11fA", this->open_moving_current_threshold_);
139  if (this->open_obstacle_current_threshold_ != FLT_MAX) {
140  ESP_LOGCONFIG(TAG, " Open obstacle current threshold: %.11fA", this->open_obstacle_current_threshold_);
141  }
142  ESP_LOGCONFIG(TAG, " Open Duration: %.1fs", this->open_duration_ / 1e3f);
143  LOG_SENSOR(" ", "Close Sensor", this->close_sensor_);
144  ESP_LOGCONFIG(TAG, " Close moving current threshold: %.11fA", this->close_moving_current_threshold_);
145  if (this->close_obstacle_current_threshold_ != FLT_MAX) {
146  ESP_LOGCONFIG(TAG, " Close obstacle current threshold: %.11fA", this->close_obstacle_current_threshold_);
147  }
148  ESP_LOGCONFIG(TAG, " Close Duration: %.1fs", this->close_duration_ / 1e3f);
149  ESP_LOGCONFIG(TAG, "Obstacle Rollback: %.1f%%", this->obstacle_rollback_ * 100);
150  if (this->max_duration_ != UINT32_MAX) {
151  ESP_LOGCONFIG(TAG, "Maximum duration: %.1fs", this->max_duration_ / 1e3f);
152  }
153  ESP_LOGCONFIG(TAG, "Start sensing delay: %.1fs", this->start_sensing_delay_ / 1e3f);
154  ESP_LOGCONFIG(TAG, "Malfunction detection: %s", YESNO(this->malfunction_detection_));
155 }
156 
159  if (this->prev_command_trigger_ != nullptr) {
160  this->prev_command_trigger_->stop_action();
161  this->prev_command_trigger_ = nullptr;
162  }
163 }
164 
166  return this->open_sensor_->get_state() > this->open_moving_current_threshold_;
167 }
168 
170  if (this->open_obstacle_current_threshold_ == FLT_MAX) {
171  return false;
172  }
173  return this->open_sensor_->get_state() > this->open_obstacle_current_threshold_;
174 }
175 
177  return this->close_sensor_->get_state() > this->close_moving_current_threshold_;
178 }
179 
181  if (this->close_obstacle_current_threshold_ == FLT_MAX) {
182  return false;
183  }
184  return this->close_sensor_->get_state() > this->close_obstacle_current_threshold_;
185 }
187  return millis() - this->start_dir_time_ > this->start_sensing_delay_;
188 }
189 
191  switch (this->current_operation) {
193  if (this->target_position_ == COVER_OPEN) {
194  if (!this->is_initial_delay_finished_()) // During initial delay, state is assumed
195  return false;
196  return !this->is_opening_();
197  }
198  return this->position >= this->target_position_;
200  if (this->target_position_ == COVER_CLOSED) {
201  if (!this->is_initial_delay_finished_()) // During initial delay, state is assumed
202  return false;
203  return !this->is_closing_();
204  }
205  return this->position <= this->target_position_;
207  default:
208  return true;
209  }
210 }
212  if (dir == this->current_operation)
213  return;
214 
215  this->recompute_position_();
216  Trigger<> *trig;
217  switch (dir) {
219  trig = this->stop_trigger_;
220  break;
222  this->last_operation_ = dir;
223  trig = this->open_trigger_;
224  break;
226  this->last_operation_ = dir;
227  trig = this->close_trigger_;
228  break;
229  default:
230  return;
231  }
232 
233  this->current_operation = dir;
234 
235  this->stop_prev_trigger_();
236  trig->trigger();
237  this->prev_command_trigger_ = trig;
238 
239  const auto now = millis();
240  this->start_dir_time_ = now;
241  this->last_recompute_time_ = now;
242 }
244  if (this->current_operation == COVER_OPERATION_IDLE)
245  return;
246 
247  float dir;
248  float action_dur;
249  switch (this->current_operation) {
251  dir = 1.0F;
252  action_dur = this->open_duration_;
253  break;
255  dir = -1.0F;
256  action_dur = this->close_duration_;
257  break;
258  default:
259  return;
260  }
261 
262  const auto now = millis();
263  this->position += dir * (now - this->last_recompute_time_) / action_dur;
264  this->position = clamp(this->position, 0.0F, 1.0F);
265 
266  this->last_recompute_time_ = now;
267 }
268 
269 } // namespace current_based
270 } // namespace esphome
void start_direction_(cover::CoverOperation dir)
const float DATA
For components that import data from directly connected sensors like DHT.
Definition: component.cpp:19
void direction_idle_(float new_position=FLT_MAX)
CoverOperation
Enum encoding the current operation of a cover.
Definition: cover.h:80
The cover is currently closing.
Definition: cover.h:86
const float COVER_CLOSED
Definition: cover.cpp:10
bool has_value() const
Definition: optional.h:87
constexpr const T & clamp(const T &v, const T &lo, const T &hi, Compare comp)
Definition: helpers.h:93
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
void trigger(Ts... x)
Inform the parent automation that the event has triggered.
Definition: automation.h:95
const optional< bool > & get_toggle() const
Definition: cover.cpp:99
const float COVER_OPEN
Definition: cover.cpp:9
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
float position
Definition: cover.h:14
The cover is currently opening.
Definition: cover.h:84
const optional< float > & get_position() const
Definition: cover.cpp:97
void control(const cover::CoverCall &call) override
bool get_stop() const
Definition: cover.cpp:147