ESPHome  2024.7.2
nextion.cpp
Go to the documentation of this file.
1 #include "nextion.h"
2 #include "esphome/core/util.h"
3 #include "esphome/core/log.h"
5 #include <cinttypes>
6 
7 namespace esphome {
8 namespace nextion {
9 
10 static const char *const TAG = "nextion";
11 
13  this->is_setup_ = false;
14  this->ignore_is_setup_ = true;
15 
16  // Wake up the nextion
17  this->send_command_("bkcmd=0");
18  this->send_command_("sleep=0");
19 
20  this->send_command_("bkcmd=0");
21  this->send_command_("sleep=0");
22 
23  // Reboot it
24  this->send_command_("rest");
25 
26  this->ignore_is_setup_ = false;
27 }
28 
29 bool Nextion::send_command_(const std::string &command) {
30  if (!this->ignore_is_setup_ && !this->is_setup()) {
31  return false;
32  }
33 
34  ESP_LOGN(TAG, "send_command %s", command.c_str());
35 
36  this->write_str(command.c_str());
37  const uint8_t to_send[3] = {0xFF, 0xFF, 0xFF};
38  this->write_array(to_send, sizeof(to_send));
39  return true;
40 }
41 
43  if (this->get_is_connected_())
44  return true;
45 
46  if (this->comok_sent_ == 0) {
47  this->reset_(false);
48 
49  this->ignore_is_setup_ = true;
50  this->send_command_("boguscommand=0"); // bogus command. needed sometimes after updating
51  if (this->exit_reparse_on_start_) {
52  this->send_command_("DRAKJHSUYDGBNCJHGJKSHBDN");
53  }
54  this->send_command_("connect");
55 
56  this->comok_sent_ = millis();
57  this->ignore_is_setup_ = false;
58 
59  return false;
60  }
61 
62  if (millis() - this->comok_sent_ <= 500) // Wait 500 ms
63  return false;
64 
65  std::string response;
66 
67  this->recv_ret_string_(response, 0, false);
68  if (!response.empty() && response[0] == 0x1A) {
69  // Swallow invalid variable name responses that may be caused by the above commands
70  ESP_LOGD(TAG, "0x1A error ignored during setup");
71  return false;
72  }
73  if (response.empty() || response.find("comok") == std::string::npos) {
74 #ifdef NEXTION_PROTOCOL_LOG
75  ESP_LOGN(TAG, "Bad connect request %s", response.c_str());
76  for (size_t i = 0; i < response.length(); i++) {
77  ESP_LOGN(TAG, "response %s %d %d %c", response.c_str(), i, response[i], response[i]);
78  }
79 #endif
80 
81  ESP_LOGW(TAG, "Nextion is not connected! ");
82  comok_sent_ = 0;
83  return false;
84  }
85 
86  this->ignore_is_setup_ = true;
87  ESP_LOGI(TAG, "Nextion is connected");
88  this->is_connected_ = true;
89 
90  ESP_LOGN(TAG, "connect request %s", response.c_str());
91 
92  size_t start;
93  size_t end = 0;
94  std::vector<std::string> connect_info;
95  while ((start = response.find_first_not_of(',', end)) != std::string::npos) {
96  end = response.find(',', start);
97  connect_info.push_back(response.substr(start, end - start));
98  }
99 
100  this->is_detected_ = (connect_info.size() == 7);
101  if (this->is_detected_) {
102  ESP_LOGN(TAG, "Received connect_info %zu", connect_info.size());
103 
104  this->device_model_ = connect_info[2];
105  this->firmware_version_ = connect_info[3];
106  this->serial_number_ = connect_info[5];
107  this->flash_size_ = connect_info[6];
108  } else {
109  ESP_LOGE(TAG, "Nextion returned bad connect value \"%s\"", response.c_str());
110  }
111 
112  this->ignore_is_setup_ = false;
113  this->dump_config();
114  return true;
115 }
116 
117 void Nextion::reset_(bool reset_nextion) {
118  uint8_t d;
119 
120  while (this->available()) { // Clear receive buffer
121  this->read_byte(&d);
122  };
123  this->nextion_queue_.clear();
124  this->waveform_queue_.clear();
125 }
126 
128  ESP_LOGCONFIG(TAG, "Nextion:");
129  ESP_LOGCONFIG(TAG, " Device Model: %s", this->device_model_.c_str());
130  ESP_LOGCONFIG(TAG, " Firmware Version: %s", this->firmware_version_.c_str());
131  ESP_LOGCONFIG(TAG, " Serial Number: %s", this->serial_number_.c_str());
132  ESP_LOGCONFIG(TAG, " Flash Size: %s", this->flash_size_.c_str());
133  ESP_LOGCONFIG(TAG, " Wake On Touch: %s", YESNO(this->auto_wake_on_touch_));
134  ESP_LOGCONFIG(TAG, " Exit reparse: %s", YESNO(this->exit_reparse_on_start_));
135 
136  if (this->touch_sleep_timeout_ != 0) {
137  ESP_LOGCONFIG(TAG, " Touch Timeout: %" PRIu32, this->touch_sleep_timeout_);
138  }
139 
140  if (this->wake_up_page_ != -1) {
141  ESP_LOGCONFIG(TAG, " Wake Up Page: %" PRId16, this->wake_up_page_);
142  }
143 
144  if (this->start_up_page_ != -1) {
145  ESP_LOGCONFIG(TAG, " Start Up Page: %" PRId16, this->start_up_page_);
146  }
147 }
148 
151  if (!this->is_setup()) {
152  return;
153  }
154  if (this->writer_.has_value()) {
155  (*this->writer_)(*this);
156  }
157 }
158 
159 void Nextion::add_sleep_state_callback(std::function<void()> &&callback) {
160  this->sleep_callback_.add(std::move(callback));
161 }
162 
163 void Nextion::add_wake_state_callback(std::function<void()> &&callback) {
164  this->wake_callback_.add(std::move(callback));
165 }
166 
167 void Nextion::add_setup_state_callback(std::function<void()> &&callback) {
168  this->setup_callback_.add(std::move(callback));
169 }
170 
171 void Nextion::add_new_page_callback(std::function<void(uint8_t)> &&callback) {
172  this->page_callback_.add(std::move(callback));
173 }
174 
175 void Nextion::add_touch_event_callback(std::function<void(uint8_t, uint8_t, bool)> &&callback) {
176  this->touch_callback_.add(std::move(callback));
177 }
178 
180  if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping())
181  return;
182 
183  for (auto *binarysensortype : this->binarysensortype_) {
184  binarysensortype->update_component();
185  }
186  for (auto *sensortype : this->sensortype_) {
187  sensortype->update_component();
188  }
189  for (auto *switchtype : this->switchtype_) {
190  switchtype->update_component();
191  }
192  for (auto *textsensortype : this->textsensortype_) {
193  textsensortype->update_component();
194  }
195 }
196 
197 bool Nextion::send_command(const char *command) {
198  if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping())
199  return false;
200 
201  if (this->send_command_(command)) {
202  this->add_no_result_to_queue_("send_command");
203  return true;
204  }
205  return false;
206 }
207 
208 bool Nextion::send_command_printf(const char *format, ...) {
209  if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping())
210  return false;
211 
212  char buffer[256];
213  va_list arg;
214  va_start(arg, format);
215  int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
216  va_end(arg);
217  if (ret <= 0) {
218  ESP_LOGW(TAG, "Building command for format '%s' failed!", format);
219  return false;
220  }
221 
222  if (this->send_command_(buffer)) {
223  this->add_no_result_to_queue_("send_command_printf");
224  return true;
225  }
226  return false;
227 }
228 
229 #ifdef NEXTION_PROTOCOL_LOG
231  ESP_LOGN(TAG, "print_queue_members_ (top 10) size %zu", this->nextion_queue_.size());
232  ESP_LOGN(TAG, "*******************************************");
233  int count = 0;
234  for (auto *i : this->nextion_queue_) {
235  if (count++ == 10)
236  break;
237 
238  if (i == nullptr) {
239  ESP_LOGN(TAG, "Nextion queue is null");
240  } else {
241  ESP_LOGN(TAG, "Nextion queue type: %d:%s , name: %s", i->component->get_queue_type(),
242  i->component->get_queue_type_string().c_str(), i->component->get_variable_name().c_str());
243  }
244  }
245  ESP_LOGN(TAG, "*******************************************");
246 }
247 #endif
248 
250  if (!this->check_connect_() || this->is_updating_)
251  return;
252 
253  if (this->nextion_reports_is_setup_ && !this->sent_setup_commands_) {
254  this->ignore_is_setup_ = true;
255  this->sent_setup_commands_ = true;
256  this->send_command_("bkcmd=3"); // Always, returns 0x00 to 0x23 result of serial command.
257 
259 
260  // Check if a startup page has been set and send the command
261  if (this->start_up_page_ != -1) {
262  this->goto_page(this->start_up_page_);
263  }
264 
267 
268  if (this->touch_sleep_timeout_ != 0) {
270  }
271 
272  if (this->wake_up_page_ != -1) {
273  this->set_wake_up_page(this->wake_up_page_);
274  }
275 
276  this->ignore_is_setup_ = false;
277  }
278 
279  this->process_serial_(); // Receive serial data
280  this->process_nextion_commands_(); // Process nextion return commands
281 
282  if (!this->nextion_reports_is_setup_) {
283  if (this->started_ms_ == 0)
284  this->started_ms_ = millis();
285 
286  if (this->started_ms_ + this->startup_override_ms_ < millis()) {
287  ESP_LOGD(TAG, "Manually set nextion report ready");
288  this->nextion_reports_is_setup_ = true;
289  }
290  }
291 }
292 
293 bool Nextion::remove_from_q_(bool report_empty) {
294  if (this->nextion_queue_.empty()) {
295  if (report_empty) {
296  ESP_LOGE(TAG, "Nextion queue is empty!");
297  }
298  return false;
299  }
300 
301  NextionQueue *nb = this->nextion_queue_.front();
302  NextionComponentBase *component = nb->component;
303 
304  ESP_LOGN(TAG, "Removing %s from the queue", component->get_variable_name().c_str());
305 
306  if (component->get_queue_type() == NextionQueueType::NO_RESULT) {
307  if (component->get_variable_name() == "sleep_wake") {
308  this->is_sleeping_ = false;
309  }
310  delete component; // NOLINT(cppcoreguidelines-owning-memory)
311  }
312  delete nb; // NOLINT(cppcoreguidelines-owning-memory)
313  this->nextion_queue_.pop_front();
314  return true;
315 }
316 
318  uint8_t d;
319 
320  while (this->available()) {
321  read_byte(&d);
322  this->command_data_ += d;
323  }
324 }
325 // nextion.tech/instruction-set/
327  if (this->command_data_.length() == 0) {
328  return;
329  }
330 
331  size_t to_process_length = 0;
332  std::string to_process;
333 
334  ESP_LOGN(TAG, "this->command_data_ %s length %d", this->command_data_.c_str(), this->command_data_.length());
335 #ifdef NEXTION_PROTOCOL_LOG
336  this->print_queue_members_();
337 #endif
338  while ((to_process_length = this->command_data_.find(COMMAND_DELIMITER)) != std::string::npos) {
339  ESP_LOGN(TAG, "print_queue_members_ size %zu", this->nextion_queue_.size());
340  while (to_process_length + COMMAND_DELIMITER.length() < this->command_data_.length() &&
341  static_cast<uint8_t>(this->command_data_[to_process_length + COMMAND_DELIMITER.length()]) == 0xFF) {
342  ++to_process_length;
343  ESP_LOGN(TAG, "Add extra 0xFF to process");
344  }
345 
346  this->nextion_event_ = this->command_data_[0];
347 
348  to_process_length -= 1;
349  to_process = this->command_data_.substr(1, to_process_length);
350 
351  switch (this->nextion_event_) {
352  case 0x00: // instruction sent by user has failed
353  ESP_LOGW(TAG, "Nextion reported invalid instruction!");
354  this->remove_from_q_();
355 
356  break;
357  case 0x01: // instruction sent by user was successful
358 
359  ESP_LOGVV(TAG, "instruction sent by user was successful");
360  ESP_LOGN(TAG, "this->nextion_queue_.empty() %s", this->nextion_queue_.empty() ? "True" : "False");
361 
362  this->remove_from_q_();
363  if (!this->is_setup_) {
364  if (this->nextion_queue_.empty()) {
365  ESP_LOGD(TAG, "Nextion is setup");
366  this->is_setup_ = true;
367  this->setup_callback_.call();
368  }
369  }
370 
371  break;
372  case 0x02: // invalid Component ID or name was used
373  ESP_LOGW(TAG, "Nextion reported component ID or name invalid!");
374  this->remove_from_q_();
375  break;
376  case 0x03: // invalid Page ID or name was used
377  ESP_LOGW(TAG, "Nextion reported page ID invalid!");
378  this->remove_from_q_();
379  break;
380  case 0x04: // invalid Picture ID was used
381  ESP_LOGW(TAG, "Nextion reported picture ID invalid!");
382  this->remove_from_q_();
383  break;
384  case 0x05: // invalid Font ID was used
385  ESP_LOGW(TAG, "Nextion reported font ID invalid!");
386  this->remove_from_q_();
387  break;
388  case 0x06: // File operation fails
389  ESP_LOGW(TAG, "Nextion File operation fail!");
390  break;
391  case 0x09: // Instructions with CRC validation fails their CRC check
392  ESP_LOGW(TAG, "Nextion Instructions with CRC validation fails their CRC check!");
393  break;
394  case 0x11: // invalid Baud rate was used
395  ESP_LOGW(TAG, "Nextion reported baud rate invalid!");
396  break;
397  case 0x12: // invalid Waveform ID or Channel # was used
398  if (this->waveform_queue_.empty()) {
399  ESP_LOGW(TAG,
400  "Nextion reported invalid Waveform ID or Channel # was used but no waveform sensor in queue found!");
401  } else {
402  auto &nb = this->waveform_queue_.front();
403  NextionComponentBase *component = nb->component;
404 
405  ESP_LOGW(TAG, "Nextion reported invalid Waveform ID %d or Channel # %d was used!",
406  component->get_component_id(), component->get_wave_channel_id());
407 
408  ESP_LOGN(TAG, "Removing waveform from queue with component id %d and waveform id %d",
409  component->get_component_id(), component->get_wave_channel_id());
410 
411  delete nb; // NOLINT(cppcoreguidelines-owning-memory)
412  this->waveform_queue_.pop_front();
413  }
414  break;
415  case 0x1A: // variable name invalid
416  ESP_LOGW(TAG, "Nextion reported variable name invalid!");
417  this->remove_from_q_();
418  break;
419  case 0x1B: // variable operation invalid
420  ESP_LOGW(TAG, "Nextion reported variable operation invalid!");
421  this->remove_from_q_();
422  break;
423  case 0x1C: // failed to assign
424  ESP_LOGW(TAG, "Nextion reported failed to assign variable!");
425  this->remove_from_q_();
426  break;
427  case 0x1D: // operate EEPROM failed
428  ESP_LOGW(TAG, "Nextion reported operating EEPROM failed!");
429  break;
430  case 0x1E: // parameter quantity invalid
431  ESP_LOGW(TAG, "Nextion reported parameter quantity invalid!");
432  this->remove_from_q_();
433  break;
434  case 0x1F: // IO operation failed
435  ESP_LOGW(TAG, "Nextion reported component I/O operation invalid!");
436  break;
437  case 0x20: // undefined escape characters
438  ESP_LOGW(TAG, "Nextion reported undefined escape characters!");
439  this->remove_from_q_();
440  break;
441  case 0x23: // too long variable name
442  ESP_LOGW(TAG, "Nextion reported too long variable name!");
443  this->remove_from_q_();
444  break;
445  case 0x24: // Serial Buffer overflow occurs
446  ESP_LOGW(TAG, "Nextion reported Serial Buffer overflow!");
447  break;
448  case 0x65: { // touch event return data
449  if (to_process_length != 3) {
450  ESP_LOGW(TAG, "Touch event data is expecting 3, received %zu", to_process_length);
451  break;
452  }
453 
454  uint8_t page_id = to_process[0];
455  uint8_t component_id = to_process[1];
456  uint8_t touch_event = to_process[2]; // 0 -> release, 1 -> press
457  ESP_LOGD(TAG, "Got touch event:");
458  ESP_LOGD(TAG, " page_id: %u", page_id);
459  ESP_LOGD(TAG, " component_id: %u", component_id);
460  ESP_LOGD(TAG, " event type: %s", touch_event ? "PRESS" : "RELEASE");
461  for (auto *touch : this->touch_) {
462  touch->process_touch(page_id, component_id, touch_event != 0);
463  }
464  this->touch_callback_.call(page_id, component_id, touch_event != 0);
465  break;
466  }
467  case 0x66: { // Nextion initiated new page event return data.
468  // Also is used for sendme command which we never explicitly initiate
469  if (to_process_length != 1) {
470  ESP_LOGW(TAG, "New page event data is expecting 1, received %zu", to_process_length);
471  break;
472  }
473 
474  uint8_t page_id = to_process[0];
475  ESP_LOGD(TAG, "Got new page: %u", page_id);
476  this->page_callback_.call(page_id);
477  break;
478  }
479  case 0x67: { // Touch Coordinate (awake)
480  break;
481  }
482  case 0x68: { // touch coordinate data (sleep)
483 
484  if (to_process_length != 5) {
485  ESP_LOGW(TAG, "Touch coordinate data is expecting 5, received %zu", to_process_length);
486  ESP_LOGW(TAG, "%s", to_process.c_str());
487  break;
488  }
489 
490  uint16_t x = (uint16_t(to_process[0]) << 8) | to_process[1];
491  uint16_t y = (uint16_t(to_process[2]) << 8) | to_process[3];
492  uint8_t touch_event = to_process[4]; // 0 -> release, 1 -> press
493  ESP_LOGD(TAG, "Got touch event:");
494  ESP_LOGD(TAG, " x: %u", x);
495  ESP_LOGD(TAG, " y: %u", y);
496  ESP_LOGD(TAG, " type: %s", touch_event ? "PRESS" : "RELEASE");
497  break;
498  }
499 
500  // 0x70 0x61 0x62 0x31 0x32 0x33 0xFF 0xFF 0xFF
501  // Returned when using get command for a string.
502  // Each byte is converted to char.
503  // data: ab123
504  case 0x70: // string variable data return
505  {
506  if (this->nextion_queue_.empty()) {
507  ESP_LOGW(TAG, "ERROR: Received string return but the queue is empty");
508  break;
509  }
510 
511  NextionQueue *nb = this->nextion_queue_.front();
512  NextionComponentBase *component = nb->component;
513 
514  if (component->get_queue_type() != NextionQueueType::TEXT_SENSOR) {
515  ESP_LOGE(TAG, "ERROR: Received string return but next in queue \"%s\" is not a text sensor",
516  component->get_variable_name().c_str());
517  } else {
518  ESP_LOGN(TAG, "Received get_string response: \"%s\" for component id: %s, type: %s", to_process.c_str(),
519  component->get_variable_name().c_str(), component->get_queue_type_string().c_str());
520  component->set_state_from_string(to_process, true, false);
521  }
522 
523  delete nb; // NOLINT(cppcoreguidelines-owning-memory)
524  this->nextion_queue_.pop_front();
525 
526  break;
527  }
528  // 0x71 0x01 0x02 0x03 0x04 0xFF 0xFF 0xFF
529  // Returned when get command to return a number
530  // 4 byte 32-bit value in little endian order.
531  // (0x01+0x02*256+0x03*65536+0x04*16777216)
532  // data: 67305985
533  case 0x71: // numeric variable data return
534  {
535  if (this->nextion_queue_.empty()) {
536  ESP_LOGE(TAG, "ERROR: Received numeric return but the queue is empty");
537  break;
538  }
539 
540  if (to_process_length == 0) {
541  ESP_LOGE(TAG, "ERROR: Received numeric return but no data!");
542  break;
543  }
544 
545  int dataindex = 0;
546 
547  int value = 0;
548 
549  for (int i = 0; i < 4; ++i) {
550  value += to_process[i] << (8 * i);
551  ++dataindex;
552  }
553 
554  NextionQueue *nb = this->nextion_queue_.front();
555  NextionComponentBase *component = nb->component;
556 
557  if (component->get_queue_type() != NextionQueueType::SENSOR &&
559  component->get_queue_type() != NextionQueueType::SWITCH) {
560  ESP_LOGE(TAG, "ERROR: Received numeric return but next in queue \"%s\" is not a valid sensor type %d",
561  component->get_variable_name().c_str(), component->get_queue_type());
562  } else {
563  ESP_LOGN(TAG, "Received numeric return for variable %s, queue type %d:%s, value %d",
564  component->get_variable_name().c_str(), component->get_queue_type(),
565  component->get_queue_type_string().c_str(), value);
566  component->set_state_from_int(value, true, false);
567  }
568 
569  delete nb; // NOLINT(cppcoreguidelines-owning-memory)
570  this->nextion_queue_.pop_front();
571 
572  break;
573  }
574 
575  case 0x86: { // device automatically enters into sleep mode
576  ESP_LOGVV(TAG, "Received Nextion entering sleep automatically");
577  this->is_sleeping_ = true;
578  this->sleep_callback_.call();
579  break;
580  }
581  case 0x87: // device automatically wakes up
582  {
583  ESP_LOGVV(TAG, "Received Nextion leaves sleep automatically");
584  this->is_sleeping_ = false;
585  this->wake_callback_.call();
586  this->all_components_send_state_(false);
587  break;
588  }
589  case 0x88: // system successful start up
590  {
591  ESP_LOGD(TAG, "system successful start up %zu", to_process_length);
592  this->nextion_reports_is_setup_ = true;
593  break;
594  }
595  case 0x89: { // start SD card upgrade
596  break;
597  }
598  // Data from nextion is
599  // 0x90 - Start
600  // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0
601  // 00 - NULL
602  // 00/01 - Single byte for on/off
603  // FF FF FF - End
604  case 0x90: { // Switched component
605  std::string variable_name;
606 
607  // Get variable name
608  auto index = to_process.find('\0');
609  if (index == std::string::npos || (to_process_length - index - 1) < 1) {
610  ESP_LOGE(TAG, "Bad switch component data received for 0x90 event!");
611  ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index);
612  break;
613  }
614 
615  variable_name = to_process.substr(0, index);
616  ++index;
617 
618  ESP_LOGN(TAG, "Got Switch:");
619  ESP_LOGN(TAG, " variable_name: %s", variable_name.c_str());
620  ESP_LOGN(TAG, " value: %d", to_process[0] != 0);
621 
622  for (auto *switchtype : this->switchtype_) {
623  switchtype->process_bool(variable_name, to_process[index] != 0);
624  }
625  break;
626  }
627  // Data from nextion is
628  // 0x91 - Start
629  // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0
630  // 00 - NULL
631  // variable length of 0x71 return data: prints temp1.val,0
632  // FF FF FF - End
633  case 0x91: { // Sensor component
634  std::string variable_name;
635 
636  auto index = to_process.find('\0');
637  if (index == std::string::npos || (to_process_length - index - 1) != 4) {
638  ESP_LOGE(TAG, "Bad sensor component data received for 0x91 event!");
639  ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index);
640  break;
641  }
642 
643  index = to_process.find('\0');
644  variable_name = to_process.substr(0, index);
645  // // Get variable name
646  int value = 0;
647  for (int i = 0; i < 4; ++i) {
648  value += to_process[i + index + 1] << (8 * i);
649  }
650 
651  ESP_LOGN(TAG, "Got sensor:");
652  ESP_LOGN(TAG, " variable_name: %s", variable_name.c_str());
653  ESP_LOGN(TAG, " value: %d", value);
654 
655  for (auto *sensor : this->sensortype_) {
656  sensor->process_sensor(variable_name, value);
657  }
658  break;
659  }
660 
661  // Data from nextion is
662  // 0x92 - Start
663  // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0
664  // 00 - NULL
665  // variable length of 0x70 return formatted data (bytes) that contain the text prints temp1.txt,0
666  // 00 - NULL
667  // FF FF FF - End
668  case 0x92: { // Text Sensor Component
669  std::string variable_name;
670  std::string text_value;
671 
672  // Get variable name
673  auto index = to_process.find('\0');
674  if (index == std::string::npos || (to_process_length - index - 1) < 1) {
675  ESP_LOGE(TAG, "Bad text sensor component data received for 0x92 event!");
676  ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index);
677  break;
678  }
679 
680  variable_name = to_process.substr(0, index);
681  ++index;
682 
683  text_value = to_process.substr(index);
684 
685  ESP_LOGN(TAG, "Got Text Sensor:");
686  ESP_LOGN(TAG, " variable_name: %s", variable_name.c_str());
687  ESP_LOGN(TAG, " value: %s", text_value.c_str());
688 
689  // NextionTextSensorResponseQueue *nq = new NextionTextSensorResponseQueue;
690  // nq->variable_name = variable_name;
691  // nq->state = text_value;
692  // this->textsensorq_.push_back(nq);
693  for (auto *textsensortype : this->textsensortype_) {
694  textsensortype->process_text(variable_name, text_value);
695  }
696  break;
697  }
698  // Data from nextion is
699  // 0x93 - Start
700  // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0
701  // 00 - NULL
702  // 00/01 - Single byte for on/off
703  // FF FF FF - End
704  case 0x93: { // Binary Sensor component
705  std::string variable_name;
706 
707  // Get variable name
708  auto index = to_process.find('\0');
709  if (index == std::string::npos || (to_process_length - index - 1) < 1) {
710  ESP_LOGE(TAG, "Bad binary sensor component data received for 0x92 event!");
711  ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index);
712  break;
713  }
714 
715  variable_name = to_process.substr(0, index);
716  ++index;
717 
718  ESP_LOGN(TAG, "Got Binary Sensor:");
719  ESP_LOGN(TAG, " variable_name: %s", variable_name.c_str());
720  ESP_LOGN(TAG, " value: %d", to_process[index] != 0);
721 
722  for (auto *binarysensortype : this->binarysensortype_) {
723  binarysensortype->process_bool(&variable_name[0], to_process[index] != 0);
724  }
725  break;
726  }
727  case 0xFD: { // data transparent transmit finished
728  ESP_LOGVV(TAG, "Nextion reported data transmit finished!");
729  this->check_pending_waveform_();
730  break;
731  }
732  case 0xFE: { // data transparent transmit ready
733  ESP_LOGVV(TAG, "Nextion reported ready for transmit!");
734  if (this->waveform_queue_.empty()) {
735  ESP_LOGE(TAG, "No waveforms in queue to send data!");
736  break;
737  }
738 
739  auto &nb = this->waveform_queue_.front();
740  auto *component = nb->component;
741  size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size()
742  : 255; // ADDT command can only send 255
743 
744  this->write_array(component->get_wave_buffer().data(), static_cast<int>(buffer_to_send));
745 
746  ESP_LOGN(TAG, "Nextion sending waveform data for component id %d and waveform id %d, size %zu",
747  component->get_component_id(), component->get_wave_channel_id(), buffer_to_send);
748 
749  component->clear_wave_buffer(buffer_to_send);
750  delete nb; // NOLINT(cppcoreguidelines-owning-memory)
751  this->waveform_queue_.pop_front();
752  break;
753  }
754  default:
755  ESP_LOGW(TAG, "Received unknown event from nextion: 0x%02X", this->nextion_event_);
756  break;
757  }
758 
759  // ESP_LOGN(TAG, "nextion_event_ deleting from 0 to %d", to_process_length + COMMAND_DELIMITER.length() + 1);
760  this->command_data_.erase(0, to_process_length + COMMAND_DELIMITER.length() + 1);
761  // App.feed_wdt(); Remove before master merge
762  this->process_serial_();
763  }
764 
765  uint32_t ms = millis();
766 
767  if (!this->nextion_queue_.empty() && this->nextion_queue_.front()->queue_time + this->max_q_age_ms_ < ms) {
768  for (size_t i = 0; i < this->nextion_queue_.size(); i++) {
769  NextionComponentBase *component = this->nextion_queue_[i]->component;
770  if (this->nextion_queue_[i]->queue_time + this->max_q_age_ms_ < ms) {
771  if (this->nextion_queue_[i]->queue_time == 0) {
772  ESP_LOGD(TAG, "Removing old queue type \"%s\" name \"%s\" queue_time 0",
773  component->get_queue_type_string().c_str(), component->get_variable_name().c_str());
774  }
775 
776  if (component->get_variable_name() == "sleep_wake") {
777  this->is_sleeping_ = false;
778  }
779 
780  ESP_LOGD(TAG, "Removing old queue type \"%s\" name \"%s\"", component->get_queue_type_string().c_str(),
781  component->get_variable_name().c_str());
782 
783  if (component->get_queue_type() == NextionQueueType::NO_RESULT) {
784  if (component->get_variable_name() == "sleep_wake") {
785  this->is_sleeping_ = false;
786  }
787  delete component; // NOLINT(cppcoreguidelines-owning-memory)
788  }
789 
790  delete this->nextion_queue_[i]; // NOLINT(cppcoreguidelines-owning-memory)
791 
792  this->nextion_queue_.erase(this->nextion_queue_.begin() + i);
793  i--;
794 
795  } else {
796  break;
797  }
798  }
799  }
800  ESP_LOGN(TAG, "Loop End");
801  // App.feed_wdt(); Remove before master merge
802  this->process_serial_();
803 } // namespace nextion
804 
805 void Nextion::set_nextion_sensor_state(int queue_type, const std::string &name, float state) {
806  this->set_nextion_sensor_state(static_cast<NextionQueueType>(queue_type), name, state);
807 }
808 
809 void Nextion::set_nextion_sensor_state(NextionQueueType queue_type, const std::string &name, float state) {
810  ESP_LOGN(TAG, "Received state:");
811  ESP_LOGN(TAG, " variable: %s", name.c_str());
812  ESP_LOGN(TAG, " state: %lf", state);
813  ESP_LOGN(TAG, " queue type: %d", queue_type);
814 
815  switch (queue_type) {
817  for (auto *sensor : this->sensortype_) {
818  if (name == sensor->get_variable_name()) {
819  sensor->set_state(state, true, true);
820  break;
821  }
822  }
823  break;
824  }
826  for (auto *sensor : this->binarysensortype_) {
827  if (name == sensor->get_variable_name()) {
828  sensor->set_state(state != 0, true, true);
829  break;
830  }
831  }
832  break;
833  }
835  for (auto *sensor : this->switchtype_) {
836  if (name == sensor->get_variable_name()) {
837  sensor->set_state(state != 0, true, true);
838  break;
839  }
840  }
841  break;
842  }
843  default: {
844  ESP_LOGW(TAG, "set_nextion_sensor_state does not support a queue type %d", queue_type);
845  }
846  }
847 }
848 
849 void Nextion::set_nextion_text_state(const std::string &name, const std::string &state) {
850  ESP_LOGD(TAG, "Received state:");
851  ESP_LOGD(TAG, " variable: %s", name.c_str());
852  ESP_LOGD(TAG, " state: %s", state.c_str());
853 
854  for (auto *sensor : this->textsensortype_) {
855  if (name == sensor->get_variable_name()) {
856  sensor->set_state(state, true, true);
857  break;
858  }
859  }
860 }
861 
862 void Nextion::all_components_send_state_(bool force_update) {
863  ESP_LOGD(TAG, "all_components_send_state_ ");
864  for (auto *binarysensortype : this->binarysensortype_) {
865  if (force_update || binarysensortype->get_needs_to_send_update())
866  binarysensortype->send_state_to_nextion();
867  }
868  for (auto *sensortype : this->sensortype_) {
869  if ((force_update || sensortype->get_needs_to_send_update()) && sensortype->get_wave_chan_id() == 0)
870  sensortype->send_state_to_nextion();
871  }
872  for (auto *switchtype : this->switchtype_) {
873  if (force_update || switchtype->get_needs_to_send_update())
874  switchtype->send_state_to_nextion();
875  }
876  for (auto *textsensortype : this->textsensortype_) {
877  if (force_update || textsensortype->get_needs_to_send_update())
878  textsensortype->send_state_to_nextion();
879  }
880 }
881 
882 void Nextion::update_components_by_prefix(const std::string &prefix) {
883  for (auto *binarysensortype : this->binarysensortype_) {
884  if (binarysensortype->get_variable_name().find(prefix, 0) != std::string::npos)
885  binarysensortype->update_component_settings(true);
886  }
887  for (auto *sensortype : this->sensortype_) {
888  if (sensortype->get_variable_name().find(prefix, 0) != std::string::npos)
889  sensortype->update_component_settings(true);
890  }
891  for (auto *switchtype : this->switchtype_) {
892  if (switchtype->get_variable_name().find(prefix, 0) != std::string::npos)
893  switchtype->update_component_settings(true);
894  }
895  for (auto *textsensortype : this->textsensortype_) {
896  if (textsensortype->get_variable_name().find(prefix, 0) != std::string::npos)
897  textsensortype->update_component_settings(true);
898  }
899 }
900 
901 uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag) {
902  uint16_t ret = 0;
903  uint8_t c = 0;
904  uint8_t nr_of_ff_bytes = 0;
905  uint64_t start;
906  bool exit_flag = false;
907  bool ff_flag = false;
908 
909  start = millis();
910 
911  while ((timeout == 0 && this->available()) || millis() - start <= timeout) {
912  if (!this->available()) {
913  App.feed_wdt();
914  delay(1);
915  continue;
916  }
917 
918  this->read_byte(&c);
919  if (c == 0xFF) {
920  nr_of_ff_bytes++;
921  } else {
922  nr_of_ff_bytes = 0;
923  ff_flag = false;
924  }
925 
926  if (nr_of_ff_bytes >= 3)
927  ff_flag = true;
928 
929  response += (char) c;
930  if (recv_flag) {
931  if (response.find(0x05) != std::string::npos) {
932  exit_flag = true;
933  }
934  }
935  App.feed_wdt();
936  delay(2);
937 
938  if (exit_flag || ff_flag) {
939  break;
940  }
941  }
942 
943  if (ff_flag)
944  response = response.substr(0, response.length() - 3); // Remove last 3 0xFF
945 
946  ret = response.length();
947  return ret;
948 }
949 
955 void Nextion::add_no_result_to_queue_(const std::string &variable_name) {
956  // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
957  nextion::NextionQueue *nextion_queue = new nextion::NextionQueue;
958 
959  // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
960  nextion_queue->component = new nextion::NextionComponentBase;
961  nextion_queue->component->set_variable_name(variable_name);
962 
963  nextion_queue->queue_time = millis();
964 
965  this->nextion_queue_.push_back(nextion_queue);
966 
967  ESP_LOGN(TAG, "Add to queue type: NORESULT component %s", nextion_queue->component->get_variable_name().c_str());
968 }
969 
976 void Nextion::add_no_result_to_queue_with_command_(const std::string &variable_name, const std::string &command) {
977  if ((!this->is_setup() && !this->ignore_is_setup_) || command.empty())
978  return;
979 
980  if (this->send_command_(command)) {
981  this->add_no_result_to_queue_(variable_name);
982  }
983 }
984 
985 bool Nextion::add_no_result_to_queue_with_ignore_sleep_printf_(const std::string &variable_name, const char *format,
986  ...) {
987  if ((!this->is_setup() && !this->ignore_is_setup_))
988  return false;
989 
990  char buffer[256];
991  va_list arg;
992  va_start(arg, format);
993  int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
994  va_end(arg);
995  if (ret <= 0) {
996  ESP_LOGW(TAG, "Building command for format '%s' failed!", format);
997  return false;
998  }
999 
1000  this->add_no_result_to_queue_with_command_(variable_name, buffer);
1001  return true;
1002 }
1003 
1011 bool Nextion::add_no_result_to_queue_with_printf_(const std::string &variable_name, const char *format, ...) {
1012  if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping())
1013  return false;
1014 
1015  char buffer[256];
1016  va_list arg;
1017  va_start(arg, format);
1018  int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
1019  va_end(arg);
1020  if (ret <= 0) {
1021  ESP_LOGW(TAG, "Building command for format '%s' failed!", format);
1022  return false;
1023  }
1024 
1025  this->add_no_result_to_queue_with_command_(variable_name, buffer);
1026  return true;
1027 }
1028 
1040  state_value);
1041 }
1042 
1043 void Nextion::add_no_result_to_queue_with_set(const std::string &variable_name,
1044  const std::string &variable_name_to_send, int32_t state_value) {
1045  this->add_no_result_to_queue_with_set_internal_(variable_name, variable_name_to_send, state_value);
1046 }
1047 
1048 void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &variable_name,
1049  const std::string &variable_name_to_send, int32_t state_value,
1050  bool is_sleep_safe) {
1051  if ((!this->is_setup() && !this->ignore_is_setup_) || (!is_sleep_safe && this->is_sleeping()))
1052  return;
1053 
1054  this->add_no_result_to_queue_with_ignore_sleep_printf_(variable_name, "%s=%" PRId32, variable_name_to_send.c_str(),
1055  state_value);
1056 }
1057 
1066 void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) {
1068  state_value);
1069 }
1070 void Nextion::add_no_result_to_queue_with_set(const std::string &variable_name,
1071  const std::string &variable_name_to_send,
1072  const std::string &state_value) {
1073  this->add_no_result_to_queue_with_set_internal_(variable_name, variable_name_to_send, state_value);
1074 }
1075 
1076 void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &variable_name,
1077  const std::string &variable_name_to_send,
1078  const std::string &state_value, bool is_sleep_safe) {
1079  if ((!this->is_setup() && !this->ignore_is_setup_) || (!is_sleep_safe && this->is_sleeping()))
1080  return;
1081 
1082  this->add_no_result_to_queue_with_printf_(variable_name, "%s=\"%s\"", variable_name_to_send.c_str(),
1083  state_value.c_str());
1084 }
1085 
1087  if ((!this->is_setup() && !this->ignore_is_setup_))
1088  return;
1089 
1090  // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
1091  nextion::NextionQueue *nextion_queue = new nextion::NextionQueue;
1092 
1093  nextion_queue->component = component;
1094  nextion_queue->queue_time = millis();
1095 
1096  ESP_LOGN(TAG, "Add to queue type: %s component %s", component->get_queue_type_string().c_str(),
1097  component->get_variable_name().c_str());
1098 
1099  std::string command = "get " + component->get_variable_name_to_send();
1100 
1101  if (this->send_command_(command)) {
1102  this->nextion_queue_.push_back(nextion_queue);
1103  }
1104 }
1105 
1115  if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping())
1116  return;
1117 
1118  // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
1119  nextion::NextionQueue *nextion_queue = new nextion::NextionQueue;
1120 
1121  nextion_queue->component = component;
1122  nextion_queue->queue_time = millis();
1123 
1124  this->waveform_queue_.push_back(nextion_queue);
1125  if (this->waveform_queue_.size() == 1)
1126  this->check_pending_waveform_();
1127 }
1128 
1130  if (this->waveform_queue_.empty())
1131  return;
1132 
1133  auto *nb = this->waveform_queue_.front();
1134  auto *component = nb->component;
1135  size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size()
1136  : 255; // ADDT command can only send 255
1137 
1138  std::string command = "addt " + to_string(component->get_component_id()) + "," +
1139  to_string(component->get_wave_channel_id()) + "," + to_string(buffer_to_send);
1140  if (!this->send_command_(command)) {
1141  delete nb; // NOLINT(cppcoreguidelines-owning-memory)
1142  this->waveform_queue_.pop_front();
1143  }
1144 }
1145 
1146 void Nextion::set_writer(const nextion_writer_t &writer) { this->writer_ = writer; }
1147 
1148 ESPDEPRECATED("set_wait_for_ack(bool) is deprecated and has no effect", "v1.20")
1149 void Nextion::set_wait_for_ack(bool wait_for_ack) { ESP_LOGE(TAG, "This command is deprecated"); }
1150 
1151 bool Nextion::is_updating() { return this->is_updating_; }
1152 
1153 } // namespace nextion
1154 } // namespace esphome
void goto_page(const char *page)
Show the page with a given name.
bool ignore_is_setup_
Sends commands ignoring of the Nextion has been setup.
Definition: nextion.h:1211
const uint16_t startup_override_ms_
Definition: nextion.h:1328
void write_str(const char *str)
Definition: uart.h:27
void all_components_send_state_(bool force_update=false)
Definition: nextion.cpp:862
CallbackManager< void(uint8_t)> page_callback_
Definition: nextion.h:1308
CallbackManager< void()> sleep_callback_
Definition: nextion.h:1306
const char * name
Definition: stm32flash.h:78
const float DATA
For components that import data from directly connected sensors like DHT.
Definition: component.cpp:19
void add_new_page_callback(std::function< void(uint8_t)> &&callback)
Add a callback to be notified when the nextion changes pages.
Definition: nextion.cpp:171
void write_array(const uint8_t *data, size_t len)
Definition: uart.h:21
void add_wake_state_callback(std::function< void()> &&callback)
Add a callback to be notified of wake state changes.
Definition: nextion.cpp:163
bool send_command(const char *command)
Manually send a raw command to the display.
Definition: nextion.cpp:197
bool send_command_printf(const char *format,...) __attribute__((format(printf
Manually send a raw formatted command to the display.
Definition: nextion.cpp:208
void add_addt_command_to_queue(NextionComponentBase *component) override
Add addt command to the queue.
Definition: nextion.cpp:1114
bool is_updating() override
Check if the TFT update process is currently running.
Definition: nextion.cpp:1151
void add_to_get_queue(NextionComponentBase *component) override
Definition: nextion.cpp:1086
std::vector< NextionComponentBase * > touch_
Definition: nextion.h:1300
optional< nextion_writer_t > writer_
Definition: nextion.h:1311
uint16_t x
Definition: tt21100.cpp:17
void add_setup_state_callback(std::function< void()> &&callback)
Add a callback to be notified when the nextion completes its initialize setup.
Definition: nextion.cpp:167
const uint16_t max_q_age_ms_
Definition: nextion.h:1329
bool send_command_(const std::string &command)
Manually send a raw command to the display and don&#39;t wait for an acknowledgement packet.
Definition: nextion.cpp:29
float get_setup_priority() const override
Definition: nextion.cpp:149
void setup() override
Definition: nextion.cpp:12
std::string serial_number_
Definition: nextion.h:1316
CallbackManager< void(uint8_t, uint8_t, bool)> touch_callback_
Definition: nextion.h:1309
bool has_value() const
Definition: optional.h:87
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
void set_exit_reparse_on_start(bool exit_reparse)
Sets if Nextion should exit the active reparse mode before the "connect" command is sent...
uint16_t y
Definition: tt21100.cpp:18
bool add_no_result_to_queue_with_printf_(const std::string &variable_name, const char *format,...) __attribute__((format(printf
Sends a formatted command to the nextion.
Definition: nextion.cpp:1011
void add_sleep_state_callback(std::function< void()> &&callback)
Add a callback to be notified of sleep state changes.
Definition: nextion.cpp:159
virtual void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion)
std::string flash_size_
Definition: nextion.h:1317
void set_nextion_sensor_state(int queue_type, const std::string &name, float state)
Set the nextion sensor state object.
Definition: nextion.cpp:805
CallbackManager< void()> setup_callback_
Definition: nextion.h:1305
bool read_byte(uint8_t *data)
Definition: uart.h:29
void set_nextion_text_state(const std::string &name, const std::string &state)
Definition: nextion.cpp:849
void set_wait_for_ack(bool wait_for_ack)
void loop() override
Definition: nextion.cpp:249
std::deque< NextionQueue * > nextion_queue_
Definition: nextion.h:1200
CallbackManager< void()> wake_callback_
Definition: nextion.h:1307
Application App
Global storage of Application pointer - only one Application can exist.
void add_no_result_to_queue_(const std::string &variable_name)
Definition: nextion.cpp:955
std::deque< NextionQueue * > waveform_queue_
Definition: nextion.h:1201
std::string command_data_
Definition: nextion.h:1326
bool remove_from_q_(bool report_empty=true)
Definition: nextion.cpp:293
void set_backlight_brightness(float brightness)
Set the brightness of the backlight.
std::string device_model_
Definition: nextion.h:1314
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)
Definition: nextion.cpp:1048
bool add_no_result_to_queue_with_ignore_sleep_printf_(const std::string &variable_name, const char *format,...) __attribute__((format(printf
Definition: nextion.cpp:985
void set_touch_sleep_timeout(uint16_t timeout)
Set the touch sleep timeout of the display.
std::vector< NextionComponentBase * > textsensortype_
Definition: nextion.h:1303
ESPDEPRECATED("set_wait_for_ack(bool) is deprecated and has no effect", "v1.20") void Nextion
Definition: nextion.cpp:1148
void add_touch_event_callback(std::function< void(uint8_t, uint8_t, bool)> &&callback)
Add a callback to be notified when Nextion has a touch event.
Definition: nextion.cpp:175
void set_wake_up_page(uint8_t page_id=255)
Sets which page Nextion loads when exiting sleep mode.
std::vector< NextionComponentBase * > sensortype_
Definition: nextion.h:1302
void dump_config() override
Definition: nextion.cpp:127
std::string to_string(int value)
Definition: helpers.cpp:82
std::vector< NextionComponentBase * > switchtype_
Definition: nextion.h:1301
void set_auto_wake_on_touch(bool auto_wake)
Sets if Nextion should auto-wake from sleep when touch press occurs.
virtual void set_state_from_int(int state_value, bool publish, bool send_to_nextion)
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
bool void add_no_result_to_queue_with_command_(const std::string &variable_name, const std::string &command)
Definition: nextion.cpp:976
void reset_(bool reset_nextion=true)
Definition: nextion.cpp:117
void update_components_by_prefix(const std::string &prefix)
Definition: nextion.cpp:882
void update() override
Definition: nextion.cpp:150
uint8_t end[39]
Definition: sun_gtil2.cpp:31
std::string firmware_version_
Definition: nextion.h:1315
void set_variable_name(const std::string &variable_name, const std::string &variable_name_to_send="")
uint32_t touch_sleep_timeout_
Definition: nextion.h:1219
uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag)
Definition: nextion.cpp:901
void add_no_result_to_queue_with_set(NextionComponentBase *component, int32_t state_value) override
Definition: nextion.cpp:1038
std::function< void(Nextion &)> nextion_writer_t
Definition: nextion.h:34
std::vector< NextionComponentBase * > binarysensortype_
Definition: nextion.h:1304
bool state
Definition: fan.h:34
void set_writer(const nextion_writer_t &writer)
Definition: nextion.cpp:1146
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26