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