ESPHome 2025.6.3
Loading...
Searching...
No Matches
tormatic_cover.cpp
Go to the documentation of this file.
1#include <vector>
2
3#include "tormatic_cover.h"
4
5using namespace std;
6
7namespace esphome {
8namespace tormatic {
9
10static const char *const TAG = "tormatic.cover";
11
12using namespace esphome::cover;
13
15 auto restore = this->restore_state_();
16 if (restore.has_value()) {
17 restore->apply(this);
18 return;
19 }
20
21 // Assume gate is closed without preexisting state.
22 this->position = 0.0f;
23}
24
26 auto traits = CoverTraits();
27 traits.set_supports_stop(true);
28 traits.set_supports_position(true);
29 traits.set_is_assumed_state(false);
30 return traits;
31}
32
34 LOG_COVER("", "Tormatic Cover", this);
36
37 ESP_LOGCONFIG(TAG,
38 " Open Duration: %.1fs\n"
39 " Close Duration: %.1fs",
40 this->open_duration_ / 1e3f, this->close_duration_ / 1e3f);
41
42 auto restore = this->restore_state_();
43 if (restore.has_value()) {
44 ESP_LOGCONFIG(TAG, " Saved position %d%%", (int) (restore->position * 100.f));
45 }
46}
47
49
51 auto o_status = this->read_gate_status_();
52 if (o_status) {
53 auto status = o_status.value();
54
57 }
58
59 this->recompute_position_();
60 this->stop_at_target_();
61}
62
64 if (call.get_stop()) {
66 return;
67 }
68
69 if (call.get_position().has_value()) {
70 auto pos = call.get_position().value();
71 this->control_position_(pos);
72 return;
73 }
74}
75
76// Wrap the Cover's publish_state with a rate limiter. Publishes if the last
77// publish was longer than ratelimit milliseconds ago. 0 to disable.
78void Tormatic::publish_state(bool save, uint32_t ratelimit) {
79 auto now = millis();
80 if ((now - this->last_publish_time_) < ratelimit) {
81 return;
82 }
83 this->last_publish_time_ = now;
84
86};
87
88// Recalibrate the gate's estimated open or close duration based on the
89// actual time the operation took.
91 if (this->current_status_ == s) {
92 return;
93 }
94
95 auto now = millis();
96 auto old = this->current_status_;
97
98 // Gate paused halfway through opening or closing, invalidate the start time
99 // of the current operation. Close/open durations can only be accurately
100 // calibrated on full open or close cycle due to motor acceleration.
101 if (s == PAUSED) {
102 ESP_LOGD(TAG, "Gate paused, clearing direction start time");
103 this->direction_start_time_ = 0;
104 return;
105 }
106
107 // Record the start time of a state transition if the gate was in the fully
108 // open or closed position before the command.
109 if ((old == CLOSED && s == OPENING) || (old == OPENED && s == CLOSING)) {
110 ESP_LOGD(TAG, "Gate started moving from fully open or closed state");
111 this->direction_start_time_ = now;
112 return;
113 }
114
115 // The gate was resumed from a paused state, don't attempt recalibration.
116 if (this->direction_start_time_ == 0) {
117 return;
118 }
119
120 if (s == OPENED) {
121 this->open_duration_ = now - this->direction_start_time_;
122 ESP_LOGI(TAG, "Recalibrated the gate's open duration to %dms", this->open_duration_);
123 }
124 if (s == CLOSED) {
125 this->close_duration_ = now - this->direction_start_time_;
126 ESP_LOGI(TAG, "Recalibrated the gate's close duration to %dms", this->close_duration_);
127 }
128
129 this->direction_start_time_ = 0;
130}
131
132// Set the Cover's internal state based on a status message
133// received from the unit.
135 if (this->current_status_ == s) {
136 return;
137 }
138
139 ESP_LOGI(TAG, "Status changed from %s to %s", gate_status_to_str(this->current_status_), gate_status_to_str(s));
140
141 switch (s) {
142 case OPENED:
143 // The Novoferm 423 doesn't respond to the first 'Close' command after
144 // being opened completely. Sending a pause command after opening fixes
145 // that.
147
148 this->position = COVER_OPEN;
149 break;
150 case CLOSED:
151 this->position = COVER_CLOSED;
152 break;
153 default:
154 break;
155 }
156
157 this->current_status_ = s;
159
160 this->publish_state(true);
161
162 // This timestamp is used to generate position deltas on every loop() while
163 // the gate is moving. Bump it on each state transition so the first tick
164 // doesn't generate a huge delta.
166}
167
168// Recompute the gate's position and publish the results while
169// the gate is moving. No-op when the gate is idle.
172 return;
173 }
174
175 const uint32_t now = millis();
176 uint32_t diff = now - this->last_recompute_time_;
177
178 auto direction = +1.0f;
179 uint32_t duration = this->open_duration_;
181 direction = -1.0f;
182 duration = this->close_duration_;
183 }
184
185 auto delta = direction * diff / duration;
186
187 this->position = clamp(this->position + delta, COVER_CLOSED, COVER_OPEN);
188
189 this->last_recompute_time_ = now;
190
191 this->publish_state(true, 250);
192}
193
194// Start moving the gate in the direction of the target position.
195void Tormatic::control_position_(float target) {
196 if (target == this->position) {
197 return;
198 }
199
200 if (target == COVER_OPEN) {
201 ESP_LOGI(TAG, "Fully opening gate");
203 return;
204 }
205 if (target == COVER_CLOSED) {
206 ESP_LOGI(TAG, "Fully closing gate");
208 return;
209 }
210
211 // Don't set target position when fully opening or closing the gate, the gate
212 // stops automatically when it reaches the configured open/closed positions.
213 this->target_position_ = target;
214
215 if (target > this->position) {
216 ESP_LOGI(TAG, "Opening gate towards %.1f", target);
218 return;
219 }
220
221 if (target < this->position) {
222 ESP_LOGI(TAG, "Closing gate towards %.1f", target);
224 return;
225 }
226}
227
228// Stop the gate if it is moving at or beyond its target position. Target
229// position is only set when the gate is requested to move to a halfway
230// position.
233 return;
234 }
235 if (!this->target_position_) {
236 return;
237 }
238 auto target = this->target_position_.value();
239
240 if (this->current_operation == COVER_OPERATION_OPENING && this->position < target) {
241 return;
242 }
243 if (this->current_operation == COVER_OPERATION_CLOSING && this->position > target) {
244 return;
245 }
246
248 this->target_position_.reset();
249}
250
251// Read a GateStatus from the unit. The unit only sends messages in response to
252// status requests or commands, so a message needs to be sent first.
254 if (this->available() < sizeof(MessageHeader)) {
255 return {};
256 }
257
258 auto o_hdr = this->read_data_<MessageHeader>();
259 if (!o_hdr) {
260 ESP_LOGE(TAG, "Timeout reading message header");
261 return {};
262 }
263 auto hdr = o_hdr.value();
264
265 switch (hdr.type) {
266 case STATUS: {
267 if (hdr.payload_size() != sizeof(StatusReply)) {
268 ESP_LOGE(TAG, "Header specifies payload size %d but size of StatusReply is %d", hdr.payload_size(),
269 sizeof(StatusReply));
270 }
271
272 // Read a StatusReply requested by update().
273 auto o_status = this->read_data_<StatusReply>();
274 if (!o_status) {
275 return {};
276 }
277 auto status = o_status.value();
278
279 return status.state;
280 }
281
282 case COMMAND:
283 // Commands initiated by control() are simply echoed back by the unit, but
284 // don't guarantee that the unit's internal state has been transitioned,
285 // nor that the motor started moving. A subsequent status request may
286 // still return the previous state. Discard these messages, don't use them
287 // to drive the Cover state machine.
288 break;
289
290 default:
291 // Unknown message type, drain the remaining amount of bytes specified in
292 // the header.
293 ESP_LOGE(TAG, "Reading remaining %d payload bytes of unknown type 0x%x", hdr.payload_size(), hdr.type);
294 break;
295 }
296
297 // Drain any unhandled payload bytes described by the message header, if any.
298 this->drain_rx_(hdr.payload_size());
299
300 return {};
301}
302
303// Send a message to the unit requesting the gate's status.
305 ESP_LOGV(TAG, "Requesting gate status");
306 StatusRequest req(GATE);
307 this->send_message_(STATUS, req);
308}
309
310// Send a message to the unit issuing a command.
312 ESP_LOGI(TAG, "Sending gate command %s", gate_status_to_str(s));
313 CommandRequestReply req(s);
314 this->send_message_(COMMAND, req);
315}
316
317template<typename T> void Tormatic::send_message_(MessageType t, T req) {
318 MessageHeader hdr(t, ++this->seq_tx_, sizeof(req));
319
320 auto out = serialize(hdr);
321 auto reqv = serialize(req);
322 out.insert(out.end(), reqv.begin(), reqv.end());
323
324 this->write_array(out);
325}
326
327template<typename T> optional<T> Tormatic::read_data_() {
328 T obj;
329 uint32_t start = millis();
330
331 auto ok = this->read_array((uint8_t *) &obj, sizeof(obj));
332 if (!ok) {
333 // Couldn't read object successfully, timeout?
334 return {};
335 }
336 obj.byteswap();
337
338 ESP_LOGV(TAG, "Read %s in %d ms", obj.print().c_str(), millis() - start);
339 return obj;
340}
341
342// Drain up to n amount of bytes from the uart rx buffer.
343void Tormatic::drain_rx_(uint16_t n) {
344 uint8_t data;
345 uint16_t count = 0;
346 while (this->available()) {
347 this->read_byte(&data);
348 count++;
349
350 if (n > 0 && count >= n) {
351 return;
352 }
353 }
354}
355
356} // namespace tormatic
357} // namespace esphome
uint8_t status
Definition bl0942.h:8
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
bool has_value() const
Definition optional.h:87
value_type const & value() const
Definition optional.h:89
optional< GateStatus > read_gate_status_()
void send_message_(MessageType t, T r)
void recalibrate_duration_(GateStatus s)
optional< float > target_position_
void send_gate_command_(GateStatus s)
void control(const cover::CoverCall &call) override
void control_position_(float target)
void handle_gate_status_(GateStatus s)
cover::CoverTraits get_traits() override
void publish_state(bool save=true, uint32_t ratelimit=0)
optional< std::array< uint8_t, N > > read_array()
Definition uart.h:33
void check_uart_settings(uint32_t baud_rate, uint8_t stop_bits=1, UARTParityOptions parity=UART_CONFIG_PARITY_NONE, uint8_t data_bits=8)
Check that the configuration of the UART bus matches the provided values and otherwise print a warnin...
Definition uart.cpp:13
bool read_byte(uint8_t *data)
Definition uart.h:29
void write_array(const uint8_t *data, size_t len)
Definition uart.h:21
FanDirection direction
Definition fan.h:3
uint8_t duration
Definition msa3xx.h:0
const float COVER_CLOSED
Definition cover.cpp:10
const float COVER_OPEN
Definition cover.cpp:9
@ 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
CoverOperation gate_status_to_cover_operation(GateStatus s)
std::vector< uint8_t > serialize(T obj)
const char * gate_status_to_str(GateStatus s)
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
constexpr const T & clamp(const T &v, const T &lo, const T &hi, Compare comp)
Definition helpers.h:102