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