ESPHome 2026.6.0-dev
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 // Parse comok response fields directly
121 // Format: comok <touch>,<reserved>,<model>,<fw>,<mcu_code>,<serial>,<flash>
122 size_t field_count = 0;
123 size_t start = 0;
124 size_t end = 0;
125 auto copy_field = [&](char *dst, size_t cap) {
126 size_t len = (end == std::string::npos ? response.size() : end) - start;
127 size_t n = len < cap ? len : cap;
128 std::memcpy(dst, response.data() + start, n);
129 dst[n] = '\0';
130 };
131 while ((start = response.find_first_not_of(',', end)) != std::string::npos) {
132 end = response.find(',', start);
133 switch (field_count) {
134 case 2:
135 copy_field(this->device_model_, this->NEXTION_MODEL_MAX);
136 break;
137 case 3:
138 copy_field(this->firmware_version_, this->NEXTION_FW_MAX);
139 break;
140 case 5:
141 copy_field(this->serial_number_, this->NEXTION_SERIAL_MAX);
142 break;
143 case 6:
144 this->flash_size_ = static_cast<uint32_t>(std::strtoul(response.data() + start, nullptr, 10));
145 break;
146 default:
147 break;
148 }
149 ++field_count;
150 }
151
152 this->is_detected_ = (field_count == 7);
153 if (this->is_detected_) {
154 ESP_LOGN(TAG, "Connect info: %zu fields", field_count);
155 } else {
156 ESP_LOGE(TAG, "Bad connect value: '%s'", response.c_str());
157 }
158
159 this->connection_state_.ignore_is_setup_ = false;
160 this->dump_config();
161 return true;
162#endif // USE_NEXTION_CONFIG_SKIP_CONNECTION_HANDSHAKE
163}
164
165void Nextion::reset_(bool reset_nextion) {
166 uint8_t d;
167
168 while (this->available()) { // Clear receive buffer
169 this->read_byte(&d);
170 }
171 for (auto *entry : this->nextion_queue_) {
172 if (entry->component != nullptr && entry->component->get_queue_type() == NextionQueueType::NO_RESULT) {
173 delete entry->component; // NOLINT(cppcoreguidelines-owning-memory)
174 }
175 delete entry; // NOLINT(cppcoreguidelines-owning-memory)
176 }
177 this->nextion_queue_.clear();
178#ifdef USE_NEXTION_WAVEFORM
179 for (auto *entry : this->waveform_queue_) {
180 delete entry; // NOLINT(cppcoreguidelines-owning-memory)
181 }
182 this->waveform_queue_.clear();
183#endif // USE_NEXTION_WAVEFORM
184}
185
187 ESP_LOGCONFIG(TAG, "Nextion:");
188
189#ifdef USE_NEXTION_CONFIG_SKIP_CONNECTION_HANDSHAKE
190 ESP_LOGCONFIG(TAG, " Skip handshake: YES");
191#else // USE_NEXTION_CONFIG_SKIP_CONNECTION_HANDSHAKE
192 if (this->is_setup()) {
193 ESP_LOGCONFIG(TAG,
194 " Device Model: %s\n"
195 " FW Version: %s\n"
196 " Serial Number: %s\n"
197 " Flash Size: %" PRIu32 " bytes",
198 this->device_model_, this->firmware_version_, this->serial_number_, this->flash_size_);
199 } else {
200 ESP_LOGCONFIG(TAG, " Device info: not yet detected");
201 }
202 ESP_LOGCONFIG(TAG,
203#ifdef USE_NEXTION_CONFIG_EXIT_REPARSE_ON_START
204 " Exit reparse: YES\n"
205#endif // USE_NEXTION_CONFIG_EXIT_REPARSE_ON_START
206 " Max queue age: %u ms\n"
207 " Startup override: %u ms\n"
208 " Wake On Touch: %s\n"
209 " Touch Timeout: %" PRIu16,
210 this->max_q_age_ms_, this->startup_override_ms_, YESNO(this->connection_state_.auto_wake_on_touch_),
211 this->touch_sleep_timeout_);
212#endif // USE_NEXTION_CONFIG_SKIP_CONNECTION_HANDSHAKE
213
214#ifdef USE_NEXTION_MAX_COMMANDS_PER_LOOP
215 ESP_LOGCONFIG(TAG, " Max commands per loop: %u", this->max_commands_per_loop_);
216#endif // USE_NEXTION_MAX_COMMANDS_PER_LOOP
217
218 if (this->wake_up_page_ != 255) {
219 ESP_LOGCONFIG(TAG, " Wake Up Page: %u", this->wake_up_page_);
220 }
221
222#ifdef USE_NEXTION_CONF_START_UP_PAGE
223 if (this->start_up_page_ != 255) {
224 ESP_LOGCONFIG(TAG, " Start Up Page: %u", this->start_up_page_);
225 }
226#endif // USE_NEXTION_CONF_START_UP_PAGE
227
228#ifdef USE_NEXTION_COMMAND_SPACING
229 ESP_LOGCONFIG(TAG, " Cmd spacing: %u ms", this->command_pacer_.get_spacing());
230#endif // USE_NEXTION_COMMAND_SPACING
231
232#ifdef USE_NEXTION_MAX_QUEUE_SIZE
233 ESP_LOGCONFIG(TAG, " Max queue size: %zu", this->max_queue_size_);
234#endif
235#ifdef USE_NEXTION_TFT_UPLOAD
236 ESP_LOGCONFIG(TAG,
237 " TFT URL: %s\n"
238 " TFT upload HTTP timeout: %" PRIu16 "ms\n"
239 " TFT upload HTTP retries: %u",
240 this->tft_url_.c_str(), this->tft_upload_http_timeout_, this->tft_upload_http_retries_);
241#ifdef USE_ESP32
242 if (this->tft_upload_watchdog_timeout_ > 0) {
243 ESP_LOGCONFIG(TAG, " TFT upload WDT timeout: %" PRIu32 "ms", this->tft_upload_watchdog_timeout_);
244 }
245#endif // USE_ESP32
246#endif // USE_NEXTION_TFT_UPLOAD
247}
248
249void Nextion::update() {
250 if (!this->is_setup()) {
251 return;
252 }
253 if (this->writer_.has_value()) {
254 (*this->writer_)(*this);
255 }
256}
257
259 if ((!this->is_setup() && !this->connection_state_.ignore_is_setup_) || this->is_sleeping())
260 return;
261
262 for (auto *binarysensortype : this->binarysensortype_) {
263 binarysensortype->update_component();
264 }
265 for (auto *sensortype : this->sensortype_) {
266 sensortype->update_component();
267 }
268 for (auto *switchtype : this->switchtype_) {
269 switchtype->update_component();
270 }
271 for (auto *textsensortype : this->textsensortype_) {
272 textsensortype->update_component();
273 }
274}
275
276bool Nextion::send_command(const char *command) {
277 if ((!this->is_setup() && !this->connection_state_.ignore_is_setup_) || this->is_sleeping())
278 return false;
279
280 this->add_no_result_to_queue_with_command_("command", command);
281 return true;
282}
283
284bool Nextion::send_command_printf(const char *format, ...) {
285 if ((!this->is_setup() && !this->connection_state_.ignore_is_setup_) || this->is_sleeping())
286 return false;
287
288 char buffer[256];
289 va_list arg;
290 va_start(arg, format);
291 int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
292 va_end(arg);
293 if (ret <= 0) {
294 ESP_LOGW(TAG, "Bad cmd format: '%s'", format);
295 return false;
296 }
297
298 this->add_no_result_to_queue_with_command_("command_printf", buffer);
299 return true;
300}
301
302#ifdef NEXTION_PROTOCOL_LOG
304 ESP_LOGN(TAG, "print_queue_members_ (top 10) size %zu", this->nextion_queue_.size());
305 ESP_LOGN(TAG, "*******************************************");
306 int count = 0;
307 for (auto *i : this->nextion_queue_) {
308 if (count++ == 10)
309 break;
310
311 if (i == nullptr) {
312 ESP_LOGN(TAG, "Queue null");
313 } else {
314 ESP_LOGN(TAG, "Queue type: %d:%s, name: %s", i->component->get_queue_type(),
315 i->component->get_queue_type_string(), i->component->get_variable_name().c_str());
316 }
317 }
318 ESP_LOGN(TAG, "*******************************************");
319}
320#endif
321
322void Nextion::loop() {
323 if (!this->check_connect_() || this->connection_state_.is_updating_)
324 return;
325
326 if (this->connection_state_.nextion_reports_is_setup_ && !this->connection_state_.sent_setup_commands_) {
327 this->connection_state_.ignore_is_setup_ = true;
328 this->connection_state_.sent_setup_commands_ = true;
329 this->send_command_("bkcmd=3"); // Always, returns 0x00 to 0x23 result of serial command.
330
331 if (this->brightness_.has_value()) {
332 this->set_backlight_brightness(this->brightness_.value());
333 }
334
335#ifdef USE_NEXTION_CONF_START_UP_PAGE
336 // Check if a startup page has been set and send the command
337 if (this->start_up_page_ != 255) {
338 this->goto_page(this->start_up_page_);
339 }
340#endif // USE_NEXTION_CONF_START_UP_PAGE
341
342 if (this->wake_up_page_ != 255) {
343 this->set_wake_up_page(this->wake_up_page_);
344 }
345
346 if (this->touch_sleep_timeout_ != 0) {
348 }
349
350 this->set_auto_wake_on_touch(this->connection_state_.auto_wake_on_touch_);
351
352 this->connection_state_.ignore_is_setup_ = false;
353 }
354
355 this->process_serial_(); // Receive serial data
356 this->process_nextion_commands_(); // Process nextion return commands
357
358 if (!this->connection_state_.nextion_reports_is_setup_) {
359 if (this->started_ms_ == 0)
361
362 if (this->startup_override_ms_ > 0 &&
363 App.get_loop_component_start_time() - this->started_ms_ > this->startup_override_ms_) {
364 ESP_LOGV(TAG, "Manual ready set");
365 this->connection_state_.nextion_reports_is_setup_ = true;
366 }
367 }
368
369#ifdef USE_NEXTION_COMMAND_SPACING
371#ifdef USE_NEXTION_WAVEFORM
372 if (!this->waveform_queue_.empty()) {
374 }
375#endif // USE_NEXTION_WAVEFORM
376#endif // USE_NEXTION_COMMAND_SPACING
377}
378
379#ifdef USE_NEXTION_COMMAND_SPACING
381#ifdef USE_NEXTION_MAX_COMMANDS_PER_LOOP
382 size_t commands_sent = 0;
383#endif // USE_NEXTION_MAX_COMMANDS_PER_LOOP
384
385 for (auto *item : this->nextion_queue_) {
386 if (item == nullptr || item->pending_command.empty()) {
387 continue; // Already sent, waiting for ACK — skip, don't stop
388 }
389
390#ifdef USE_NEXTION_MAX_COMMANDS_PER_LOOP
391 if (++commands_sent > this->max_commands_per_loop_) {
392 ESP_LOGV(TAG, "Pending cmds: loop limit reached, deferring");
393 break;
394 }
395#endif // USE_NEXTION_MAX_COMMANDS_PER_LOOP
396
398 if (!this->command_pacer_.can_send(now)) {
399 break; // Spacing not elapsed, stop for this loop iteration
400 }
401
402 if (!this->send_command_(item->pending_command)) {
403 break; // Unexpected send failure, stop
404 }
405 item->pending_command.clear();
406 ESP_LOGVV(TAG, "Pending cmd sent: %s", item->component->get_variable_name().c_str());
407 }
408}
409#endif // USE_NEXTION_COMMAND_SPACING
410
411bool Nextion::remove_from_q_(bool report_empty) {
412 if (this->nextion_queue_.empty()) {
413 if (report_empty) {
414 ESP_LOGE(TAG, "Queue empty");
415 }
416 return false;
417 }
418
419 NextionQueue *nb = this->nextion_queue_.front();
420 if (!nb || !nb->component) {
421 ESP_LOGE(TAG, "Invalid queue");
422 this->nextion_queue_.pop_front();
423 return false;
424 }
425 NextionComponentBase *component = nb->component;
426
427 ESP_LOGN(TAG, "Removed: %s", component->get_variable_name().c_str());
428
429 if (component->get_queue_type() == NextionQueueType::NO_RESULT) {
430 if (component->get_variable_name() == "sleep_wake") {
431 this->is_sleeping_ = false;
432 }
433 delete component; // NOLINT(cppcoreguidelines-owning-memory)
434 }
435 delete nb; // NOLINT(cppcoreguidelines-owning-memory)
436 this->nextion_queue_.pop_front();
437 return true;
438}
439
441 // Read all available bytes in batches to reduce UART call overhead.
442 size_t avail = this->available();
443 uint8_t buf[64];
444 while (avail > 0) {
445 size_t to_read = std::min(avail, sizeof(buf));
446 if (!this->read_array(buf, to_read)) {
447 break;
448 }
449 avail -= to_read;
450
451 this->command_data_.append(reinterpret_cast<const char *>(buf), to_read);
452 }
453}
454// nextion.tech/instruction-set/
456 if (this->command_data_.empty()) {
457 return;
458 }
459
460#ifdef USE_NEXTION_MAX_COMMANDS_PER_LOOP
461 size_t commands_processed = 0;
462#endif // USE_NEXTION_MAX_COMMANDS_PER_LOOP
463
464 size_t to_process_length = 0;
465 std::string to_process;
466
467 ESP_LOGN(TAG, "command_data_ %s len %d", this->command_data_.c_str(), this->command_data_.length());
468#ifdef NEXTION_PROTOCOL_LOG
469 this->print_queue_members_();
470#endif
471 while ((to_process_length = this->command_data_.find(reinterpret_cast<const char *>(COMMAND_DELIMITER), 0,
472 DELIMITER_SIZE)) != std::string::npos) {
473#ifdef USE_NEXTION_MAX_COMMANDS_PER_LOOP
474 if (++commands_processed > this->max_commands_per_loop_) {
475 ESP_LOGV(TAG, "Command limit reached, deferring");
476 break;
477 }
478#endif // USE_NEXTION_MAX_COMMANDS_PER_LOOP
479 ESP_LOGN(TAG, "queue size: %zu", this->nextion_queue_.size());
480 while (to_process_length + DELIMITER_SIZE < this->command_data_.length() &&
481 static_cast<uint8_t>(this->command_data_[to_process_length + DELIMITER_SIZE]) == 0xFF) {
482 ++to_process_length;
483 ESP_LOGN(TAG, "Add 0xFF");
484 }
485
486 const uint8_t nextion_event = this->command_data_[0];
487
488 to_process_length -= 1;
489 to_process = this->command_data_.substr(1, to_process_length);
490
491 switch (nextion_event) {
492 case 0x00: // instruction sent by user has failed
493 ESP_LOGW(TAG, "Invalid instruction");
494 this->remove_from_q_();
495
496 break;
497 case 0x01: // instruction sent by user was successful
498
499 ESP_LOGVV(TAG, "Cmd OK");
500 ESP_LOGN(TAG, "this->nextion_queue_.empty() %s", YESNO(this->nextion_queue_.empty()));
501
502 this->remove_from_q_();
503 if (!this->is_setup_) {
504 if (this->nextion_queue_.empty()) {
505 this->is_setup_ = true;
506 this->setup_callback_.call();
507 }
508 }
509 break;
510 case 0x02: // invalid Component ID or name was used
511 ESP_LOGW(TAG, "Invalid component ID/name");
512 this->remove_from_q_();
513 break;
514 case 0x03: // invalid Page ID or name was used
515 ESP_LOGW(TAG, "Invalid page ID");
516 this->remove_from_q_();
517 break;
518 case 0x04: // invalid Picture ID was used
519 ESP_LOGW(TAG, "Invalid picture ID");
520 this->remove_from_q_();
521 break;
522 case 0x05: // invalid Font ID was used
523 ESP_LOGW(TAG, "Invalid font ID");
524 this->remove_from_q_();
525 break;
526 case 0x06: // File operation fails
527 ESP_LOGW(TAG, "File operation failed");
528 break;
529 case 0x09: // Instructions with CRC validation fails their CRC check
530 ESP_LOGW(TAG, "CRC validation failed");
531 break;
532 case 0x11: // invalid Baud rate was used
533 ESP_LOGW(TAG, "Invalid baud rate");
534 break;
535 case 0x12: // invalid Waveform ID or Channel # was used
536#ifdef USE_NEXTION_WAVEFORM
537 if (this->waveform_queue_.empty()) {
538 ESP_LOGW(TAG, "Waveform ID/ch used but no sensor queued");
539 } else {
540 auto &nb = this->waveform_queue_.front();
541 NextionComponentBase *component = nb->component;
542 ESP_LOGW(TAG, "Invalid waveform ID %d/ch %d", component->get_component_id(),
543 component->get_wave_channel_id());
544 ESP_LOGN(TAG, "Remove waveform ID %d/ch %d", component->get_component_id(), component->get_wave_channel_id());
545 delete nb; // NOLINT(cppcoreguidelines-owning-memory)
546 this->waveform_queue_.pop();
547 }
548#else // USE_NEXTION_WAVEFORM
549 ESP_LOGW(TAG, "Waveform ID/ch error but waveform not enabled");
550#endif // USE_NEXTION_WAVEFORM
551 break;
552 case 0x1A: // variable name invalid
553 ESP_LOGW(TAG, "Invalid variable name");
554 this->remove_from_q_();
555 break;
556 case 0x1B: // variable operation invalid
557 ESP_LOGW(TAG, "Invalid variable operation");
558 this->remove_from_q_();
559 break;
560 case 0x1C: // failed to assign
561 ESP_LOGW(TAG, "Variable assign failed");
562 this->remove_from_q_();
563 break;
564 case 0x1D: // operate EEPROM failed
565 ESP_LOGW(TAG, "EEPROM operation failed");
566 break;
567 case 0x1E: // parameter quantity invalid
568 ESP_LOGW(TAG, "Invalid parameter count");
569 this->remove_from_q_();
570 break;
571 case 0x1F: // IO operation failed
572 ESP_LOGW(TAG, "Invalid component I/O");
573 break;
574 case 0x20: // undefined escape characters
575 ESP_LOGW(TAG, "Undefined escape chars");
576 this->remove_from_q_();
577 break;
578 case 0x23: // too long variable name
579 ESP_LOGW(TAG, "Variable name too long");
580 this->remove_from_q_();
581 break;
582 case 0x24: // Serial Buffer overflow occurs
583 // Buffer will continue to receive the current instruction, all previous instructions are lost.
584 ESP_LOGE(TAG, "Serial buffer overflow");
585 this->buffer_overflow_callback_.call();
586 break;
587 case 0x65: { // touch event return data
588 if (to_process_length != 3) {
589 ESP_LOGW(TAG, "Incorrect touch len: %zu (need 3)", to_process_length);
590 break;
591 }
592
593 uint8_t page_id = to_process[0];
594 uint8_t component_id = to_process[1];
595 uint8_t touch_event = to_process[2]; // 0 -> release, 1 -> press
596 ESP_LOGV(TAG, "Touch %s: page %u comp %u", touch_event ? "PRESS" : "RELEASE", page_id, component_id);
597 for (auto *touch : this->touch_) {
598 touch->process_touch(page_id, component_id, touch_event != 0);
599 }
600 this->touch_callback_.call(page_id, component_id, touch_event != 0);
601 break;
602 }
603 case 0x66: { // Nextion initiated new page event return data.
604 // Also is used for sendme command which we never explicitly initiate
605 if (to_process_length != 1) {
606 ESP_LOGW(TAG, "Page event: expect 1, got %zu", to_process_length);
607 break;
608 }
609
610 uint8_t page_id = to_process[0];
611 ESP_LOGV(TAG, "New page: %u", page_id);
612 this->page_callback_.call(page_id);
613 break;
614 }
615 case 0x67: { // Touch Coordinate (awake)
616 break;
617 }
618 case 0x68: { // touch coordinate data (sleep)
619
620 if (to_process_length != 5) {
621 ESP_LOGW(TAG, "Touch coordinate: expect 5, got %zu", to_process_length);
622 ESP_LOGW(TAG, "%s", to_process.c_str());
623 break;
624 }
625
626 const uint16_t x = (uint16_t(to_process[0]) << 8) | to_process[1];
627 const uint16_t y = (uint16_t(to_process[2]) << 8) | to_process[3];
628 const uint8_t touch_event = to_process[4]; // 0 -> release, 1 -> press
629 ESP_LOGV(TAG, "Touch %s at %u,%u", touch_event ? "PRESS" : "RELEASE", x, y);
630 break;
631 }
632
633 // 0x70 0x61 0x62 0x31 0x32 0x33 0xFF 0xFF 0xFF
634 // Returned when using get command for a string.
635 // Each byte is converted to char.
636 // data: ab123
637 case 0x70: // string variable data return
638 {
639 if (this->nextion_queue_.empty()) {
640 ESP_LOGW(TAG, "String return but queue is empty");
641 break;
642 }
643
644 NextionQueue *nb = this->nextion_queue_.front();
645 if (!nb || !nb->component) {
646 ESP_LOGE(TAG, "Invalid queue entry");
647 this->nextion_queue_.pop_front();
648 return;
649 }
650 NextionComponentBase *component = nb->component;
651
652 if (component->get_queue_type() != NextionQueueType::TEXT_SENSOR) {
653 ESP_LOGE(TAG, "String return but '%s' not text sensor", component->get_variable_name().c_str());
654 } else {
655 ESP_LOGN(TAG, "String resp: '%s' id: %s type: %s", to_process.c_str(), component->get_variable_name().c_str(),
656 component->get_queue_type_string());
657 component->set_state_from_string(to_process, true, false);
658 }
659
660 delete nb; // NOLINT(cppcoreguidelines-owning-memory)
661 this->nextion_queue_.pop_front();
662
663 break;
664 }
665 // 0x71 0x01 0x02 0x03 0x04 0xFF 0xFF 0xFF
666 // Returned when get command to return a number
667 // 4 byte 32-bit value in little endian order.
668 // (0x01+0x02*256+0x03*65536+0x04*16777216)
669 // data: 67305985
670 case 0x71: // numeric variable data return
671 {
672 if (this->nextion_queue_.empty()) {
673 ESP_LOGE(TAG, "Numeric return but queue empty");
674 break;
675 }
676
677 if (to_process_length < 4) {
678 ESP_LOGE(TAG, "Numeric return but insufficient data (need 4, got %zu)", to_process_length);
679 break;
680 }
681
682 int value = static_cast<int>(encode_uint32(to_process[3], to_process[2], to_process[1], to_process[0]));
683
684 NextionQueue *nb = this->nextion_queue_.front();
685 if (!nb || !nb->component) {
686 ESP_LOGE(TAG, "Invalid queue");
687 this->nextion_queue_.pop_front();
688 return;
689 }
690 NextionComponentBase *component = nb->component;
691
692 if (component->get_queue_type() != NextionQueueType::SENSOR &&
693 component->get_queue_type() != NextionQueueType::BINARY_SENSOR &&
694 component->get_queue_type() != NextionQueueType::SWITCH) {
695 ESP_LOGE(TAG, "Numeric return but '%s' invalid type %d", component->get_variable_name().c_str(),
696 component->get_queue_type());
697 } else {
698 ESP_LOGN(TAG, "Numeric: %s type %d:%s val %d", component->get_variable_name().c_str(),
699 component->get_queue_type(), component->get_queue_type_string(), value);
700 component->set_state_from_int(value, true, false);
701 }
702
703 delete nb; // NOLINT(cppcoreguidelines-owning-memory)
704 this->nextion_queue_.pop_front();
705
706 break;
707 }
708
709 case 0x86: { // device automatically enters into sleep mode
710 ESP_LOGVV(TAG, "Auto sleep");
711 this->is_sleeping_ = true;
712 this->sleep_callback_.call();
713 break;
714 }
715 case 0x87: // device automatically wakes up
716 {
717 ESP_LOGVV(TAG, "Auto wake");
718 this->is_sleeping_ = false;
719 this->wake_callback_.call();
720 this->all_components_send_state_(false);
721 break;
722 }
723 case 0x88: // system successful start up
724 {
725 ESP_LOGV(TAG, "System start: %zu", to_process_length);
726 this->connection_state_.nextion_reports_is_setup_ = true;
727 break;
728 }
729 case 0x89: { // start SD card upgrade
730 break;
731 }
732 // Data from nextion is
733 // 0x90 - Start
734 // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0
735 // 00 - NULL
736 // 00/01 - Single byte for on/off
737 // FF FF FF - End
738 case 0x90: { // Switched component
739 std::string variable_name;
740
741 // Get variable name
742 auto index = to_process.find('\0');
743 if (index == std::string::npos || (to_process_length - index - 1) < 1) {
744 ESP_LOGE(TAG, "Bad switch data (0x90)");
745 ESP_LOGN(TAG, "proc: %s %zu %zu", to_process.c_str(), to_process_length, index);
746 break;
747 }
748
749 variable_name = to_process.substr(0, index);
750 ++index;
751
752 ESP_LOGN(TAG, "Switch %s: %s", ONOFF(to_process[index] != 0), variable_name.c_str());
753
754#ifdef USE_NEXTION_TRIGGER_CUSTOM_SWITCH
755 this->custom_switch_callback_.call(StringRef(variable_name), to_process[index] != 0);
756#endif // USE_NEXTION_TRIGGER_CUSTOM_SWITCH
757
758 for (auto *switchtype : this->switchtype_) {
759 switchtype->process_bool(variable_name, to_process[index] != 0);
760 }
761 break;
762 }
763 // Data from nextion is
764 // 0x91 - Start
765 // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0
766 // 00 - NULL
767 // variable length of 0x71 return data: prints temp1.val,0
768 // FF FF FF - End
769 case 0x91: { // Sensor component
770 std::string variable_name;
771
772 auto index = to_process.find('\0');
773 if (index == std::string::npos || (to_process_length - index - 1) != 4) {
774 ESP_LOGE(TAG, "Bad sensor data (0x91)");
775 ESP_LOGN(TAG, "proc: %s %zu %zu", to_process.c_str(), to_process_length, index);
776 break;
777 }
778
779 index = to_process.find('\0');
780 variable_name = to_process.substr(0, index);
781 // // Get variable name
782 int value = static_cast<int>(
783 encode_uint32(to_process[index + 4], to_process[index + 3], to_process[index + 2], to_process[index + 1]));
784
785 ESP_LOGN(TAG, "Sensor: %s=%d", variable_name.c_str(), value);
786
787#ifdef USE_NEXTION_TRIGGER_CUSTOM_SENSOR
788 this->custom_sensor_callback_.call(StringRef(variable_name), value);
789#endif // USE_NEXTION_TRIGGER_CUSTOM_SENSOR
790
791 for (auto *sensor : this->sensortype_) {
792 sensor->process_sensor(variable_name, value);
793 }
794 break;
795 }
796
797 // Data from nextion is
798 // 0x92 - Start
799 // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0
800 // 00 - NULL
801 // variable length of 0x70 return formatted data (bytes) that contain the text prints temp1.txt,0
802 // 00 - NULL
803 // FF FF FF - End
804 case 0x92: { // Text Sensor Component
805 std::string variable_name;
806 std::string text_value;
807
808 // Get variable name
809 auto index = to_process.find('\0');
810 if (index == std::string::npos || (to_process_length - index - 1) < 1) {
811 ESP_LOGE(TAG, "Bad text data (0x92)");
812 ESP_LOGN(TAG, "proc: %s %zu %zu", to_process.c_str(), to_process_length, index);
813 break;
814 }
815
816 variable_name = to_process.substr(0, index);
817 ++index;
818
819 // Get variable value without terminating NUL byte. Length check above ensures substr len >= 0.
820 text_value = to_process.substr(index, to_process_length - index - 1);
821
822 ESP_LOGN(TAG, "Text sensor: %s='%s'", variable_name.c_str(), text_value.c_str());
823
824 // NextionTextSensorResponseQueue *nq = new NextionTextSensorResponseQueue;
825 // nq->variable_name = variable_name;
826 // nq->state = text_value;
827 // this->textsensorq_.push_back(nq);
828
829#ifdef USE_NEXTION_TRIGGER_CUSTOM_TEXT_SENSOR
830 this->custom_text_sensor_callback_.call(StringRef(variable_name), StringRef(text_value));
831#endif // USE_NEXTION_TRIGGER_CUSTOM_TEXT_SENSOR
832
833 for (auto *textsensortype : this->textsensortype_) {
834 textsensortype->process_text(variable_name, text_value);
835 }
836 break;
837 }
838 // Data from nextion is
839 // 0x93 - Start
840 // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0
841 // 00 - NULL
842 // 00/01 - Single byte for on/off
843 // FF FF FF - End
844 case 0x93: { // Binary Sensor component
845 std::string variable_name;
846
847 // Get variable name
848 auto index = to_process.find('\0');
849 if (index == std::string::npos || (to_process_length - index - 1) < 1) {
850 ESP_LOGE(TAG, "Bad binary data (0x93)");
851 ESP_LOGN(TAG, "proc: %s %zu %zu", to_process.c_str(), to_process_length, index);
852 break;
853 }
854
855 variable_name = to_process.substr(0, index);
856 ++index;
857
858 ESP_LOGN(TAG, "Binary sensor: %s=%s", variable_name.c_str(), ONOFF(to_process[index] != 0));
859
860#ifdef USE_NEXTION_TRIGGER_CUSTOM_BINARY_SENSOR
861 this->custom_binary_sensor_callback_.call(StringRef(variable_name), to_process[index] != 0);
862#endif // USE_NEXTION_TRIGGER_CUSTOM_BINARY_SENSOR
863
864 for (auto *binarysensortype : this->binarysensortype_) {
865 binarysensortype->process_bool(&variable_name[0], to_process[index] != 0);
866 }
867 break;
868 }
869 case 0xFD: { // data transparent transmit finished
870 ESP_LOGVV(TAG, "Data transmit done");
871#ifdef USE_NEXTION_WAVEFORM
873#endif // USE_NEXTION_WAVEFORM
874 break;
875 }
876 case 0xFE: { // data transparent transmit ready
877 ESP_LOGVV(TAG, "Ready for transmit");
878#ifdef USE_NEXTION_WAVEFORM
879 if (this->waveform_queue_.empty()) {
880 ESP_LOGE(TAG, "No waveforms queued");
881 break;
882 }
883 auto &nb = this->waveform_queue_.front();
884 auto *component = nb->component;
885 size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size() : 255;
886 this->write_array(component->get_wave_buffer().data(), static_cast<int>(buffer_to_send));
887 ESP_LOGN(TAG, "Send waveform: component id %d, waveform id %d, size %zu", component->get_component_id(),
888 component->get_wave_channel_id(), buffer_to_send);
889 component->clear_wave_buffer(buffer_to_send);
890 delete nb; // NOLINT(cppcoreguidelines-owning-memory)
891 this->waveform_queue_.pop();
892#else // USE_NEXTION_WAVEFORM
893 ESP_LOGW(TAG, "Waveform transmit ready but waveform not enabled");
894#endif // USE_NEXTION_WAVEFORM
895 break;
896 }
897 default:
898 ESP_LOGW(TAG, "Unknown event: 0x%02X", nextion_event);
899 break;
900 }
901
902 this->command_data_.erase(0, to_process_length + DELIMITER_SIZE + 1);
903 }
904
906
907 if (this->max_q_age_ms_ > 0 && !this->nextion_queue_.empty() &&
908 ms - this->nextion_queue_.front()->queue_time > this->max_q_age_ms_) {
909 for (auto it = this->nextion_queue_.begin(); it != this->nextion_queue_.end();) {
910 if (ms - (*it)->queue_time > this->max_q_age_ms_) {
911 NextionComponentBase *component = (*it)->component;
912 ESP_LOGV(TAG, "Remove old queue '%s':'%s'", component->get_queue_type_string(),
913 component->get_variable_name().c_str());
914
915 if (component->get_queue_type() == NextionQueueType::NO_RESULT) {
916 if (component->get_variable_name() == "sleep_wake") {
917 this->is_sleeping_ = false;
918 }
919 delete component; // NOLINT(cppcoreguidelines-owning-memory)
920 }
921
922 delete *it; // NOLINT(cppcoreguidelines-owning-memory)
923 it = this->nextion_queue_.erase(it);
924
925 } else {
926 break;
927 }
928 }
929 }
930 ESP_LOGN(TAG, "Loop end");
931 // App.feed_wdt(); Remove before master merge
932 this->process_serial_();
933} // Nextion::process_nextion_commands_()
934
935void Nextion::set_nextion_sensor_state(int queue_type, const std::string &name, float state) {
936 this->set_nextion_sensor_state(static_cast<NextionQueueType>(queue_type), name, state);
937}
938
939void Nextion::set_nextion_sensor_state(NextionQueueType queue_type, const std::string &name, float state) {
940 ESP_LOGN(TAG, "State: %s=%lf (type %d)", name.c_str(), state, queue_type);
941
942 switch (queue_type) {
944 for (auto *sensor : this->sensortype_) {
945 if (name == sensor->get_variable_name()) {
946 sensor->set_state(state, true, true);
947 break;
948 }
949 }
950 break;
951 }
953 for (auto *sensor : this->binarysensortype_) {
954 if (name == sensor->get_variable_name()) {
955 sensor->set_state(state != 0, true, true);
956 break;
957 }
958 }
959 break;
960 }
962 for (auto *sensor : this->switchtype_) {
963 if (name == sensor->get_variable_name()) {
964 sensor->set_state(state != 0, true, true);
965 break;
966 }
967 }
968 break;
969 }
970 default: {
971 ESP_LOGW(TAG, "set_sensor_state: bad type %d", queue_type);
972 }
973 }
974}
975
976void Nextion::set_nextion_text_state(const std::string &name, const std::string &state) {
977 ESP_LOGV(TAG, "State: %s='%s'", name.c_str(), state.c_str());
978
979 for (auto *sensor : this->textsensortype_) {
980 if (name == sensor->get_variable_name()) {
981 sensor->set_state(state, true, true);
982 break;
983 }
984 }
985}
986
987void Nextion::all_components_send_state_(bool force_update) {
988 ESP_LOGV(TAG, "Send states");
989 for (auto *binarysensortype : this->binarysensortype_) {
990 if (force_update || binarysensortype->get_needs_to_send_update())
991 binarysensortype->send_state_to_nextion();
992 }
993 for (auto *sensortype : this->sensortype_) {
994#ifdef USE_NEXTION_WAVEFORM
995 if ((force_update || sensortype->get_needs_to_send_update()) && sensortype->get_wave_channel_id() == UINT8_MAX) {
996#else // USE_NEXTION_WAVEFORM
997 if (force_update || sensortype->get_needs_to_send_update()) {
998#endif // USE_NEXTION_WAVEFORM
999 sensortype->send_state_to_nextion();
1000 }
1001 }
1002 for (auto *switchtype : this->switchtype_) {
1003 if (force_update || switchtype->get_needs_to_send_update())
1004 switchtype->send_state_to_nextion();
1005 }
1006 for (auto *textsensortype : this->textsensortype_) {
1007 if (force_update || textsensortype->get_needs_to_send_update())
1008 textsensortype->send_state_to_nextion();
1009 }
1010}
1011
1012void Nextion::update_components_by_prefix(const std::string &prefix) {
1013 for (auto *binarysensortype : this->binarysensortype_) {
1014 if (binarysensortype->get_variable_name().find(prefix, 0) != std::string::npos)
1015 binarysensortype->update_component_settings(true);
1016 }
1017 for (auto *sensortype : this->sensortype_) {
1018 if (sensortype->get_variable_name().find(prefix, 0) != std::string::npos)
1019 sensortype->update_component_settings(true);
1020 }
1021 for (auto *switchtype : this->switchtype_) {
1022 if (switchtype->get_variable_name().find(prefix, 0) != std::string::npos)
1023 switchtype->update_component_settings(true);
1024 }
1025 for (auto *textsensortype : this->textsensortype_) {
1026 if (textsensortype->get_variable_name().find(prefix, 0) != std::string::npos)
1027 textsensortype->update_component_settings(true);
1028 }
1029}
1030
1031uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag) {
1032 uint8_t c = 0;
1033 uint8_t nr_of_ff_bytes = 0;
1034 bool exit_flag = false;
1035 bool ff_flag = false;
1036
1037 const uint32_t start = millis();
1038
1039 while ((timeout == 0 && this->available()) || millis() - start <= timeout) {
1040 if (!this->available()) {
1041 App.feed_wdt();
1042 delay(1);
1043 continue;
1044 }
1045
1046 this->read_byte(&c);
1047 if (c == 0xFF) {
1048 nr_of_ff_bytes++;
1049 } else {
1050 nr_of_ff_bytes = 0;
1051 ff_flag = false;
1052 }
1053
1054 if (nr_of_ff_bytes >= 3)
1055 ff_flag = true;
1056
1057 response += (char) c;
1058 if (recv_flag) {
1059 if (response.find(0x05) != std::string::npos) {
1060 exit_flag = true;
1061 }
1062 }
1063 App.feed_wdt();
1064 delay(2);
1065
1066 if (exit_flag || ff_flag) {
1067 break;
1068 }
1069 }
1070
1071 if (ff_flag)
1072 response = response.substr(0, response.length() - 3); // Remove last 3 0xFF
1073
1074 return response.length();
1075}
1076
1087void Nextion::add_no_result_to_queue_(const std::string &variable_name) {
1088#ifdef USE_NEXTION_MAX_QUEUE_SIZE
1089 if (this->max_queue_size_ > 0 && this->nextion_queue_.size() >= this->max_queue_size_) {
1090 ESP_LOGW(TAG, "Queue full (%zu), drop: %s", this->nextion_queue_.size(), variable_name.c_str());
1091 return;
1092 }
1093#endif
1094
1095 RAMAllocator<nextion::NextionQueue> allocator;
1096 nextion::NextionQueue *nextion_queue = allocator.allocate(1);
1097 if (nextion_queue == nullptr) {
1098 ESP_LOGW(TAG, "Queue alloc failed");
1099 return;
1100 }
1101 new (nextion_queue) nextion::NextionQueue();
1102
1103 // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
1104 nextion_queue->component = new nextion::NextionComponentBase;
1105 nextion_queue->component->set_variable_name(variable_name);
1106
1107 nextion_queue->queue_time = App.get_loop_component_start_time();
1108
1109 this->nextion_queue_.push_back(nextion_queue);
1110
1111 ESP_LOGN(TAG, "Queue NORESULT: %s", nextion_queue->component->get_variable_name().c_str());
1112}
1113
1128void Nextion::add_no_result_to_queue_with_command_(const std::string &variable_name, const std::string &command) {
1129 if ((!this->is_setup() && !this->connection_state_.ignore_is_setup_) || command.empty())
1130 return;
1131
1132 if (this->send_command_(command)) {
1133 this->add_no_result_to_queue_(variable_name);
1134#ifdef USE_NEXTION_COMMAND_SPACING
1135 } else {
1136 // Command blocked by spacing, add to queue WITH the command for retry
1137 this->add_no_result_to_queue_with_pending_command_(variable_name, command);
1138#endif // USE_NEXTION_COMMAND_SPACING
1139 }
1140}
1141
1142#ifdef USE_NEXTION_COMMAND_SPACING
1143void Nextion::add_no_result_to_queue_with_pending_command_(const std::string &variable_name,
1144 const std::string &command) {
1145#ifdef USE_NEXTION_MAX_QUEUE_SIZE
1146 if (this->max_queue_size_ > 0 && this->nextion_queue_.size() >= this->max_queue_size_) {
1147 ESP_LOGW(TAG, "Queue full (%zu), drop: %s", this->nextion_queue_.size(), variable_name.c_str());
1148 return;
1149 }
1150#endif
1151
1152 RAMAllocator<nextion::NextionQueue> allocator;
1153 nextion::NextionQueue *nextion_queue = allocator.allocate(1);
1154 if (nextion_queue == nullptr) {
1155 ESP_LOGW(TAG, "Queue alloc failed");
1156 return;
1157 }
1158 new (nextion_queue) nextion::NextionQueue();
1159
1160 nextion_queue->component = new nextion::NextionComponentBase;
1161 nextion_queue->component->set_variable_name(variable_name);
1162 nextion_queue->queue_time = App.get_loop_component_start_time();
1163 nextion_queue->pending_command = command; // Store command for retry
1164
1165 this->nextion_queue_.push_back(nextion_queue);
1166 ESP_LOGVV(TAG, "Queue with pending command: %s", variable_name.c_str());
1167}
1168#endif // USE_NEXTION_COMMAND_SPACING
1169
1170bool Nextion::add_no_result_to_queue_with_ignore_sleep_printf_(const std::string &variable_name, const char *format,
1171 ...) {
1172 if ((!this->is_setup() && !this->connection_state_.ignore_is_setup_))
1173 return false;
1174
1175 char buffer[256];
1176 va_list arg;
1177 va_start(arg, format);
1178 int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
1179 va_end(arg);
1180 if (ret <= 0) {
1181 ESP_LOGW(TAG, "Bad cmd format: '%s'", format);
1182 return false;
1183 }
1184
1185 this->add_no_result_to_queue_with_command_(variable_name, buffer);
1186 return true;
1187}
1188
1196bool Nextion::add_no_result_to_queue_with_printf_(const std::string &variable_name, const char *format, ...) {
1197 if ((!this->is_setup() && !this->connection_state_.ignore_is_setup_) || this->is_sleeping())
1198 return false;
1199
1200 char buffer[256];
1201 va_list arg;
1202 va_start(arg, format);
1203 int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
1204 va_end(arg);
1205 if (ret <= 0) {
1206 ESP_LOGW(TAG, "Bad cmd format: '%s'", format);
1207 return false;
1208 }
1209
1210 this->add_no_result_to_queue_with_command_(variable_name, buffer);
1211 return true;
1212}
1213
1223void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, int32_t state_value) {
1224 this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(),
1225 state_value);
1226}
1227
1228void Nextion::add_no_result_to_queue_with_set(const std::string &variable_name,
1229 const std::string &variable_name_to_send, int32_t state_value) {
1230 this->add_no_result_to_queue_with_set_internal_(variable_name, variable_name_to_send, state_value);
1231}
1232
1233void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &variable_name,
1234 const std::string &variable_name_to_send, int32_t state_value,
1235 bool is_sleep_safe) {
1236 if ((!this->is_setup() && !this->connection_state_.ignore_is_setup_) || (!is_sleep_safe && this->is_sleeping()))
1237 return;
1238
1239 this->add_no_result_to_queue_with_ignore_sleep_printf_(variable_name, "%s=%" PRId32, variable_name_to_send.c_str(),
1240 state_value);
1241}
1242
1251void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) {
1252 this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(),
1253 state_value);
1254}
1255void Nextion::add_no_result_to_queue_with_set(const std::string &variable_name,
1256 const std::string &variable_name_to_send,
1257 const std::string &state_value) {
1258 this->add_no_result_to_queue_with_set_internal_(variable_name, variable_name_to_send, state_value);
1259}
1260
1261void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &variable_name,
1262 const std::string &variable_name_to_send,
1263 const std::string &state_value, bool is_sleep_safe) {
1264 if ((!this->is_setup() && !this->connection_state_.ignore_is_setup_) || (!is_sleep_safe && this->is_sleeping()))
1265 return;
1266
1267 this->add_no_result_to_queue_with_printf_(variable_name, "%s=\"%s\"", variable_name_to_send.c_str(),
1268 state_value.c_str());
1269}
1270
1280void Nextion::add_to_get_queue(NextionComponentBase *component) {
1281 if ((!this->is_setup() && !this->connection_state_.ignore_is_setup_))
1282 return;
1283
1284#ifdef USE_NEXTION_MAX_QUEUE_SIZE
1285 if (this->max_queue_size_ > 0 && this->nextion_queue_.size() >= this->max_queue_size_) {
1286 ESP_LOGW(TAG, "Queue full (%zu), drop GET: %s", this->nextion_queue_.size(),
1287 component->get_variable_name().c_str());
1288 return;
1289 }
1290#endif
1291
1292 RAMAllocator<nextion::NextionQueue> allocator;
1293 nextion::NextionQueue *nextion_queue = allocator.allocate(1);
1294 if (nextion_queue == nullptr) {
1295 ESP_LOGW(TAG, "Queue alloc failed");
1296 return;
1297 }
1298 new (nextion_queue) nextion::NextionQueue();
1299
1300 nextion_queue->component = component;
1301 nextion_queue->queue_time = App.get_loop_component_start_time();
1302
1303 ESP_LOGN(TAG, "Queue %s: %s", component->get_queue_type_string(), component->get_variable_name().c_str());
1304
1305 std::string command = "get " + component->get_variable_name_to_send();
1306
1307#ifdef USE_NEXTION_COMMAND_SPACING
1308 // Always enqueue first so the response handler is present when the command
1309 // is eventually sent. Store the command for retry if spacing blocked it;
1310 // process_pending_in_queue_() will transmit it when the pacer allows.
1311 nextion_queue->pending_command = command;
1312 this->nextion_queue_.push_back(nextion_queue);
1313 if (this->send_command_(command)) {
1314 nextion_queue->pending_command.clear();
1315 }
1316#else // USE_NEXTION_COMMAND_SPACING
1317 if (this->send_command_(command)) {
1318 this->nextion_queue_.push_back(nextion_queue);
1319 } else {
1320 delete nextion_queue; // NOLINT(cppcoreguidelines-owning-memory)
1321 }
1322#endif // USE_NEXTION_COMMAND_SPACING
1323}
1324
1325#ifdef USE_NEXTION_WAVEFORM
1331void Nextion::add_addt_command_to_queue(NextionComponentBase *component) {
1332 if ((!this->is_setup() && !this->connection_state_.ignore_is_setup_) || this->is_sleeping())
1333 return;
1334
1335 RAMAllocator<nextion::NextionQueue> allocator;
1336 nextion::NextionQueue *nextion_queue = allocator.allocate(1);
1337 if (nextion_queue == nullptr) {
1338 ESP_LOGW(TAG, "Queue alloc failed");
1339 return;
1340 }
1341 new (nextion_queue) nextion::NextionQueue();
1342
1343 nextion_queue->component = component;
1344 nextion_queue->queue_time = App.get_loop_component_start_time();
1345
1346 if (!this->waveform_queue_.push(nextion_queue)) {
1347 ESP_LOGW(TAG, "Waveform queue full, drop");
1348 delete nextion_queue; // NOLINT(cppcoreguidelines-owning-memory)
1349 return;
1350 }
1351 if (this->waveform_queue_.size() == 1)
1353}
1354
1356 if (this->waveform_queue_.empty())
1357 return;
1358
1359 auto *nb = this->waveform_queue_.front();
1360 auto *component = nb->component;
1361 size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size() : 255;
1362
1363 char command[24]; // "addt " + uint8 + "," + uint8 + "," + uint8 + null = max 17 chars
1364 buf_append_printf(command, sizeof(command), 0, "addt %u,%u,%zu", component->get_component_id(),
1365 component->get_wave_channel_id(), buffer_to_send);
1366 // If spacing or setup state blocks the send, leave the entry at the front
1367 // of waveform_queue_ for retry on the next loop iteration via
1368 // check_pending_waveform_(). Only pop on a successful send.
1369 this->send_command_(command);
1370}
1371#endif // USE_NEXTION_WAVEFORM
1372
1373void Nextion::set_writer(const nextion_writer_t &writer) { this->writer_ = writer; }
1374
1375bool Nextion::is_updating() { return this->connection_state_.is_updating_; }
1376
1377} // 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:1634
bool send_command_(const std::string &command)
Manually send a raw command to the display and don't wait for an acknowledgement packet.
static constexpr size_t NEXTION_MODEL_MAX
Max observed ~18 chars from product numbering rules.
Definition nextion.h:1615
std::vector< NextionComponentBase * > textsensortype_
Definition nextion.h:1589
char firmware_version_[NEXTION_FW_MAX+1]
Definition nextion.h:1619
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.
void all_components_send_state_(bool force_update=false)
void set_nextion_sensor_state(int queue_type, const std::string &name, float state)
Set the nextion sensor state object.
char serial_number_[NEXTION_SERIAL_MAX+1]
Definition nextion.h:1620
void add_addt_command_to_queue(NextionComponentBase *component) override
uint32_t flash_size_
Flash size in bytes — plain integer, no string needed.
Definition nextion.h:1621
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:1633
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:1630
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.
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.
static constexpr size_t NEXTION_FW_MAX
'S' prefix + integer (e.g. 'S99' or 123)
Definition nextion.h:1616
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)
char device_model_[NEXTION_MODEL_MAX+1]
Definition nextion.h:1618
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
static constexpr size_t NEXTION_SERIAL_MAX
Consistently 16 hex chars across all documented examples.
Definition nextion.h:1617
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)
const void size_t len
Definition hal.h:64
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:867
size_t size_t const char va_start(args, fmt)
void HOT delay(uint32_t ms)
Definition hal.cpp:85
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:28
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