ESPHome 2026.4.5
Loading...
Searching...
No Matches
nextion.cpp
Go to the documentation of this file.
1#include "nextion.h"
2
3#include <cinttypes>
4
7#include "esphome/core/log.h"
9#include "esphome/core/util.h"
10
11namespace esphome::nextion {
12
13static const char *const TAG = "nextion";
14
15// Nextion command terminator: three consecutive 0xFF bytes (per Nextion Instruction Set v1.1).
16static constexpr uint8_t COMMAND_DELIMITER[3] = {0xFF, 0xFF, 0xFF};
17static constexpr size_t DELIMITER_SIZE = sizeof(COMMAND_DELIMITER);
18
19void Nextion::setup() {
20 this->is_setup_ = false;
21 this->connection_state_.ignore_is_setup_ = true;
22
23 // Wake up the nextion and ensure clean communication state
24 this->send_command_("sleep=0"); // Exit sleep mode if sleeping
25 this->send_command_("bkcmd=0"); // Disable return data during init sequence
26
27 // Reset device for clean state - critical for reliable communication
28 this->send_command_("rest");
29
30 this->connection_state_.ignore_is_setup_ = false;
31}
32
33bool Nextion::send_command_(const std::string &command) {
34 if (!this->connection_state_.ignore_is_setup_ && !this->is_setup()) {
35 return false;
36 }
37
38#ifdef USE_NEXTION_COMMAND_SPACING
40 if (!this->connection_state_.ignore_is_setup_ && !this->command_pacer_.can_send(now)) {
41 ESP_LOGN(TAG, "Command spacing: delaying '%s'", command.c_str());
42 return false;
43 }
44#endif // USE_NEXTION_COMMAND_SPACING
45
46 ESP_LOGN(TAG, "cmd: %s", command.c_str());
47
48 this->write_str(command.c_str());
49 const uint8_t to_send[3] = {0xFF, 0xFF, 0xFF};
50 this->write_array(to_send, sizeof(to_send));
51
52#ifdef USE_NEXTION_COMMAND_SPACING
53 // Mark sent immediately after writing to UART. The pacer enforces inter-command
54 // spacing from the transmit side. Marking on ACK (0x01) would leave last_command_time_
55 // at zero indefinitely, making can_send() always return true and spacing a no-op.
56 // ignore_is_setup_ commands (setup/init sequence) bypass spacing intentionally.
57 if (!this->connection_state_.ignore_is_setup_) {
58 this->command_pacer_.mark_sent(now);
59 }
60#endif // USE_NEXTION_COMMAND_SPACING
61
62 return true;
63}
64
66 if (this->connection_state_.is_connected_)
67 return true;
68
69#ifdef USE_NEXTION_CONFIG_SKIP_CONNECTION_HANDSHAKE
70 ESP_LOGW(TAG, "Connected (no handshake)"); // Log the connection status without handshake
71 this->connection_state_.is_connected_ = true; // Set the connection status to true
72 return true; // Return true indicating the connection is set
73#else // USE_NEXTION_CONFIG_SKIP_CONNECTION_HANDSHAKE
74 if (this->comok_sent_ == 0) {
75 this->reset_(false);
76
77 this->connection_state_.ignore_is_setup_ = true;
78 this->send_command_("boguscommand=0"); // bogus command. needed sometimes after updating
79#ifdef USE_NEXTION_CONFIG_EXIT_REPARSE_ON_START
80 this->send_command_("DRAKJHSUYDGBNCJHGJKSHBDN");
81#endif // USE_NEXTION_CONFIG_EXIT_REPARSE_ON_START
82 this->send_command_("connect");
83
85 this->connection_state_.ignore_is_setup_ = false;
86
87 return false;
88 }
89
90 if (App.get_loop_component_start_time() - this->comok_sent_ <= 500) // Wait 500 ms
91 return false;
92
93 std::string response;
94
95 this->recv_ret_string_(response, 0, false);
96 if (!response.empty() && response[0] == 0x1A) {
97 // Swallow invalid variable name responses that may be caused by the above commands
98 ESP_LOGV(TAG, "0x1A error ignored (setup)");
99 return false;
100 }
101 if (response.empty() || response.find("comok") == std::string::npos) {
102#ifdef NEXTION_PROTOCOL_LOG
103 ESP_LOGN(TAG, "Bad connect: %s", response.c_str());
104 for (size_t i = 0; i < response.length(); i++) {
105 ESP_LOGN(TAG, "resp: %s %d %d %c", response.c_str(), i, response[i], response[i]);
106 }
107#endif // NEXTION_PROTOCOL_LOG
108
109 ESP_LOGW(TAG, "Not connected");
110 this->comok_sent_ = 0;
111 return false;
112 }
113
114 this->connection_state_.ignore_is_setup_ = true;
115 ESP_LOGI(TAG, "Connected");
116 this->connection_state_.is_connected_ = true;
117
118 ESP_LOGN(TAG, "connect: %s", response.c_str());
119
120 size_t start;
121 size_t end = 0;
122 std::vector<std::string> connect_info;
123 while ((start = response.find_first_not_of(',', end)) != std::string::npos) {
124 end = response.find(',', start);
125 connect_info.push_back(response.substr(start, end - start));
126 }
127
128 this->is_detected_ = (connect_info.size() == 7);
129 if (this->is_detected_) {
130 ESP_LOGN(TAG, "Connect info: %zu", connect_info.size());
131#ifdef USE_NEXTION_CONFIG_DUMP_DEVICE_INFO
132 this->device_model_ = connect_info[2];
133 this->firmware_version_ = connect_info[3];
134 this->serial_number_ = connect_info[5];
135 this->flash_size_ = connect_info[6];
136#else // USE_NEXTION_CONFIG_DUMP_DEVICE_INFO
137 ESP_LOGI(TAG,
138 " Device Model: %s\n"
139 " FW Version: %s\n"
140 " Serial Number: %s\n"
141 " Flash Size: %s\n",
142 connect_info[2].c_str(), connect_info[3].c_str(), connect_info[5].c_str(), connect_info[6].c_str());
143#endif // USE_NEXTION_CONFIG_DUMP_DEVICE_INFO
144 } else {
145 ESP_LOGE(TAG, "Bad connect value: '%s'", response.c_str());
146 }
147
148 this->connection_state_.ignore_is_setup_ = false;
149 this->dump_config();
150 return true;
151#endif // USE_NEXTION_CONFIG_SKIP_CONNECTION_HANDSHAKE
152}
153
154void Nextion::reset_(bool reset_nextion) {
155 uint8_t d;
156
157 while (this->available()) { // Clear receive buffer
158 this->read_byte(&d);
159 }
160 for (auto *entry : this->nextion_queue_) {
161 if (entry->component != nullptr && entry->component->get_queue_type() == NextionQueueType::NO_RESULT) {
162 delete entry->component; // NOLINT(cppcoreguidelines-owning-memory)
163 }
164 delete entry; // NOLINT(cppcoreguidelines-owning-memory)
165 }
166 this->nextion_queue_.clear();
167#ifdef USE_NEXTION_WAVEFORM
168 for (auto *entry : this->waveform_queue_) {
169 delete entry; // NOLINT(cppcoreguidelines-owning-memory)
170 }
171 this->waveform_queue_.clear();
172#endif // USE_NEXTION_WAVEFORM
173}
174
176 ESP_LOGCONFIG(TAG, "Nextion:");
177
178#ifdef USE_NEXTION_CONFIG_SKIP_CONNECTION_HANDSHAKE
179 ESP_LOGCONFIG(TAG, " Skip handshake: YES");
180#else // USE_NEXTION_CONFIG_SKIP_CONNECTION_HANDSHAKE
181#ifdef USE_NEXTION_CONFIG_DUMP_DEVICE_INFO
182 ESP_LOGCONFIG(TAG,
183 " Device Model: %s\n"
184 " FW Version: %s\n"
185 " Serial Number: %s\n"
186 " Flash Size: %s\n"
187 " Max queue age: %u ms\n"
188 " Startup override: %u ms\n",
189 this->device_model_.c_str(), this->firmware_version_.c_str(), this->serial_number_.c_str(),
190 this->flash_size_.c_str(), this->max_q_age_ms_, this->startup_override_ms_);
191#endif // USE_NEXTION_CONFIG_DUMP_DEVICE_INFO
192#ifdef USE_NEXTION_CONFIG_EXIT_REPARSE_ON_START
193 ESP_LOGCONFIG(TAG, " Exit reparse: YES\n");
194#endif // USE_NEXTION_CONFIG_EXIT_REPARSE_ON_START
195 ESP_LOGCONFIG(TAG,
196 " Wake On Touch: %s\n"
197 " Touch Timeout: %" PRIu16,
198 YESNO(this->connection_state_.auto_wake_on_touch_), this->touch_sleep_timeout_);
199#endif // USE_NEXTION_CONFIG_SKIP_CONNECTION_HANDSHAKE
200
201#ifdef USE_NEXTION_MAX_COMMANDS_PER_LOOP
202 ESP_LOGCONFIG(TAG, " Max commands per loop: %u", this->max_commands_per_loop_);
203#endif // USE_NEXTION_MAX_COMMANDS_PER_LOOP
204
205 if (this->wake_up_page_ != 255) {
206 ESP_LOGCONFIG(TAG, " Wake Up Page: %u", this->wake_up_page_);
207 }
208
209#ifdef USE_NEXTION_CONF_START_UP_PAGE
210 if (this->start_up_page_ != 255) {
211 ESP_LOGCONFIG(TAG, " Start Up Page: %u", this->start_up_page_);
212 }
213#endif // USE_NEXTION_CONF_START_UP_PAGE
214
215#ifdef USE_NEXTION_COMMAND_SPACING
216 ESP_LOGCONFIG(TAG, " Cmd spacing: %u ms", this->command_pacer_.get_spacing());
217#endif // USE_NEXTION_COMMAND_SPACING
218
219#ifdef USE_NEXTION_MAX_QUEUE_SIZE
220 ESP_LOGCONFIG(TAG, " Max queue size: %zu", this->max_queue_size_);
221#endif
222#ifdef USE_NEXTION_TFT_UPLOAD
223 ESP_LOGCONFIG(TAG,
224 " TFT URL: %s\n"
225 " TFT upload HTTP timeout: %" PRIu16 "ms\n"
226 " TFT upload HTTP retries: %u",
227 this->tft_url_.c_str(), this->tft_upload_http_timeout_, this->tft_upload_http_retries_);
228#ifdef USE_ESP32
229 if (this->tft_upload_watchdog_timeout_ > 0) {
230 ESP_LOGCONFIG(TAG, " TFT upload WDT timeout: %" PRIu32 "ms", this->tft_upload_watchdog_timeout_);
231 }
232#endif // USE_ESP32
233#endif // USE_NEXTION_TFT_UPLOAD
234}
235
236void Nextion::update() {
237 if (!this->is_setup()) {
238 return;
239 }
240 if (this->writer_.has_value()) {
241 (*this->writer_)(*this);
242 }
243}
244
246 if ((!this->is_setup() && !this->connection_state_.ignore_is_setup_) || this->is_sleeping())
247 return;
248
249 for (auto *binarysensortype : this->binarysensortype_) {
250 binarysensortype->update_component();
251 }
252 for (auto *sensortype : this->sensortype_) {
253 sensortype->update_component();
254 }
255 for (auto *switchtype : this->switchtype_) {
256 switchtype->update_component();
257 }
258 for (auto *textsensortype : this->textsensortype_) {
259 textsensortype->update_component();
260 }
261}
262
263bool Nextion::send_command(const char *command) {
264 if ((!this->is_setup() && !this->connection_state_.ignore_is_setup_) || this->is_sleeping())
265 return false;
266
267 this->add_no_result_to_queue_with_command_("command", command);
268 return true;
269}
270
271bool Nextion::send_command_printf(const char *format, ...) {
272 if ((!this->is_setup() && !this->connection_state_.ignore_is_setup_) || this->is_sleeping())
273 return false;
274
275 char buffer[256];
276 va_list arg;
277 va_start(arg, format);
278 int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
279 va_end(arg);
280 if (ret <= 0) {
281 ESP_LOGW(TAG, "Bad cmd format: '%s'", format);
282 return false;
283 }
284
285 this->add_no_result_to_queue_with_command_("command_printf", buffer);
286 return true;
287}
288
289#ifdef NEXTION_PROTOCOL_LOG
291 ESP_LOGN(TAG, "print_queue_members_ (top 10) size %zu", this->nextion_queue_.size());
292 ESP_LOGN(TAG, "*******************************************");
293 int count = 0;
294 for (auto *i : this->nextion_queue_) {
295 if (count++ == 10)
296 break;
297
298 if (i == nullptr) {
299 ESP_LOGN(TAG, "Queue null");
300 } else {
301 ESP_LOGN(TAG, "Queue type: %d:%s, name: %s", i->component->get_queue_type(),
302 i->component->get_queue_type_string(), i->component->get_variable_name().c_str());
303 }
304 }
305 ESP_LOGN(TAG, "*******************************************");
306}
307#endif
308
309void Nextion::loop() {
310 if (!this->check_connect_() || this->connection_state_.is_updating_)
311 return;
312
313 if (this->connection_state_.nextion_reports_is_setup_ && !this->connection_state_.sent_setup_commands_) {
314 this->connection_state_.ignore_is_setup_ = true;
315 this->connection_state_.sent_setup_commands_ = true;
316 this->send_command_("bkcmd=3"); // Always, returns 0x00 to 0x23 result of serial command.
317
318 if (this->brightness_.has_value()) {
319 this->set_backlight_brightness(this->brightness_.value());
320 }
321
322#ifdef USE_NEXTION_CONF_START_UP_PAGE
323 // Check if a startup page has been set and send the command
324 if (this->start_up_page_ != 255) {
325 this->goto_page(this->start_up_page_);
326 }
327#endif // USE_NEXTION_CONF_START_UP_PAGE
328
329 if (this->wake_up_page_ != 255) {
330 this->set_wake_up_page(this->wake_up_page_);
331 }
332
333 if (this->touch_sleep_timeout_ != 0) {
335 }
336
337 this->set_auto_wake_on_touch(this->connection_state_.auto_wake_on_touch_);
338
339 this->connection_state_.ignore_is_setup_ = false;
340 }
341
342 this->process_serial_(); // Receive serial data
343 this->process_nextion_commands_(); // Process nextion return commands
344
345 if (!this->connection_state_.nextion_reports_is_setup_) {
346 if (this->started_ms_ == 0)
348
349 if (this->startup_override_ms_ > 0 &&
350 App.get_loop_component_start_time() - this->started_ms_ > this->startup_override_ms_) {
351 ESP_LOGV(TAG, "Manual ready set");
352 this->connection_state_.nextion_reports_is_setup_ = true;
353 }
354 }
355
356#ifdef USE_NEXTION_COMMAND_SPACING
358#ifdef USE_NEXTION_WAVEFORM
359 if (!this->waveform_queue_.empty()) {
361 }
362#endif // USE_NEXTION_WAVEFORM
363#endif // USE_NEXTION_COMMAND_SPACING
364}
365
366#ifdef USE_NEXTION_COMMAND_SPACING
368#ifdef USE_NEXTION_MAX_COMMANDS_PER_LOOP
369 size_t commands_sent = 0;
370#endif // USE_NEXTION_MAX_COMMANDS_PER_LOOP
371
372 for (auto *item : this->nextion_queue_) {
373 if (item == nullptr || item->pending_command.empty()) {
374 continue; // Already sent, waiting for ACK — skip, don't stop
375 }
376
377#ifdef USE_NEXTION_MAX_COMMANDS_PER_LOOP
378 if (++commands_sent > this->max_commands_per_loop_) {
379 ESP_LOGV(TAG, "Pending cmds: loop limit reached, deferring");
380 break;
381 }
382#endif // USE_NEXTION_MAX_COMMANDS_PER_LOOP
383
385 if (!this->command_pacer_.can_send(now)) {
386 break; // Spacing not elapsed, stop for this loop iteration
387 }
388
389 if (!this->send_command_(item->pending_command)) {
390 break; // Unexpected send failure, stop
391 }
392 item->pending_command.clear();
393 ESP_LOGVV(TAG, "Pending cmd sent: %s", item->component->get_variable_name().c_str());
394 }
395}
396#endif // USE_NEXTION_COMMAND_SPACING
397
398bool Nextion::remove_from_q_(bool report_empty) {
399 if (this->nextion_queue_.empty()) {
400 if (report_empty) {
401 ESP_LOGE(TAG, "Queue empty");
402 }
403 return false;
404 }
405
406 NextionQueue *nb = this->nextion_queue_.front();
407 if (!nb || !nb->component) {
408 ESP_LOGE(TAG, "Invalid queue");
409 this->nextion_queue_.pop_front();
410 return false;
411 }
412 NextionComponentBase *component = nb->component;
413
414 ESP_LOGN(TAG, "Removed: %s", component->get_variable_name().c_str());
415
416 if (component->get_queue_type() == NextionQueueType::NO_RESULT) {
417 if (component->get_variable_name() == "sleep_wake") {
418 this->is_sleeping_ = false;
419 }
420 delete component; // NOLINT(cppcoreguidelines-owning-memory)
421 }
422 delete nb; // NOLINT(cppcoreguidelines-owning-memory)
423 this->nextion_queue_.pop_front();
424 return true;
425}
426
428 // Read all available bytes in batches to reduce UART call overhead.
429 size_t avail = this->available();
430 uint8_t buf[64];
431 while (avail > 0) {
432 size_t to_read = std::min(avail, sizeof(buf));
433 if (!this->read_array(buf, to_read)) {
434 break;
435 }
436 avail -= to_read;
437
438 this->command_data_.append(reinterpret_cast<const char *>(buf), to_read);
439 }
440}
441// nextion.tech/instruction-set/
443 if (this->command_data_.empty()) {
444 return;
445 }
446
447#ifdef USE_NEXTION_MAX_COMMANDS_PER_LOOP
448 size_t commands_processed = 0;
449#endif // USE_NEXTION_MAX_COMMANDS_PER_LOOP
450
451 size_t to_process_length = 0;
452 std::string to_process;
453
454 ESP_LOGN(TAG, "command_data_ %s len %d", this->command_data_.c_str(), this->command_data_.length());
455#ifdef NEXTION_PROTOCOL_LOG
456 this->print_queue_members_();
457#endif
458 while ((to_process_length = this->command_data_.find(reinterpret_cast<const char *>(COMMAND_DELIMITER), 0,
459 DELIMITER_SIZE)) != std::string::npos) {
460#ifdef USE_NEXTION_MAX_COMMANDS_PER_LOOP
461 if (++commands_processed > this->max_commands_per_loop_) {
462 ESP_LOGV(TAG, "Command limit reached, deferring");
463 break;
464 }
465#endif // USE_NEXTION_MAX_COMMANDS_PER_LOOP
466 ESP_LOGN(TAG, "queue size: %zu", this->nextion_queue_.size());
467 while (to_process_length + DELIMITER_SIZE < this->command_data_.length() &&
468 static_cast<uint8_t>(this->command_data_[to_process_length + DELIMITER_SIZE]) == 0xFF) {
469 ++to_process_length;
470 ESP_LOGN(TAG, "Add 0xFF");
471 }
472
473 const uint8_t nextion_event = this->command_data_[0];
474
475 to_process_length -= 1;
476 to_process = this->command_data_.substr(1, to_process_length);
477
478 switch (nextion_event) {
479 case 0x00: // instruction sent by user has failed
480 ESP_LOGW(TAG, "Invalid instruction");
481 this->remove_from_q_();
482
483 break;
484 case 0x01: // instruction sent by user was successful
485
486 ESP_LOGVV(TAG, "Cmd OK");
487 ESP_LOGN(TAG, "this->nextion_queue_.empty() %s", YESNO(this->nextion_queue_.empty()));
488
489 this->remove_from_q_();
490 if (!this->is_setup_) {
491 if (this->nextion_queue_.empty()) {
492 this->is_setup_ = true;
493 this->setup_callback_.call();
494 }
495 }
496 break;
497 case 0x02: // invalid Component ID or name was used
498 ESP_LOGW(TAG, "Invalid component ID/name");
499 this->remove_from_q_();
500 break;
501 case 0x03: // invalid Page ID or name was used
502 ESP_LOGW(TAG, "Invalid page ID");
503 this->remove_from_q_();
504 break;
505 case 0x04: // invalid Picture ID was used
506 ESP_LOGW(TAG, "Invalid picture ID");
507 this->remove_from_q_();
508 break;
509 case 0x05: // invalid Font ID was used
510 ESP_LOGW(TAG, "Invalid font ID");
511 this->remove_from_q_();
512 break;
513 case 0x06: // File operation fails
514 ESP_LOGW(TAG, "File operation failed");
515 break;
516 case 0x09: // Instructions with CRC validation fails their CRC check
517 ESP_LOGW(TAG, "CRC validation failed");
518 break;
519 case 0x11: // invalid Baud rate was used
520 ESP_LOGW(TAG, "Invalid baud rate");
521 break;
522 case 0x12: // invalid Waveform ID or Channel # was used
523#ifdef USE_NEXTION_WAVEFORM
524 if (this->waveform_queue_.empty()) {
525 ESP_LOGW(TAG, "Waveform ID/ch used but no sensor queued");
526 } else {
527 auto &nb = this->waveform_queue_.front();
528 NextionComponentBase *component = nb->component;
529 ESP_LOGW(TAG, "Invalid waveform ID %d/ch %d", component->get_component_id(),
530 component->get_wave_channel_id());
531 ESP_LOGN(TAG, "Remove waveform ID %d/ch %d", component->get_component_id(), component->get_wave_channel_id());
532 delete nb; // NOLINT(cppcoreguidelines-owning-memory)
533 this->waveform_queue_.pop();
534 }
535#else // USE_NEXTION_WAVEFORM
536 ESP_LOGW(TAG, "Waveform ID/ch error but waveform not enabled");
537#endif // USE_NEXTION_WAVEFORM
538 break;
539 case 0x1A: // variable name invalid
540 ESP_LOGW(TAG, "Invalid variable name");
541 this->remove_from_q_();
542 break;
543 case 0x1B: // variable operation invalid
544 ESP_LOGW(TAG, "Invalid variable operation");
545 this->remove_from_q_();
546 break;
547 case 0x1C: // failed to assign
548 ESP_LOGW(TAG, "Variable assign failed");
549 this->remove_from_q_();
550 break;
551 case 0x1D: // operate EEPROM failed
552 ESP_LOGW(TAG, "EEPROM operation failed");
553 break;
554 case 0x1E: // parameter quantity invalid
555 ESP_LOGW(TAG, "Invalid parameter count");
556 this->remove_from_q_();
557 break;
558 case 0x1F: // IO operation failed
559 ESP_LOGW(TAG, "Invalid component I/O");
560 break;
561 case 0x20: // undefined escape characters
562 ESP_LOGW(TAG, "Undefined escape chars");
563 this->remove_from_q_();
564 break;
565 case 0x23: // too long variable name
566 ESP_LOGW(TAG, "Variable name too long");
567 this->remove_from_q_();
568 break;
569 case 0x24: // Serial Buffer overflow occurs
570 // Buffer will continue to receive the current instruction, all previous instructions are lost.
571 ESP_LOGE(TAG, "Serial buffer overflow");
572 this->buffer_overflow_callback_.call();
573 break;
574 case 0x65: { // touch event return data
575 if (to_process_length != 3) {
576 ESP_LOGW(TAG, "Incorrect touch len: %zu (need 3)", to_process_length);
577 break;
578 }
579
580 uint8_t page_id = to_process[0];
581 uint8_t component_id = to_process[1];
582 uint8_t touch_event = to_process[2]; // 0 -> release, 1 -> press
583 ESP_LOGV(TAG, "Touch %s: page %u comp %u", touch_event ? "PRESS" : "RELEASE", page_id, component_id);
584 for (auto *touch : this->touch_) {
585 touch->process_touch(page_id, component_id, touch_event != 0);
586 }
587 this->touch_callback_.call(page_id, component_id, touch_event != 0);
588 break;
589 }
590 case 0x66: { // Nextion initiated new page event return data.
591 // Also is used for sendme command which we never explicitly initiate
592 if (to_process_length != 1) {
593 ESP_LOGW(TAG, "Page event: expect 1, got %zu", to_process_length);
594 break;
595 }
596
597 uint8_t page_id = to_process[0];
598 ESP_LOGV(TAG, "New page: %u", page_id);
599 this->page_callback_.call(page_id);
600 break;
601 }
602 case 0x67: { // Touch Coordinate (awake)
603 break;
604 }
605 case 0x68: { // touch coordinate data (sleep)
606
607 if (to_process_length != 5) {
608 ESP_LOGW(TAG, "Touch coordinate: expect 5, got %zu", to_process_length);
609 ESP_LOGW(TAG, "%s", to_process.c_str());
610 break;
611 }
612
613 const uint16_t x = (uint16_t(to_process[0]) << 8) | to_process[1];
614 const uint16_t y = (uint16_t(to_process[2]) << 8) | to_process[3];
615 const uint8_t touch_event = to_process[4]; // 0 -> release, 1 -> press
616 ESP_LOGV(TAG, "Touch %s at %u,%u", touch_event ? "PRESS" : "RELEASE", x, y);
617 break;
618 }
619
620 // 0x70 0x61 0x62 0x31 0x32 0x33 0xFF 0xFF 0xFF
621 // Returned when using get command for a string.
622 // Each byte is converted to char.
623 // data: ab123
624 case 0x70: // string variable data return
625 {
626 if (this->nextion_queue_.empty()) {
627 ESP_LOGW(TAG, "String return but queue is empty");
628 break;
629 }
630
631 NextionQueue *nb = this->nextion_queue_.front();
632 if (!nb || !nb->component) {
633 ESP_LOGE(TAG, "Invalid queue entry");
634 this->nextion_queue_.pop_front();
635 return;
636 }
637 NextionComponentBase *component = nb->component;
638
639 if (component->get_queue_type() != NextionQueueType::TEXT_SENSOR) {
640 ESP_LOGE(TAG, "String return but '%s' not text sensor", component->get_variable_name().c_str());
641 } else {
642 ESP_LOGN(TAG, "String resp: '%s' id: %s type: %s", to_process.c_str(), component->get_variable_name().c_str(),
643 component->get_queue_type_string());
644 component->set_state_from_string(to_process, true, false);
645 }
646
647 delete nb; // NOLINT(cppcoreguidelines-owning-memory)
648 this->nextion_queue_.pop_front();
649
650 break;
651 }
652 // 0x71 0x01 0x02 0x03 0x04 0xFF 0xFF 0xFF
653 // Returned when get command to return a number
654 // 4 byte 32-bit value in little endian order.
655 // (0x01+0x02*256+0x03*65536+0x04*16777216)
656 // data: 67305985
657 case 0x71: // numeric variable data return
658 {
659 if (this->nextion_queue_.empty()) {
660 ESP_LOGE(TAG, "Numeric return but queue empty");
661 break;
662 }
663
664 if (to_process_length < 4) {
665 ESP_LOGE(TAG, "Numeric return but insufficient data (need 4, got %zu)", to_process_length);
666 break;
667 }
668
669 int value = static_cast<int>(encode_uint32(to_process[3], to_process[2], to_process[1], to_process[0]));
670
671 NextionQueue *nb = this->nextion_queue_.front();
672 if (!nb || !nb->component) {
673 ESP_LOGE(TAG, "Invalid queue");
674 this->nextion_queue_.pop_front();
675 return;
676 }
677 NextionComponentBase *component = nb->component;
678
679 if (component->get_queue_type() != NextionQueueType::SENSOR &&
680 component->get_queue_type() != NextionQueueType::BINARY_SENSOR &&
681 component->get_queue_type() != NextionQueueType::SWITCH) {
682 ESP_LOGE(TAG, "Numeric return but '%s' invalid type %d", component->get_variable_name().c_str(),
683 component->get_queue_type());
684 } else {
685 ESP_LOGN(TAG, "Numeric: %s type %d:%s val %d", component->get_variable_name().c_str(),
686 component->get_queue_type(), component->get_queue_type_string(), value);
687 component->set_state_from_int(value, true, false);
688 }
689
690 delete nb; // NOLINT(cppcoreguidelines-owning-memory)
691 this->nextion_queue_.pop_front();
692
693 break;
694 }
695
696 case 0x86: { // device automatically enters into sleep mode
697 ESP_LOGVV(TAG, "Auto sleep");
698 this->is_sleeping_ = true;
699 this->sleep_callback_.call();
700 break;
701 }
702 case 0x87: // device automatically wakes up
703 {
704 ESP_LOGVV(TAG, "Auto wake");
705 this->is_sleeping_ = false;
706 this->wake_callback_.call();
707 this->all_components_send_state_(false);
708 break;
709 }
710 case 0x88: // system successful start up
711 {
712 ESP_LOGV(TAG, "System start: %zu", to_process_length);
713 this->connection_state_.nextion_reports_is_setup_ = true;
714 break;
715 }
716 case 0x89: { // start SD card upgrade
717 break;
718 }
719 // Data from nextion is
720 // 0x90 - Start
721 // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0
722 // 00 - NULL
723 // 00/01 - Single byte for on/off
724 // FF FF FF - End
725 case 0x90: { // Switched component
726 std::string variable_name;
727
728 // Get variable name
729 auto index = to_process.find('\0');
730 if (index == std::string::npos || (to_process_length - index - 1) < 1) {
731 ESP_LOGE(TAG, "Bad switch data (0x90)");
732 ESP_LOGN(TAG, "proc: %s %zu %zu", to_process.c_str(), to_process_length, index);
733 break;
734 }
735
736 variable_name = to_process.substr(0, index);
737 ++index;
738
739 ESP_LOGN(TAG, "Switch %s: %s", ONOFF(to_process[index] != 0), variable_name.c_str());
740
741#ifdef USE_NEXTION_TRIGGER_CUSTOM_SWITCH
742 this->custom_switch_callback_.call(StringRef(variable_name), to_process[index] != 0);
743#endif // USE_NEXTION_TRIGGER_CUSTOM_SWITCH
744
745 for (auto *switchtype : this->switchtype_) {
746 switchtype->process_bool(variable_name, to_process[index] != 0);
747 }
748 break;
749 }
750 // Data from nextion is
751 // 0x91 - Start
752 // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0
753 // 00 - NULL
754 // variable length of 0x71 return data: prints temp1.val,0
755 // FF FF FF - End
756 case 0x91: { // Sensor component
757 std::string variable_name;
758
759 auto index = to_process.find('\0');
760 if (index == std::string::npos || (to_process_length - index - 1) != 4) {
761 ESP_LOGE(TAG, "Bad sensor data (0x91)");
762 ESP_LOGN(TAG, "proc: %s %zu %zu", to_process.c_str(), to_process_length, index);
763 break;
764 }
765
766 index = to_process.find('\0');
767 variable_name = to_process.substr(0, index);
768 // // Get variable name
769 int value = static_cast<int>(
770 encode_uint32(to_process[index + 4], to_process[index + 3], to_process[index + 2], to_process[index + 1]));
771
772 ESP_LOGN(TAG, "Sensor: %s=%d", variable_name.c_str(), value);
773
774#ifdef USE_NEXTION_TRIGGER_CUSTOM_SENSOR
775 this->custom_sensor_callback_.call(StringRef(variable_name), value);
776#endif // USE_NEXTION_TRIGGER_CUSTOM_SENSOR
777
778 for (auto *sensor : this->sensortype_) {
779 sensor->process_sensor(variable_name, value);
780 }
781 break;
782 }
783
784 // Data from nextion is
785 // 0x92 - Start
786 // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0
787 // 00 - NULL
788 // variable length of 0x70 return formatted data (bytes) that contain the text prints temp1.txt,0
789 // 00 - NULL
790 // FF FF FF - End
791 case 0x92: { // Text Sensor Component
792 std::string variable_name;
793 std::string text_value;
794
795 // Get variable name
796 auto index = to_process.find('\0');
797 if (index == std::string::npos || (to_process_length - index - 1) < 1) {
798 ESP_LOGE(TAG, "Bad text data (0x92)");
799 ESP_LOGN(TAG, "proc: %s %zu %zu", to_process.c_str(), to_process_length, index);
800 break;
801 }
802
803 variable_name = to_process.substr(0, index);
804 ++index;
805
806 // Get variable value without terminating NUL byte. Length check above ensures substr len >= 0.
807 text_value = to_process.substr(index, to_process_length - index - 1);
808
809 ESP_LOGN(TAG, "Text sensor: %s='%s'", variable_name.c_str(), text_value.c_str());
810
811 // NextionTextSensorResponseQueue *nq = new NextionTextSensorResponseQueue;
812 // nq->variable_name = variable_name;
813 // nq->state = text_value;
814 // this->textsensorq_.push_back(nq);
815
816#ifdef USE_NEXTION_TRIGGER_CUSTOM_TEXT_SENSOR
817 this->custom_text_sensor_callback_.call(StringRef(variable_name), StringRef(text_value));
818#endif // USE_NEXTION_TRIGGER_CUSTOM_TEXT_SENSOR
819
820 for (auto *textsensortype : this->textsensortype_) {
821 textsensortype->process_text(variable_name, text_value);
822 }
823 break;
824 }
825 // Data from nextion is
826 // 0x93 - Start
827 // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0
828 // 00 - NULL
829 // 00/01 - Single byte for on/off
830 // FF FF FF - End
831 case 0x93: { // Binary Sensor component
832 std::string variable_name;
833
834 // Get variable name
835 auto index = to_process.find('\0');
836 if (index == std::string::npos || (to_process_length - index - 1) < 1) {
837 ESP_LOGE(TAG, "Bad binary data (0x93)");
838 ESP_LOGN(TAG, "proc: %s %zu %zu", to_process.c_str(), to_process_length, index);
839 break;
840 }
841
842 variable_name = to_process.substr(0, index);
843 ++index;
844
845 ESP_LOGN(TAG, "Binary sensor: %s=%s", variable_name.c_str(), ONOFF(to_process[index] != 0));
846
847#ifdef USE_NEXTION_TRIGGER_CUSTOM_BINARY_SENSOR
848 this->custom_binary_sensor_callback_.call(StringRef(variable_name), to_process[index] != 0);
849#endif // USE_NEXTION_TRIGGER_CUSTOM_BINARY_SENSOR
850
851 for (auto *binarysensortype : this->binarysensortype_) {
852 binarysensortype->process_bool(&variable_name[0], to_process[index] != 0);
853 }
854 break;
855 }
856 case 0xFD: { // data transparent transmit finished
857 ESP_LOGVV(TAG, "Data transmit done");
858#ifdef USE_NEXTION_WAVEFORM
860#endif // USE_NEXTION_WAVEFORM
861 break;
862 }
863 case 0xFE: { // data transparent transmit ready
864 ESP_LOGVV(TAG, "Ready for transmit");
865#ifdef USE_NEXTION_WAVEFORM
866 if (this->waveform_queue_.empty()) {
867 ESP_LOGE(TAG, "No waveforms queued");
868 break;
869 }
870 auto &nb = this->waveform_queue_.front();
871 auto *component = nb->component;
872 size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size() : 255;
873 this->write_array(component->get_wave_buffer().data(), static_cast<int>(buffer_to_send));
874 ESP_LOGN(TAG, "Send waveform: component id %d, waveform id %d, size %zu", component->get_component_id(),
875 component->get_wave_channel_id(), buffer_to_send);
876 component->clear_wave_buffer(buffer_to_send);
877 delete nb; // NOLINT(cppcoreguidelines-owning-memory)
878 this->waveform_queue_.pop();
879#else // USE_NEXTION_WAVEFORM
880 ESP_LOGW(TAG, "Waveform transmit ready but waveform not enabled");
881#endif // USE_NEXTION_WAVEFORM
882 break;
883 }
884 default:
885 ESP_LOGW(TAG, "Unknown event: 0x%02X", nextion_event);
886 break;
887 }
888
889 this->command_data_.erase(0, to_process_length + DELIMITER_SIZE + 1);
890 }
891
893
894 if (this->max_q_age_ms_ > 0 && !this->nextion_queue_.empty() &&
895 ms - this->nextion_queue_.front()->queue_time > this->max_q_age_ms_) {
896 for (auto it = this->nextion_queue_.begin(); it != this->nextion_queue_.end();) {
897 if (ms - (*it)->queue_time > this->max_q_age_ms_) {
898 NextionComponentBase *component = (*it)->component;
899 ESP_LOGV(TAG, "Remove old queue '%s':'%s'", component->get_queue_type_string(),
900 component->get_variable_name().c_str());
901
902 if (component->get_queue_type() == NextionQueueType::NO_RESULT) {
903 if (component->get_variable_name() == "sleep_wake") {
904 this->is_sleeping_ = false;
905 }
906 delete component; // NOLINT(cppcoreguidelines-owning-memory)
907 }
908
909 delete *it; // NOLINT(cppcoreguidelines-owning-memory)
910 it = this->nextion_queue_.erase(it);
911
912 } else {
913 break;
914 }
915 }
916 }
917 ESP_LOGN(TAG, "Loop end");
918 // App.feed_wdt(); Remove before master merge
919 this->process_serial_();
920} // Nextion::process_nextion_commands_()
921
922void Nextion::set_nextion_sensor_state(int queue_type, const std::string &name, float state) {
923 this->set_nextion_sensor_state(static_cast<NextionQueueType>(queue_type), name, state);
924}
925
926void Nextion::set_nextion_sensor_state(NextionQueueType queue_type, const std::string &name, float state) {
927 ESP_LOGN(TAG, "State: %s=%lf (type %d)", name.c_str(), state, queue_type);
928
929 switch (queue_type) {
931 for (auto *sensor : this->sensortype_) {
932 if (name == sensor->get_variable_name()) {
933 sensor->set_state(state, true, true);
934 break;
935 }
936 }
937 break;
938 }
940 for (auto *sensor : this->binarysensortype_) {
941 if (name == sensor->get_variable_name()) {
942 sensor->set_state(state != 0, true, true);
943 break;
944 }
945 }
946 break;
947 }
949 for (auto *sensor : this->switchtype_) {
950 if (name == sensor->get_variable_name()) {
951 sensor->set_state(state != 0, true, true);
952 break;
953 }
954 }
955 break;
956 }
957 default: {
958 ESP_LOGW(TAG, "set_sensor_state: bad type %d", queue_type);
959 }
960 }
961}
962
963void Nextion::set_nextion_text_state(const std::string &name, const std::string &state) {
964 ESP_LOGV(TAG, "State: %s='%s'", name.c_str(), state.c_str());
965
966 for (auto *sensor : this->textsensortype_) {
967 if (name == sensor->get_variable_name()) {
968 sensor->set_state(state, true, true);
969 break;
970 }
971 }
972}
973
974void Nextion::all_components_send_state_(bool force_update) {
975 ESP_LOGV(TAG, "Send states");
976 for (auto *binarysensortype : this->binarysensortype_) {
977 if (force_update || binarysensortype->get_needs_to_send_update())
978 binarysensortype->send_state_to_nextion();
979 }
980 for (auto *sensortype : this->sensortype_) {
981#ifdef USE_NEXTION_WAVEFORM
982 if ((force_update || sensortype->get_needs_to_send_update()) && sensortype->get_wave_channel_id() == UINT8_MAX) {
983#else // USE_NEXTION_WAVEFORM
984 if (force_update || sensortype->get_needs_to_send_update()) {
985#endif // USE_NEXTION_WAVEFORM
986 sensortype->send_state_to_nextion();
987 }
988 }
989 for (auto *switchtype : this->switchtype_) {
990 if (force_update || switchtype->get_needs_to_send_update())
991 switchtype->send_state_to_nextion();
992 }
993 for (auto *textsensortype : this->textsensortype_) {
994 if (force_update || textsensortype->get_needs_to_send_update())
995 textsensortype->send_state_to_nextion();
996 }
997}
998
999void Nextion::update_components_by_prefix(const std::string &prefix) {
1000 for (auto *binarysensortype : this->binarysensortype_) {
1001 if (binarysensortype->get_variable_name().find(prefix, 0) != std::string::npos)
1002 binarysensortype->update_component_settings(true);
1003 }
1004 for (auto *sensortype : this->sensortype_) {
1005 if (sensortype->get_variable_name().find(prefix, 0) != std::string::npos)
1006 sensortype->update_component_settings(true);
1007 }
1008 for (auto *switchtype : this->switchtype_) {
1009 if (switchtype->get_variable_name().find(prefix, 0) != std::string::npos)
1010 switchtype->update_component_settings(true);
1011 }
1012 for (auto *textsensortype : this->textsensortype_) {
1013 if (textsensortype->get_variable_name().find(prefix, 0) != std::string::npos)
1014 textsensortype->update_component_settings(true);
1015 }
1016}
1017
1018uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag) {
1019 uint8_t c = 0;
1020 uint8_t nr_of_ff_bytes = 0;
1021 bool exit_flag = false;
1022 bool ff_flag = false;
1023
1024 const uint32_t start = millis();
1025
1026 while ((timeout == 0 && this->available()) || millis() - start <= timeout) {
1027 if (!this->available()) {
1028 App.feed_wdt();
1029 delay(1);
1030 continue;
1031 }
1032
1033 this->read_byte(&c);
1034 if (c == 0xFF) {
1035 nr_of_ff_bytes++;
1036 } else {
1037 nr_of_ff_bytes = 0;
1038 ff_flag = false;
1039 }
1040
1041 if (nr_of_ff_bytes >= 3)
1042 ff_flag = true;
1043
1044 response += (char) c;
1045 if (recv_flag) {
1046 if (response.find(0x05) != std::string::npos) {
1047 exit_flag = true;
1048 }
1049 }
1050 App.feed_wdt();
1051 delay(2);
1052
1053 if (exit_flag || ff_flag) {
1054 break;
1055 }
1056 }
1057
1058 if (ff_flag)
1059 response = response.substr(0, response.length() - 3); // Remove last 3 0xFF
1060
1061 return response.length();
1062}
1063
1074void Nextion::add_no_result_to_queue_(const std::string &variable_name) {
1075#ifdef USE_NEXTION_MAX_QUEUE_SIZE
1076 if (this->max_queue_size_ > 0 && this->nextion_queue_.size() >= this->max_queue_size_) {
1077 ESP_LOGW(TAG, "Queue full (%zu), drop: %s", this->nextion_queue_.size(), variable_name.c_str());
1078 return;
1079 }
1080#endif
1081
1082 RAMAllocator<nextion::NextionQueue> allocator;
1083 nextion::NextionQueue *nextion_queue = allocator.allocate(1);
1084 if (nextion_queue == nullptr) {
1085 ESP_LOGW(TAG, "Queue alloc failed");
1086 return;
1087 }
1088 new (nextion_queue) nextion::NextionQueue();
1089
1090 // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
1091 nextion_queue->component = new nextion::NextionComponentBase;
1092 nextion_queue->component->set_variable_name(variable_name);
1093
1094 nextion_queue->queue_time = App.get_loop_component_start_time();
1095
1096 this->nextion_queue_.push_back(nextion_queue);
1097
1098 ESP_LOGN(TAG, "Queue NORESULT: %s", nextion_queue->component->get_variable_name().c_str());
1099}
1100
1115void Nextion::add_no_result_to_queue_with_command_(const std::string &variable_name, const std::string &command) {
1116 if ((!this->is_setup() && !this->connection_state_.ignore_is_setup_) || command.empty())
1117 return;
1118
1119 if (this->send_command_(command)) {
1120 this->add_no_result_to_queue_(variable_name);
1121#ifdef USE_NEXTION_COMMAND_SPACING
1122 } else {
1123 // Command blocked by spacing, add to queue WITH the command for retry
1124 this->add_no_result_to_queue_with_pending_command_(variable_name, command);
1125#endif // USE_NEXTION_COMMAND_SPACING
1126 }
1127}
1128
1129#ifdef USE_NEXTION_COMMAND_SPACING
1130void Nextion::add_no_result_to_queue_with_pending_command_(const std::string &variable_name,
1131 const std::string &command) {
1132#ifdef USE_NEXTION_MAX_QUEUE_SIZE
1133 if (this->max_queue_size_ > 0 && this->nextion_queue_.size() >= this->max_queue_size_) {
1134 ESP_LOGW(TAG, "Queue full (%zu), drop: %s", this->nextion_queue_.size(), variable_name.c_str());
1135 return;
1136 }
1137#endif
1138
1139 RAMAllocator<nextion::NextionQueue> allocator;
1140 nextion::NextionQueue *nextion_queue = allocator.allocate(1);
1141 if (nextion_queue == nullptr) {
1142 ESP_LOGW(TAG, "Queue alloc failed");
1143 return;
1144 }
1145 new (nextion_queue) nextion::NextionQueue();
1146
1147 nextion_queue->component = new nextion::NextionComponentBase;
1148 nextion_queue->component->set_variable_name(variable_name);
1149 nextion_queue->queue_time = App.get_loop_component_start_time();
1150 nextion_queue->pending_command = command; // Store command for retry
1151
1152 this->nextion_queue_.push_back(nextion_queue);
1153 ESP_LOGVV(TAG, "Queue with pending command: %s", variable_name.c_str());
1154}
1155#endif // USE_NEXTION_COMMAND_SPACING
1156
1157bool Nextion::add_no_result_to_queue_with_ignore_sleep_printf_(const std::string &variable_name, const char *format,
1158 ...) {
1159 if ((!this->is_setup() && !this->connection_state_.ignore_is_setup_))
1160 return false;
1161
1162 char buffer[256];
1163 va_list arg;
1164 va_start(arg, format);
1165 int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
1166 va_end(arg);
1167 if (ret <= 0) {
1168 ESP_LOGW(TAG, "Bad cmd format: '%s'", format);
1169 return false;
1170 }
1171
1172 this->add_no_result_to_queue_with_command_(variable_name, buffer);
1173 return true;
1174}
1175
1183bool Nextion::add_no_result_to_queue_with_printf_(const std::string &variable_name, const char *format, ...) {
1184 if ((!this->is_setup() && !this->connection_state_.ignore_is_setup_) || this->is_sleeping())
1185 return false;
1186
1187 char buffer[256];
1188 va_list arg;
1189 va_start(arg, format);
1190 int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
1191 va_end(arg);
1192 if (ret <= 0) {
1193 ESP_LOGW(TAG, "Bad cmd format: '%s'", format);
1194 return false;
1195 }
1196
1197 this->add_no_result_to_queue_with_command_(variable_name, buffer);
1198 return true;
1199}
1200
1210void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, int32_t state_value) {
1211 this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(),
1212 state_value);
1213}
1214
1215void Nextion::add_no_result_to_queue_with_set(const std::string &variable_name,
1216 const std::string &variable_name_to_send, int32_t state_value) {
1217 this->add_no_result_to_queue_with_set_internal_(variable_name, variable_name_to_send, state_value);
1218}
1219
1220void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &variable_name,
1221 const std::string &variable_name_to_send, int32_t state_value,
1222 bool is_sleep_safe) {
1223 if ((!this->is_setup() && !this->connection_state_.ignore_is_setup_) || (!is_sleep_safe && this->is_sleeping()))
1224 return;
1225
1226 this->add_no_result_to_queue_with_ignore_sleep_printf_(variable_name, "%s=%" PRId32, variable_name_to_send.c_str(),
1227 state_value);
1228}
1229
1238void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) {
1239 this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(),
1240 state_value);
1241}
1242void Nextion::add_no_result_to_queue_with_set(const std::string &variable_name,
1243 const std::string &variable_name_to_send,
1244 const std::string &state_value) {
1245 this->add_no_result_to_queue_with_set_internal_(variable_name, variable_name_to_send, state_value);
1246}
1247
1248void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &variable_name,
1249 const std::string &variable_name_to_send,
1250 const std::string &state_value, bool is_sleep_safe) {
1251 if ((!this->is_setup() && !this->connection_state_.ignore_is_setup_) || (!is_sleep_safe && this->is_sleeping()))
1252 return;
1253
1254 this->add_no_result_to_queue_with_printf_(variable_name, "%s=\"%s\"", variable_name_to_send.c_str(),
1255 state_value.c_str());
1256}
1257
1267void Nextion::add_to_get_queue(NextionComponentBase *component) {
1268 if ((!this->is_setup() && !this->connection_state_.ignore_is_setup_))
1269 return;
1270
1271#ifdef USE_NEXTION_MAX_QUEUE_SIZE
1272 if (this->max_queue_size_ > 0 && this->nextion_queue_.size() >= this->max_queue_size_) {
1273 ESP_LOGW(TAG, "Queue full (%zu), drop GET: %s", this->nextion_queue_.size(),
1274 component->get_variable_name().c_str());
1275 return;
1276 }
1277#endif
1278
1279 RAMAllocator<nextion::NextionQueue> allocator;
1280 nextion::NextionQueue *nextion_queue = allocator.allocate(1);
1281 if (nextion_queue == nullptr) {
1282 ESP_LOGW(TAG, "Queue alloc failed");
1283 return;
1284 }
1285 new (nextion_queue) nextion::NextionQueue();
1286
1287 nextion_queue->component = component;
1288 nextion_queue->queue_time = App.get_loop_component_start_time();
1289
1290 ESP_LOGN(TAG, "Queue %s: %s", component->get_queue_type_string(), component->get_variable_name().c_str());
1291
1292 std::string command = "get " + component->get_variable_name_to_send();
1293
1294#ifdef USE_NEXTION_COMMAND_SPACING
1295 // Always enqueue first so the response handler is present when the command
1296 // is eventually sent. Store the command for retry if spacing blocked it;
1297 // process_pending_in_queue_() will transmit it when the pacer allows.
1298 nextion_queue->pending_command = command;
1299 this->nextion_queue_.push_back(nextion_queue);
1300 if (this->send_command_(command)) {
1301 nextion_queue->pending_command.clear();
1302 }
1303#else // USE_NEXTION_COMMAND_SPACING
1304 if (this->send_command_(command)) {
1305 this->nextion_queue_.push_back(nextion_queue);
1306 } else {
1307 delete nextion_queue; // NOLINT(cppcoreguidelines-owning-memory)
1308 }
1309#endif // USE_NEXTION_COMMAND_SPACING
1310}
1311
1312#ifdef USE_NEXTION_WAVEFORM
1318void Nextion::add_addt_command_to_queue(NextionComponentBase *component) {
1319 if ((!this->is_setup() && !this->connection_state_.ignore_is_setup_) || this->is_sleeping())
1320 return;
1321
1322 RAMAllocator<nextion::NextionQueue> allocator;
1323 nextion::NextionQueue *nextion_queue = allocator.allocate(1);
1324 if (nextion_queue == nullptr) {
1325 ESP_LOGW(TAG, "Queue alloc failed");
1326 return;
1327 }
1328 new (nextion_queue) nextion::NextionQueue();
1329
1330 nextion_queue->component = component;
1331 nextion_queue->queue_time = App.get_loop_component_start_time();
1332
1333 if (!this->waveform_queue_.push(nextion_queue)) {
1334 ESP_LOGW(TAG, "Waveform queue full, drop");
1335 delete nextion_queue; // NOLINT(cppcoreguidelines-owning-memory)
1336 return;
1337 }
1338 if (this->waveform_queue_.size() == 1)
1340}
1341
1343 if (this->waveform_queue_.empty())
1344 return;
1345
1346 auto *nb = this->waveform_queue_.front();
1347 auto *component = nb->component;
1348 size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size() : 255;
1349
1350 char command[24]; // "addt " + uint8 + "," + uint8 + "," + uint8 + null = max 17 chars
1351 buf_append_printf(command, sizeof(command), 0, "addt %u,%u,%zu", component->get_component_id(),
1352 component->get_wave_channel_id(), buffer_to_send);
1353 // If spacing or setup state blocks the send, leave the entry at the front
1354 // of waveform_queue_ for retry on the next loop iteration via
1355 // check_pending_waveform_(). Only pop on a successful send.
1356 this->send_command_(command);
1357}
1358#endif // USE_NEXTION_WAVEFORM
1359
1360void Nextion::set_writer(const nextion_writer_t &writer) { this->writer_ = writer; }
1361
1362bool Nextion::is_updating() { return this->connection_state_.is_updating_; }
1363
1364} // namespace esphome::nextion
void feed_wdt()
Feed the task watchdog.
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.
uint8_t get_spacing() const
Get current command spacing.
Definition nextion.h:55
bool can_send(uint32_t now) const
Check if enough time has passed to send the next command.
Definition nextion.h:63
void mark_sent(uint32_t now)
Record the transmit timestamp for the most recently sent command.
Definition nextion.h:71
std::vector< NextionComponentBase * > touch_
Definition nextion.h:1586
std::vector< NextionComponentBase * > switchtype_
Definition nextion.h:1587
std::vector< NextionComponentBase * > binarysensortype_
Definition nextion.h:1590
StaticRingBuffer< NextionQueue *, 4 > waveform_queue_
Fixed-size ring buffer for waveform queue.
Definition nextion.h:1464
uint16_t max_q_age_ms_
Maximum age for queue items in ms.
Definition nextion.h:1631
bool send_command_(const std::string &command)
Manually send a raw command to the display and don't wait for an acknowledgement packet.
std::vector< NextionComponentBase * > textsensortype_
Definition nextion.h:1589
CallbackManager< void(uint8_t)> page_callback_
Definition nextion.h:1594
struct esphome::nextion::Nextion::@144 connection_state_
Status flags for Nextion display state management.
CallbackManager< void(StringRef, int32_t)> custom_sensor_callback_
Definition nextion.h:1601
void set_wake_up_page(uint8_t wake_up_page=255)
Sets which page Nextion loads when exiting sleep mode.
std::string device_model_
Definition nextion.h:1614
void all_components_send_state_(bool force_update=false)
std::string firmware_version_
Definition nextion.h:1615
void set_nextion_sensor_state(int queue_type, const std::string &name, float state)
Set the nextion sensor state object.
void add_addt_command_to_queue(NextionComponentBase *component) override
uint16_t max_commands_per_loop_
Definition nextion.h:1438
nextion_writer_t writer_
Definition nextion.h:1610
uint16_t startup_override_ms_
Timeout before forcing setup complete.
Definition nextion.h:1630
void add_to_get_queue(NextionComponentBase *component) override
bool send_command_printf(const char *format,...) __attribute__((format(printf
Manually send a raw formatted command to the display.
std::list< NextionQueue * > nextion_queue_
Definition nextion.h:1460
void set_auto_wake_on_touch(bool auto_wake_on_touch)
Sets if Nextion should auto-wake from sleep when touch press occurs.
bool remove_from_q_(bool report_empty=true)
std::vector< NextionComponentBase * > sensortype_
Definition nextion.h:1588
bool add_no_result_to_queue_with_printf_(const std::string &variable_name, const char *format,...) __attribute__((format(printf
void set_touch_sleep_timeout(uint16_t touch_sleep_timeout=0)
Set the touch sleep timeout of the display using the thsp command.
CallbackManager< void()> setup_callback_
Definition nextion.h:1591
std::string command_data_
Definition nextion.h:1627
optional< float > brightness_
Definition nextion.h:1611
CallbackManager< void()> wake_callback_
Definition nextion.h:1593
bool is_updating() override
Check if the TFT update process is currently running.
uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag)
void goto_page(const char *page)
Show the page with a given name.
bool void add_no_result_to_queue_with_set_internal_(const std::string &variable_name, const std::string &variable_name_to_send, int32_t state_value, bool is_sleep_safe=false)
bool void add_no_result_to_queue_with_command_(const std::string &variable_name, const std::string &command)
CallbackManager< void(StringRef, bool)> custom_binary_sensor_callback_
Definition nextion.h:1598
bool send_command(const char *command)
Manually send a raw command to the display.
std::string serial_number_
Definition nextion.h:1616
CallbackManager< void()> sleep_callback_
Definition nextion.h:1592
void reset_(bool reset_nextion=true)
void process_pending_in_queue_()
Process any commands in the queue that are pending due to command spacing.
void add_no_result_to_queue_(const std::string &variable_name)
CallbackManager< void(uint8_t, uint8_t, bool)> touch_callback_
Definition nextion.h:1595
void dump_config() override
NextionCommandPacer command_pacer_
Definition nextion.h:1445
void set_nextion_text_state(const std::string &name, const std::string &state)
bool add_no_result_to_queue_with_ignore_sleep_printf_(const std::string &variable_name, const char *format,...) __attribute__((format(printf
void update() override
CallbackManager< void()> buffer_overflow_callback_
Definition nextion.h:1596
void set_writer(const nextion_writer_t &writer)
CallbackManager< void(StringRef, StringRef)> custom_text_sensor_callback_
Definition nextion.h:1607
void add_no_result_to_queue_with_pending_command_(const std::string &variable_name, const std::string &command)
Add a command to the Nextion queue with a pending command for retry.
void add_no_result_to_queue_with_set(NextionComponentBase *component, int32_t state_value) override
CallbackManager< void(StringRef, bool)> custom_switch_callback_
Definition nextion.h:1604
void update_components_by_prefix(const std::string &prefix)
uint32_t tft_upload_watchdog_timeout_
WDT timeout in ms (0 = no adjustment)
Definition nextion.h:1553
void set_backlight_brightness(float brightness)
Set the brightness of the backlight.
optional< std::array< uint8_t, N > > read_array()
Definition uart.h:38
void write_str(const char *str)
Definition uart.h:32
bool read_byte(uint8_t *data)
Definition uart.h:34
void write_array(const uint8_t *data, size_t len)
Definition uart.h:26
const Component * component
Definition component.cpp:34
bool state
Definition fan.h:2
display::DisplayWriter< Nextion > nextion_writer_t
Definition nextion.h:34
const char *const TAG
Definition spi.cpp:7
const char int const __FlashStringHelper * format
Definition log.h:74
va_end(args)
constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4)
Encode a 32-bit value given four bytes in most to least significant byte order.
Definition helpers.h:889
size_t size_t const char va_start(args, fmt)
void HOT delay(uint32_t ms)
Definition core.cpp:28
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:26
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t
uint8_t end[39]
Definition sun_gtil2.cpp:17
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6