ESPHome 2025.12.0-dev
Loading...
Searching...
No Matches
nextion_upload_arduino.cpp
Go to the documentation of this file.
1#include "nextion.h"
2
3#ifdef USE_NEXTION_TFT_UPLOAD
4#ifdef USE_ARDUINO
5
6#include <cinttypes>
10#include "esphome/core/log.h"
11#include "esphome/core/util.h"
12
13#ifdef USE_ESP32
14#include <esp_heap_caps.h>
15#endif
16
17namespace esphome {
18namespace nextion {
19static const char *const TAG = "nextion.upload.arduino";
20
21// Followed guide
22// https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2
23
24inline uint32_t Nextion::get_free_heap_() {
25#if defined(USE_ESP32)
26 return heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
27#elif defined(USE_ESP8266)
28 return EspClass::getFreeHeap();
29#endif // USE_ESP32 vs USE_ESP8266
30}
31
32int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) {
33 uint32_t range_size = this->tft_size_ - range_start;
34 ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_());
35 uint32_t range_end = ((upload_first_chunk_sent_ or this->tft_size_ < 4096) ? this->tft_size_ : 4096) - 1;
36 ESP_LOGD(TAG, "Range start: %" PRIu32, range_start);
37 if (range_size <= 0 or range_end <= range_start) {
38 ESP_LOGD(TAG, "Range end: %" PRIu32, range_end);
39 ESP_LOGD(TAG, "Range size: %" PRIu32, range_size);
40 ESP_LOGE(TAG, "Invalid range");
41 return -1;
42 }
43
44 char range_header[32];
45 sprintf(range_header, "bytes=%" PRIu32 "-%" PRIu32, range_start, range_end);
46 ESP_LOGV(TAG, "Range: %s", range_header);
47 http_client.addHeader("Range", range_header);
48 int code = http_client.GET();
49 if (code != HTTP_CODE_OK and code != HTTP_CODE_PARTIAL_CONTENT) {
50 ESP_LOGW(TAG, "HTTP failed: %s", HTTPClient::errorToString(code).c_str());
51 return -1;
52 }
53
54 // Allocate the buffer dynamically
55 RAMAllocator<uint8_t> allocator;
56 uint8_t *buffer = allocator.allocate(4096);
57 if (!buffer) {
58 ESP_LOGE(TAG, "Buffer alloc failed");
59 return -1;
60 }
61
62 std::string recv_string;
63 while (true) {
64 App.feed_wdt();
65 const uint16_t buffer_size =
66 this->content_length_ < 4096 ? this->content_length_ : 4096; // Limits buffer to the remaining data
67 ESP_LOGV(TAG, "Fetch %" PRIu16 " bytes", buffer_size);
68 uint16_t read_len = 0;
69 int partial_read_len = 0;
70 const uint32_t start_time = App.get_loop_component_start_time();
71 while (read_len < buffer_size && App.get_loop_component_start_time() - start_time < 5000) {
72 if (http_client.getStreamPtr()->available() > 0) {
73 partial_read_len =
74 http_client.getStreamPtr()->readBytes(reinterpret_cast<char *>(buffer) + read_len, buffer_size - read_len);
75 read_len += partial_read_len;
76 if (partial_read_len > 0) {
77 App.feed_wdt();
78 delay(2);
79 }
80 }
81 }
82 if (read_len != buffer_size) {
83 // Did not receive the full package within the timeout period
84 ESP_LOGE(TAG, "Read failed: %" PRIu16 "/%" PRIu16 " bytes", read_len, buffer_size);
85 // Deallocate buffer
86 allocator.deallocate(buffer, 4096);
87 buffer = nullptr;
88 return -1;
89 }
90 ESP_LOGV(TAG, "Fetched %d bytes", read_len);
91 if (read_len > 0) {
92 recv_string.clear();
93 this->write_array(buffer, buffer_size);
94 App.feed_wdt();
95 this->recv_ret_string_(recv_string, upload_first_chunk_sent_ ? 500 : 5000, true);
96 this->content_length_ -= read_len;
97 const float upload_percentage = 100.0f * (this->tft_size_ - this->content_length_) / this->tft_size_;
98#if defined(USE_ESP32) && defined(USE_PSRAM)
99 ESP_LOGD(TAG, "Upload: %0.2f%% (%" PRIu32 " left, heap: %" PRIu32 "+%" PRIu32 ")", upload_percentage,
100 this->content_length_, static_cast<uint32_t>(heap_caps_get_free_size(MALLOC_CAP_INTERNAL)),
101 static_cast<uint32_t>(heap_caps_get_free_size(MALLOC_CAP_SPIRAM)));
102#else
103 ESP_LOGD(TAG, "Upload: %0.2f%% (%" PRIu32 " left, heap: %" PRIu32 ")", upload_percentage, this->content_length_,
104 this->get_free_heap_());
105#endif
107 if (recv_string[0] == 0x08 && recv_string.size() == 5) { // handle partial upload request
108 ESP_LOGD(TAG, "Recv: [%s]",
109 format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
110 uint32_t result = 0;
111 for (int j = 0; j < 4; ++j) {
112 result += static_cast<uint8_t>(recv_string[j + 1]) << (8 * j);
113 }
114 if (result > 0) {
115 ESP_LOGI(TAG, "New range: %" PRIu32, result);
116 this->content_length_ = this->tft_size_ - result;
117 range_start = result;
118 } else {
119 range_start = range_end + 1;
120 }
121 // Deallocate buffer
122 allocator.deallocate(buffer, 4096);
123 buffer = nullptr;
124 return range_end + 1;
125 } else if (recv_string[0] != 0x05 and recv_string[0] != 0x08) { // 0x05 == "ok"
126 ESP_LOGE(TAG, "Invalid response: [%s]",
127 format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
128 // Deallocate buffer
129 allocator.deallocate(buffer, 4096);
130 buffer = nullptr;
131 return -1;
132 }
133
134 recv_string.clear();
135 } else if (read_len == 0) {
136 ESP_LOGV(TAG, "HTTP end");
137 break; // Exit the loop if there is no more data to read
138 } else {
139 ESP_LOGE(TAG, "HTTP read failed: %d", read_len);
140 break; // Exit the loop on error
141 }
142 }
143 range_start = range_end + 1;
144 // Deallocate buffer
145 allocator.deallocate(buffer, 4096);
146 buffer = nullptr;
147 return range_end + 1;
148}
149
150bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
151 ESP_LOGD(TAG, "TFT upload requested");
152 ESP_LOGD(TAG, "Exit reparse: %s", YESNO(exit_reparse));
153 ESP_LOGD(TAG, "URL: %s", this->tft_url_.c_str());
154
155 if (this->connection_state_.is_updating_) {
156 ESP_LOGW(TAG, "Upload in progress");
157 return false;
158 }
159
160 if (!network::is_connected()) {
161 ESP_LOGE(TAG, "No network");
162 return false;
163 }
164
165 this->connection_state_.is_updating_ = true;
166
167 if (exit_reparse) {
168 ESP_LOGD(TAG, "Exit reparse mode");
169 if (!this->set_protocol_reparse_mode(false)) {
170 ESP_LOGW(TAG, "Exit reparse failed");
171 return false;
172 }
173 }
174
175 // Check if baud rate is supported
177 ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate);
178
179 // Define the configuration for the HTTP client
180 ESP_LOGV(TAG, "Init HTTP client");
181 ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_());
182 HTTPClient http_client;
183 http_client.setTimeout(15000); // Yes 15 seconds.... Helps 8266s along
184
185 bool begin_status = false;
186#ifdef USE_ESP32
187 begin_status = http_client.begin(this->tft_url_.c_str());
188#endif
189#ifdef USE_ESP8266
190#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0)
191 http_client.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
192#elif USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0)
193 http_client.setFollowRedirects(true);
194#endif
195#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0)
196 http_client.setRedirectLimit(3);
197#endif
198 begin_status = http_client.begin(*this->get_wifi_client_(), this->tft_url_.c_str());
199#endif // USE_ESP8266
200 if (!begin_status) {
201 this->connection_state_.is_updating_ = false;
202 ESP_LOGD(TAG, "Connection failed");
203 return false;
204 } else {
205 ESP_LOGD(TAG, "Connected");
206 }
207 http_client.addHeader("Range", "bytes=0-255");
208 const char *header_names[] = {"Content-Range"};
209 http_client.collectHeaders(header_names, 1);
210 ESP_LOGD(TAG, "URL: %s", this->tft_url_.c_str());
211 http_client.setReuse(true);
212 // try up to 5 times. DNS sometimes needs a second try or so
213 int tries = 1;
214 int code = http_client.GET();
215 delay(100); // NOLINT
216
217 App.feed_wdt();
218 while (code != 200 && code != 206 && tries <= 5) {
219 ESP_LOGW(TAG, "HTTP fail: URL: %s; Error: %s, retry %d/5", this->tft_url_.c_str(),
220 HTTPClient::errorToString(code).c_str(), tries);
221
222 delay(250); // NOLINT
223 App.feed_wdt();
224 code = http_client.GET();
225 ++tries;
226 }
227
228 if (code != 200 and code != 206) {
229 return this->upload_end_(false);
230 }
231
232 String content_range_string = http_client.header("Content-Range");
233 content_range_string.remove(0, 12);
234 this->tft_size_ = content_range_string.toInt();
235
236 ESP_LOGD(TAG, "TFT size: %zu bytes", this->tft_size_);
237 if (this->tft_size_ < 4096) {
238 ESP_LOGE(TAG, "Size check failed");
239 ESP_LOGD(TAG, "Close HTTP");
240 http_client.end();
241 ESP_LOGV(TAG, "Connection closed");
242 return this->upload_end_(false);
243 } else {
244 ESP_LOGV(TAG, "Size check OK");
245 }
246 this->content_length_ = this->tft_size_;
247
248 ESP_LOGD(TAG, "Uploading");
249
250 // The Nextion will ignore the upload command if it is sleeping
251 ESP_LOGV(TAG, "Wake-up");
252 this->connection_state_.ignore_is_setup_ = true;
253 this->send_command_("sleep=0");
254 this->send_command_("dim=100");
255 delay(250); // NOLINT
256 ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_());
257
258 App.feed_wdt();
259 char command[128];
260 // Tells the Nextion the content length of the tft file and baud rate it will be sent at
261 // Once the Nextion accepts the command it will wait until the file is successfully uploaded
262 // If it fails for any reason a power cycle of the display will be needed
263 sprintf(command, "whmi-wris %d,%d,1", this->content_length_, baud_rate);
264
265 // Clear serial receive buffer
266 ESP_LOGV(TAG, "Clear RX buffer");
267 this->reset_(false);
268 delay(250); // NOLINT
269 ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_());
270
271 ESP_LOGV(TAG, "Upload cmd: %s", command);
272 this->send_command_(command);
273
274 if (baud_rate != this->original_baud_rate_) {
275 ESP_LOGD(TAG, "Baud: %" PRIu32 "->%" PRIu32, this->original_baud_rate_, baud_rate);
276 this->parent_->set_baud_rate(baud_rate);
277 this->parent_->load_settings();
278 }
279
280 App.feed_wdt();
281
282 std::string response;
283 ESP_LOGV(TAG, "Wait upload resp");
284 this->recv_ret_string_(response, 5000, true); // This can take some time to return
285
286 // The Nextion display will, if it's ready to accept data, send a 0x05 byte.
287 ESP_LOGD(TAG, "Upload resp: [%s] %zu B",
288 format_hex_pretty(reinterpret_cast<const uint8_t *>(response.data()), response.size()).c_str(),
289 response.length());
290 ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_());
291
292 if (response.find(0x05) != std::string::npos) {
293 ESP_LOGV(TAG, "Upload prep done");
294 } else {
295 ESP_LOGE(TAG, "Prep failed %d '%s'", response[0], response.c_str());
296 ESP_LOGD(TAG, "Close HTTP");
297 http_client.end();
298 ESP_LOGV(TAG, "Connection closed");
299 return this->upload_end_(false);
300 }
301
302 ESP_LOGD(TAG, "Upload TFT:");
303 ESP_LOGD(TAG, " URL: %s", this->tft_url_.c_str());
304 ESP_LOGD(TAG, " Size: %d bytes", this->content_length_);
305 ESP_LOGD(TAG, " Heap: %" PRIu32, this->get_free_heap_());
306
307 // Proceed with the content download as before
308
309 ESP_LOGV(TAG, "Start chunk transfer");
310
311 uint32_t position = 0;
312 while (this->content_length_ > 0) {
313 int upload_result = upload_by_chunks_(http_client, position);
314 if (upload_result < 0) {
315 ESP_LOGE(TAG, "Upload error");
316 ESP_LOGD(TAG, "Close HTTP");
317 http_client.end();
318 ESP_LOGV(TAG, "Connection closed");
319 return this->upload_end_(false);
320 }
321 App.feed_wdt();
322 ESP_LOGV(TAG, "Heap: %" PRIu32 " left: %" PRIu32, this->get_free_heap_(), this->content_length_);
323 }
324
325 ESP_LOGD(TAG, "Upload complete");
326
327 ESP_LOGV(TAG, "Close HTTP");
328 http_client.end();
329 ESP_LOGV(TAG, "Connection closed");
330 return upload_end_(true);
331}
332
333#ifdef USE_ESP8266
335 if (this->tft_url_.compare(0, 6, "https:") == 0) {
336 if (this->wifi_client_secure_ == nullptr) {
337 // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
338 this->wifi_client_secure_ = new BearSSL::WiFiClientSecure();
339 this->wifi_client_secure_->setInsecure();
340 this->wifi_client_secure_->setBufferSizes(512, 512);
341 }
342 return this->wifi_client_secure_;
343 }
344
345 if (this->wifi_client_ == nullptr) {
346 // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
347 this->wifi_client_ = new WiFiClient();
348 }
349 return this->wifi_client_;
350}
351#endif // USE_ESP8266
352
353} // namespace nextion
354} // namespace esphome
355
356#endif // USE_ARDUINO
357#endif // USE_NEXTION_TFT_UPLOAD
void feed_wdt(uint32_t time=0)
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.
An STL allocator that uses SPI or internal RAM.
Definition helpers.h:1084
void deallocate(T *p, size_t n)
Definition helpers.h:1142
T * allocate(size_t n)
Definition helpers.h:1104
uint32_t get_free_heap_()
Returns the ESP Free Heap memory.
bool send_command_(const std::string &command)
Manually send a raw command to the display and don't wait for an acknowledgement packet.
Definition nextion.cpp:26
struct esphome::nextion::Nextion::@144 connection_state_
Status flags for Nextion display state management.
WiFiClient * wifi_client_
Definition nextion.h:1415
bool upload_tft(uint32_t baud_rate=0, bool exit_reparse=true)
Uploads the TFT file to the Nextion display.
bool set_protocol_reparse_mode(bool active_mode)
Sets the Nextion display's protocol reparse mode.
bool upload_end_(bool successful)
Ends the upload process, restart Nextion and, if successful, restarts ESP.
BearSSL::WiFiClientSecure * wifi_client_secure_
Definition nextion.h:1416
uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag)
Definition nextion.cpp:976
void reset_(bool reset_nextion=true)
Definition nextion.cpp:136
int upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start)
will request chunk_size chunks from the web server and send each to the nextion
virtual void load_settings(bool dump_config)
Load the UART settings.
void set_baud_rate(uint32_t baud_rate)
UARTComponent * parent_
Definition uart.h:74
void write_array(const uint8_t *data, size_t len)
Definition uart.h:27
float position
Definition cover.h:0
bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
Definition util.cpp:26
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string format_hex_pretty(const uint8_t *data, size_t length, char separator, bool show_length)
Format a byte array in pretty-printed, human-readable hex format.
Definition helpers.cpp:317
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:31
Application App
Global storage of Application pointer - only one Application can exist.