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