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