ESPHome  2024.5.0
he60r.cpp
Go to the documentation of this file.
1 #include "he60r.h"
2 #include "esphome/core/hal.h"
3 #include "esphome/core/log.h"
4 
5 namespace esphome {
6 namespace he60r {
7 
8 static const char *const TAG = "he60r.cover";
9 static const uint8_t QUERY_BYTE = 0x38;
10 static const uint8_t TOGGLE_BYTE = 0x30;
11 
12 using namespace esphome::cover;
13 
15  auto restore = this->restore_state_();
16 
17  if (restore.has_value()) {
18  restore->apply(this);
19  this->publish_state(false);
20  } else {
21  // if no other information, assume half open
22  this->position = 0.5f;
23  }
24  this->current_operation = COVER_OPERATION_IDLE;
25  this->last_recompute_time_ = this->start_dir_time_ = millis();
26  this->set_interval(300, [this]() { this->update_(); });
27 }
28 
30  auto traits = CoverTraits();
31  traits.set_supports_stop(true);
32  traits.set_supports_position(true);
33  traits.set_supports_toggle(true);
34  traits.set_is_assumed_state(false);
35  return traits;
36 }
37 
39  LOG_COVER("", "HE60R Cover", this);
40  this->check_uart_settings(1200, 1, uart::UART_CONFIG_PARITY_EVEN, 8);
41  ESP_LOGCONFIG(TAG, " Open Duration: %.1fs", this->open_duration_ / 1e3f);
42  ESP_LOGCONFIG(TAG, " Close Duration: %.1fs", this->close_duration_ / 1e3f);
43  auto restore = this->restore_state_();
44  if (restore.has_value())
45  ESP_LOGCONFIG(TAG, " Saved position %d%%", (int) (restore->position * 100.f));
46 }
47 
49  const uint32_t now = millis();
50 
51  this->set_current_operation_(COVER_OPERATION_IDLE);
52  auto new_position = operation == COVER_OPERATION_OPENING ? COVER_OPEN : COVER_CLOSED;
53  if (new_position != this->position || this->current_operation != COVER_OPERATION_IDLE) {
54  this->position = new_position;
55  this->current_operation = COVER_OPERATION_IDLE;
56  if (this->last_command_ == operation) {
57  float dur = (now - this->start_dir_time_) / 1e3f;
58  ESP_LOGD(TAG, "'%s' - %s endstop reached. Took %.1fs.", this->name_.c_str(),
59  operation == COVER_OPERATION_OPENING ? "Open" : "Close", dur);
60  }
61  this->publish_state();
62  }
63 }
64 
66  if (this->current_operation != operation) {
67  this->current_operation = operation;
68  if (operation != COVER_OPERATION_IDLE)
69  this->last_recompute_time_ = millis();
70  this->publish_state();
71  }
72 }
73 
74 void HE60rCover::process_rx_(uint8_t data) {
75  ESP_LOGV(TAG, "Process RX data %X", data);
76  if (!this->query_seen_) {
77  this->query_seen_ = data == QUERY_BYTE;
78  if (!this->query_seen_)
79  ESP_LOGD(TAG, "RX Byte %02X", data);
80  return;
81  }
82  switch (data) {
83  case 0xB5: // at closed endstop, jammed?
84  case 0xF5: // at closed endstop, jammed?
85  case 0x55: // at closed endstop
86  this->next_direction_ = COVER_OPERATION_OPENING;
87  this->endstop_reached_(COVER_OPERATION_CLOSING);
88  break;
89 
90  case 0x52: // at opened endstop
91  this->next_direction_ = COVER_OPERATION_CLOSING;
92  this->endstop_reached_(COVER_OPERATION_OPENING);
93  break;
94 
95  case 0x51: // travelling up after encountering obstacle
96  case 0x01: // travelling up
97  case 0x11: // travelling up, triggered by remote
98  this->set_current_operation_(COVER_OPERATION_OPENING);
99  this->next_direction_ = COVER_OPERATION_IDLE;
100  break;
101 
102  case 0x44: // travelling down
103  case 0x14: // travelling down, triggered by remote
104  this->next_direction_ = COVER_OPERATION_IDLE;
105  this->set_current_operation_(COVER_OPERATION_CLOSING);
106  break;
107 
108  case 0x86: // Stopped, jammed?
109  case 0x16: // stopped midway while opening, by remote
110  case 0x06: // stopped midway while opening
111  this->next_direction_ = COVER_OPERATION_CLOSING;
112  this->set_current_operation_(COVER_OPERATION_IDLE);
113  break;
114 
115  case 0x10: // stopped midway while closing, by remote
116  case 0x00: // stopped midway while closing
117  this->next_direction_ = COVER_OPERATION_OPENING;
118  this->set_current_operation_(COVER_OPERATION_IDLE);
119  break;
120 
121  default:
122  break;
123  }
124 }
125 
127  if (toggles_needed_ != 0) {
128  if ((this->counter_++ & 0x3) == 0) {
129  toggles_needed_--;
130  ESP_LOGD(TAG, "Writing byte 0x30, still needed=%d", toggles_needed_);
131  this->write_byte(TOGGLE_BYTE);
132  } else {
133  this->write_byte(QUERY_BYTE);
134  }
135  } else {
136  this->write_byte(QUERY_BYTE);
137  this->counter_ = 0;
138  }
139  if (this->current_operation != COVER_OPERATION_IDLE) {
140  this->recompute_position_();
141 
142  // if we initiated the move, check if we reached the target position
143  if (this->last_command_ != COVER_OPERATION_IDLE) {
144  if (this->is_at_target_()) {
145  this->start_direction_(COVER_OPERATION_IDLE);
146  }
147  }
148  }
149 }
150 
152  uint8_t data;
153 
154  while (this->available() > 0) {
155  if (this->read_byte(&data)) {
156  this->process_rx_(data);
157  }
158  }
159 }
160 
161 void HE60rCover::control(const CoverCall &call) {
162  if (call.get_stop()) {
163  this->start_direction_(COVER_OPERATION_IDLE);
164  } else if (call.get_toggle().has_value()) {
165  // toggle action logic: OPEN - STOP - CLOSE
166  if (this->last_command_ != COVER_OPERATION_IDLE) {
167  this->start_direction_(COVER_OPERATION_IDLE);
168  } else {
169  this->toggles_needed_++;
170  }
171  } else if (call.get_position().has_value()) {
172  // go to position action
173  auto pos = *call.get_position();
174  // are we at the target?
175  if (pos == this->position) {
176  this->start_direction_(COVER_OPERATION_IDLE);
177  } else {
178  this->target_position_ = pos;
179  this->start_direction_(pos < this->position ? COVER_OPERATION_CLOSING : COVER_OPERATION_OPENING);
180  }
181  }
182 }
183 
190  // equality of floats is fraught with peril - this is reliable since the values are 0.0 or 1.0 which are
191  // exactly representable.
192  if (this->target_position_ == COVER_OPEN || this->target_position_ == COVER_CLOSED)
193  return false;
194  // aiming for an intermediate position - exact comparison here will not work and we need to allow for overshoot
195  switch (this->last_command_) {
197  return this->position >= this->target_position_;
199  return this->position <= this->target_position_;
201  return this->current_operation == COVER_OPERATION_IDLE;
202  default:
203  return true;
204  }
205 }
207  this->last_command_ = dir;
208  if (this->current_operation == dir)
209  return;
210  ESP_LOGD(TAG, "'%s' - Direction '%s' requested.", this->name_.c_str(),
211  dir == COVER_OPERATION_OPENING ? "OPEN"
212  : dir == COVER_OPERATION_CLOSING ? "CLOSE"
213  : "STOP");
214 
215  if (dir == this->next_direction_) {
216  // either moving and needs to stop, or stopped and will move correctly on one trigger
217  this->toggles_needed_ = 1;
218  } else {
219  if (this->current_operation == COVER_OPERATION_IDLE) {
220  // if stopped, but will go the wrong way, need 3 triggers.
221  this->toggles_needed_ = 3;
222  } else {
223  // just stop and reverse
224  this->toggles_needed_ = 2;
225  }
226  ESP_LOGD(TAG, "'%s' - Reversing direction.", this->name_.c_str());
227  }
228  this->start_dir_time_ = millis();
229 }
230 
232  if (this->current_operation == COVER_OPERATION_IDLE)
233  return;
234 
235  const uint32_t now = millis();
236  float dir;
237  float action_dur;
238 
239  switch (this->current_operation) {
241  dir = 1.0f;
242  action_dur = this->open_duration_;
243  break;
245  dir = -1.0f;
246  action_dur = this->close_duration_;
247  break;
248  default:
249  return;
250  }
251 
252  if (now > this->last_recompute_time_) {
253  auto diff = now - last_recompute_time_;
254  auto delta = dir * diff / action_dur;
255  // make sure our guesstimate never reaches full open or close.
256  this->position = clamp(delta + this->position, COVER_CLOSED + 0.01f, COVER_OPEN - 0.01f);
257  ESP_LOGD(TAG, "Recompute %dms, dir=%f, action_dur=%f, delta=%f, pos=%f", (int) diff, dir, action_dur, delta,
258  this->position);
259  this->last_recompute_time_ = now;
260  this->publish_state();
261  }
262 }
263 
264 } // namespace he60r
265 } // namespace esphome
CoverOperation
Enum encoding the current operation of a cover.
Definition: cover.h:80
void set_current_operation_(cover::CoverOperation operation)
Definition: he60r.cpp:65
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
void loop() override
Definition: he60r.cpp:151
void dump_config() override
Definition: he60r.cpp:38
constexpr const T & clamp(const T &v, const T &lo, const T &hi, Compare comp)
Definition: helpers.h:92
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
bool is_at_target_() const
Check if the cover has reached or passed the target position.
Definition: he60r.cpp:189
const optional< bool > & get_toggle() const
Definition: cover.cpp:99
void process_rx_(uint8_t data)
Definition: he60r.cpp:74
const float COVER_OPEN
Definition: cover.cpp:9
void endstop_reached_(cover::CoverOperation operation)
Definition: he60r.cpp:48
cover::CoverTraits get_traits() override
Definition: he60r.cpp:29
void start_direction_(cover::CoverOperation dir)
Definition: he60r.cpp:206
void setup() override
Definition: he60r.cpp:14
void control(const cover::CoverCall &call) override
Definition: he60r.cpp:161
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
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
bool get_stop() const
Definition: cover.cpp:147