ESPHome  2023.11.6
nextion_upload.cpp
Go to the documentation of this file.
1 #include "nextion.h"
2 
3 #ifdef USE_NEXTION_TFT_UPLOAD
4 
6 #include "esphome/core/defines.h"
7 #include "esphome/core/util.h"
8 #include "esphome/core/log.h"
10 
11 #ifdef USE_ESP32
12 #include <esp_heap_caps.h>
13 #endif
14 
15 namespace esphome {
16 namespace nextion {
17 static const char *const TAG = "nextion_upload";
18 
19 // Followed guide
20 // https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2
21 
22 int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) {
23  int range_end = 0;
24 
25  if (range_start == 0 && this->transfer_buffer_size_ > 16384) { // Start small at the first run in case of a big skip
26  range_end = 16384 - 1;
27  } else {
28  range_end = range_start + this->transfer_buffer_size_ - 1;
29  }
30 
31  if (range_end > this->tft_size_)
32  range_end = this->tft_size_;
33 
34 #ifdef USE_ESP8266
35 #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0)
36  http->setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
37 #elif USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0)
38  http->setFollowRedirects(true);
39 #endif
40 #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0)
41  http->setRedirectLimit(3);
42 #endif
43 #endif
44 
45  char range_header[64];
46  sprintf(range_header, "bytes=%d-%d", range_start, range_end);
47 
48  ESP_LOGD(TAG, "Requesting range: %s", range_header);
49 
50  int tries = 1;
51  int code = 0;
52  bool begin_status = false;
53  while (tries <= 5) {
54 #ifdef USE_ESP32
55  begin_status = http->begin(this->tft_url_.c_str());
56 #endif
57 #ifdef USE_ESP8266
58  begin_status = http->begin(*this->get_wifi_client_(), this->tft_url_.c_str());
59 #endif
60 
61  ++tries;
62  if (!begin_status) {
63  ESP_LOGD(TAG, "upload_by_chunks_: connection failed");
64  continue;
65  }
66 
67  http->addHeader("Range", range_header);
68 
69  code = http->GET();
70  if (code == 200 || code == 206) {
71  break;
72  }
73  ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s, retries(%d/5)", this->tft_url_.c_str(),
74  HTTPClient::errorToString(code).c_str(), tries);
75  http->end();
76  App.feed_wdt();
77  delay(500); // NOLINT
78  }
79 
80  if (tries > 5) {
81  return -1;
82  }
83 
84  std::string recv_string;
85  size_t size = 0;
86  int sent = 0;
87  int range = range_end - range_start;
88 
89  while (sent < range) {
90  size = http->getStreamPtr()->available();
91  if (!size) {
92  App.feed_wdt();
93  delay(0);
94  continue;
95  }
96  int c = http->getStreamPtr()->readBytes(
97  &this->transfer_buffer_[sent], ((size > this->transfer_buffer_size_) ? this->transfer_buffer_size_ : size));
98  sent += c;
99  }
100  http->end();
101  ESP_LOGN(TAG, "this->content_length_ %d sent %d", this->content_length_, sent);
102  for (int i = 0; i < range; i += 4096) {
103  this->write_array(&this->transfer_buffer_[i], 4096);
104  this->content_length_ -= 4096;
105  ESP_LOGN(TAG, "this->content_length_ %d range %d range_end %d range_start %d", this->content_length_, range,
106  range_end, range_start);
107 
108  if (!this->upload_first_chunk_sent_) {
109  this->upload_first_chunk_sent_ = true;
110  delay(500); // NOLINT
111  App.feed_wdt();
112  }
113 
114  this->recv_ret_string_(recv_string, 2048, true);
115  if (recv_string[0] == 0x08) {
116  uint32_t result = 0;
117  for (int i = 0; i < 4; ++i) {
118  result += static_cast<uint8_t>(recv_string[i + 1]) << (8 * i);
119  }
120  if (result > 0) {
121  ESP_LOGD(TAG, "Nextion reported new range %d", result);
122  this->content_length_ = this->tft_size_ - result;
123  return result;
124  }
125  }
126  recv_string.clear();
127  }
128  return range_end + 1;
129 }
130 
132  if (this->is_updating_) {
133  ESP_LOGD(TAG, "Currently updating");
134  return;
135  }
136 
137  if (!network::is_connected()) {
138  ESP_LOGD(TAG, "network is not connected");
139  return;
140  }
141 
142  this->is_updating_ = true;
143 
144  HTTPClient http;
145  http.setTimeout(15000); // Yes 15 seconds.... Helps 8266s along
146  bool begin_status = false;
147 #ifdef USE_ESP32
148  begin_status = http.begin(this->tft_url_.c_str());
149 #endif
150 #ifdef USE_ESP8266
151 #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0)
152  http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
153 #elif USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0)
154  http.setFollowRedirects(true);
155 #endif
156 #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0)
157  http.setRedirectLimit(3);
158 #endif
159  begin_status = http.begin(*this->get_wifi_client_(), this->tft_url_.c_str());
160 #endif
161 
162  if (!begin_status) {
163  this->is_updating_ = false;
164  ESP_LOGD(TAG, "connection failed");
166  allocator.deallocate(this->transfer_buffer_, this->transfer_buffer_size_);
167  return;
168  } else {
169  ESP_LOGD(TAG, "Connected");
170  }
171 
172  http.addHeader("Range", "bytes=0-255");
173  const char *header_names[] = {"Content-Range"};
174  http.collectHeaders(header_names, 1);
175  ESP_LOGD(TAG, "Requesting URL: %s", this->tft_url_.c_str());
176 
177  http.setReuse(true);
178  // try up to 5 times. DNS sometimes needs a second try or so
179  int tries = 1;
180  int code = http.GET();
181  delay(100); // NOLINT
182 
183  App.feed_wdt();
184  while (code != 200 && code != 206 && tries <= 5) {
185  ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s, retrying (%d/5)", this->tft_url_.c_str(),
186  HTTPClient::errorToString(code).c_str(), tries);
187 
188  delay(250); // NOLINT
189  App.feed_wdt();
190  code = http.GET();
191  ++tries;
192  }
193 
194  if ((code != 200 && code != 206) || tries > 5) {
195  this->upload_end_();
196  }
197 
198  String content_range_string = http.header("Content-Range");
199  content_range_string.remove(0, 12);
200  this->content_length_ = content_range_string.toInt();
201  this->tft_size_ = content_length_;
202  http.end();
203 
204  if (this->content_length_ < 4096) {
205  ESP_LOGE(TAG, "Failed to get file size");
206  this->upload_end_();
207  }
208 
209  ESP_LOGD(TAG, "Updating Nextion %s...", this->device_model_.c_str());
210  // The Nextion will ignore the update command if it is sleeping
211 
212  this->send_command_("sleep=0");
213  this->set_backlight_brightness(1.0);
214  delay(250); // NOLINT
215 
216  App.feed_wdt();
217 
218  char command[128];
219  // Tells the Nextion the content length of the tft file and baud rate it will be sent at
220  // Once the Nextion accepts the command it will wait until the file is successfully uploaded
221  // If it fails for any reason a power cycle of the display will be needed
222  sprintf(command, "whmi-wris %d,%d,1", this->content_length_, this->parent_->get_baud_rate());
223 
224  // Clear serial receive buffer
225  uint8_t d;
226  while (this->available()) {
227  this->read_byte(&d);
228  };
229 
230  this->send_command_(command);
231 
232  App.feed_wdt();
233 
234  std::string response;
235  ESP_LOGD(TAG, "Waiting for upgrade response");
236  this->recv_ret_string_(response, 2000, true); // This can take some time to return
237 
238  // The Nextion display will, if it's ready to accept data, send a 0x05 byte.
239  ESP_LOGD(TAG, "Upgrade response is %s %zu", response.c_str(), response.length());
240 
241  for (size_t i = 0; i < response.length(); i++) {
242  ESP_LOGD(TAG, "Available %d : 0x%02X", i, response[i]);
243  }
244 
245  if (response.find(0x05) != std::string::npos) {
246  ESP_LOGD(TAG, "preparation for tft update done");
247  } else {
248  ESP_LOGD(TAG, "preparation for tft update failed %d \"%s\"", response[0], response.c_str());
249  this->upload_end_();
250  }
251 
252  // Nextion wants 4096 bytes at a time. Make chunk_size a multiple of 4096
253 #ifdef USE_ESP32
254  uint32_t chunk_size = 8192;
255  if (heap_caps_get_free_size(MALLOC_CAP_SPIRAM) > 0) {
256  chunk_size = this->content_length_;
257  } else {
258  if (ESP.getFreeHeap() > 40960) { // 32K to keep on hand
259  int chunk = int((ESP.getFreeHeap() - 32768) / 4096);
260  chunk_size = chunk * 4096;
261  chunk_size = chunk_size > 65536 ? 65536 : chunk_size;
262  } else if (ESP.getFreeHeap() < 10240) {
263  chunk_size = 4096;
264  }
265  }
266 #else
267  // NOLINTNEXTLINE(readability-static-accessed-through-instance)
268  uint32_t chunk_size = ESP.getFreeHeap() < 10240 ? 4096 : 8192;
269 #endif
270 
271  if (this->transfer_buffer_ == nullptr) {
273  // NOLINTNEXTLINE(readability-static-accessed-through-instance)
274  ESP_LOGD(TAG, "Allocating buffer size %d, Heap size is %u", chunk_size, ESP.getFreeHeap());
275  this->transfer_buffer_ = allocator.allocate(chunk_size);
276  if (this->transfer_buffer_ == nullptr) { // Try a smaller size
277  ESP_LOGD(TAG, "Could not allocate buffer size: %d trying 4096 instead", chunk_size);
278  chunk_size = 4096;
279  ESP_LOGD(TAG, "Allocating %d buffer", chunk_size);
280  this->transfer_buffer_ = allocator.allocate(chunk_size);
281 
282  if (!this->transfer_buffer_)
283  this->upload_end_();
284  }
285 
286  this->transfer_buffer_size_ = chunk_size;
287  }
288 
289  // NOLINTNEXTLINE(readability-static-accessed-through-instance)
290  ESP_LOGD(TAG, "Updating tft from \"%s\" with a file size of %d using %zu chunksize, Heap Size %d",
291  this->tft_url_.c_str(), this->content_length_, this->transfer_buffer_size_, ESP.getFreeHeap());
292 
293  int result = 0;
294  while (this->content_length_ > 0) {
295  result = this->upload_by_chunks_(&http, result);
296  if (result < 0) {
297  ESP_LOGD(TAG, "Error updating Nextion!");
298  this->upload_end_();
299  }
300  App.feed_wdt();
301  // NOLINTNEXTLINE(readability-static-accessed-through-instance)
302  ESP_LOGD(TAG, "Heap Size %d, Bytes left %d", ESP.getFreeHeap(), this->content_length_);
303  }
304  ESP_LOGD(TAG, "Successfully updated Nextion!");
305 
306  this->upload_end_();
307 }
308 
310  ESP_LOGD(TAG, "Restarting Nextion");
311  this->soft_reset();
312  delay(1500); // NOLINT
313  ESP_LOGD(TAG, "Restarting esphome");
314  ESP.restart(); // NOLINT(readability-static-accessed-through-instance)
315 }
316 
317 #ifdef USE_ESP8266
319  if (this->tft_url_.compare(0, 6, "https:") == 0) {
320  if (this->wifi_client_secure_ == nullptr) {
321  // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
322  this->wifi_client_secure_ = new BearSSL::WiFiClientSecure();
323  this->wifi_client_secure_->setInsecure();
324  this->wifi_client_secure_->setBufferSizes(512, 512);
325  }
326  return this->wifi_client_secure_;
327  }
328 
329  if (this->wifi_client_ == nullptr) {
330  // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
331  this->wifi_client_ = new WiFiClient();
332  }
333  return this->wifi_client_;
334 }
335 #endif
336 } // namespace nextion
337 } // namespace esphome
338 
339 #endif // USE_NEXTION_TFT_UPLOAD
uint32_t get_baud_rate() const
void write_array(const uint8_t *data, size_t len)
Definition: uart.h:21
void upload_tft()
Upload the tft file and softreset the Nextion.
An STL allocator that uses SPI RAM.
Definition: helpers.h:645
void deallocate(T *p, size_t n)
Definition: helpers.h:672
int upload_by_chunks_(HTTPClient *http, int range_start)
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:28
BearSSL::WiFiClientSecure * wifi_client_secure_
Definition: nextion.h:817
UARTComponent * parent_
Definition: uart.h:68
bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
Definition: util.cpp:15
bool read_byte(uint8_t *data)
Definition: uart.h:29
Application App
Global storage of Application pointer - only one Application can exist.
uint8_t * transfer_buffer_
Definition: nextion.h:872
void set_backlight_brightness(float brightness)
Set the brightness of the backlight.
std::string device_model_
Definition: nextion.h:863
void soft_reset()
Softreset the Nextion.
std::string tft_url_
Definition: nextion.h:871
WiFiClient * wifi_client_
Definition: nextion.h:816
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag)
Definition: nextion.cpp:860
int content_length_
will request chunk_size chunks from the web server and send each to the nextion
Definition: nextion.h:828
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26