3#ifdef USE_NEXTION_TFT_UPLOAD
6#include <esp_heap_caps.h>
7#include <esp_http_client.h>
19static const char *
const TAG =
"nextion.upload.esp32";
20static constexpr size_t NEXTION_MAX_RESPONSE_LOG_BYTES = 16;
27static constexpr uint32_t NEXTION_UPLOAD_ACK_TIMEOUT_MS = 5000;
34 ESP_LOGV(TAG,
"Heap: %" PRIu32, esp_get_free_heap_size());
36 ESP_LOGD(TAG,
"Range start: %" PRIu32, range_start);
37 if (range_size <= 0 or range_end <= range_start) {
38 ESP_LOGE(TAG,
"Invalid range end: %" PRIu32
", size: %" PRIu32, range_end, range_size);
42 char range_header[32];
43 buf_append_printf(range_header,
sizeof(range_header), 0,
"bytes=%" PRIu32
"-%" PRIu32, range_start, range_end);
44 ESP_LOGV(TAG,
"Range: %s", range_header);
45 esp_http_client_set_header(http_client,
"Range", range_header);
46 ESP_LOGV(TAG,
"Open HTTP");
49 esp_err_t last_err = ESP_FAIL;
52 last_err = esp_http_client_open(http_client, 0);
53 if (last_err == ESP_OK) {
54 ESP_LOGV(TAG,
"Fetch length");
55 chunk_size = esp_http_client_fetch_headers(http_client);
56 ESP_LOGV(TAG,
"Length: %d", chunk_size);
57 if (chunk_size >= 0) {
58 status_code = esp_http_client_get_status_code(http_client);
62 ((status_code == 206 && chunk_size ==
static_cast<int>(range_end - range_start + 1)) ||
63 (status_code == 200 && range_start == 0 && chunk_size ==
static_cast<int>(this->
tft_size_)))) {
66 if (status_code == 200) {
67 if (range_start == 0) {
68 ESP_LOGE(TAG,
"Unexpected length for 200 response: %d (expected %d)", chunk_size,
72 ESP_LOGE(TAG,
"Server does not support range requests (got 200 at offset %" PRIu32
")", range_start);
78 ESP_LOGW(TAG,
"Bad response: status %d, length %d (expected %" PRIu32
")", status_code, chunk_size,
79 range_end - range_start + 1);
83 if (status_code >= 400 && status_code < 500 && status_code != 408 && status_code != 429) {
87 ESP_LOGW(TAG,
"Get length failed: %d", chunk_size);
91 ESP_LOGW(TAG,
"HTTP open failed: %s", esp_err_to_name(last_err));
95 esp_http_client_close(http_client);
96 vTaskDelay(pdMS_TO_TICKS(2));
99 if (chunk_size <= 0) {
100 ESP_LOGE(TAG,
"HTTP request failed, last status: %d, last error: %s", status_code, esp_err_to_name(last_err));
106 uint8_t *buffer = allocator.
allocate(4096);
108 ESP_LOGE(TAG,
"Buffer alloc failed");
112 std::string recv_string;
115 const uint16_t buffer_size =
117 ESP_LOGV(TAG,
"Fetch %" PRIu16
" bytes", buffer_size);
118 uint16_t read_len = 0;
119 int partial_read_len = 0;
124 esp_http_client_read(http_client,
reinterpret_cast<char *
>(buffer) + read_len, buffer_size - read_len);
125 if (partial_read_len > 0) {
126 read_len += partial_read_len;
132 vTaskDelay(pdMS_TO_TICKS(2));
136 if (read_len != buffer_size) {
138 ESP_LOGE(TAG,
"Read failed: %" PRIu16
"/%" PRIu16
" bytes", read_len, buffer_size);
144 ESP_LOGV(TAG,
"Fetched %d bytes", read_len);
156 if (!recv_string.empty() && recv_string[0] == 0x08 && recv_string.size() < 5) {
157 const uint32_t deadline =
millis() + NEXTION_UPLOAD_ACK_TIMEOUT_MS;
158 while (recv_string.size() < 5 &&
millis() < deadline) {
162 recv_string.push_back(
static_cast<char>(b));
165 vTaskDelay(pdMS_TO_TICKS(5));
169 if (recv_string.size() < 5) {
170 ESP_LOGE(TAG,
"Truncated 0x08 response: got %zu bytes within %" PRIu32
"ms", recv_string.size(),
171 NEXTION_UPLOAD_ACK_TIMEOUT_MS);
180 ESP_LOGD(TAG,
"Upload: %0.2f%% (%" PRIu32
" left, heap: %" PRIu32
"+%" PRIu32
")", upload_percentage,
182 static_cast<uint32_t>(heap_caps_get_free_size(MALLOC_CAP_SPIRAM)));
184 ESP_LOGD(TAG,
"Upload: %0.2f%% (%" PRIu32
" left, heap: %" PRIu32
")", upload_percentage, this->
content_length_,
185 static_cast<uint32_t>(esp_get_free_heap_size()));
188 if (recv_string.empty()) {
189 ESP_LOGW(TAG,
"No response from display after %" PRIu32
"ms", NEXTION_UPLOAD_ACK_TIMEOUT_MS);
194 if (recv_string[0] == 0x08 && recv_string.size() == 5) {
198 format_hex_pretty_to(hex_buf,
reinterpret_cast<const uint8_t *
>(recv_string.data()), recv_string.size()));
200 for (
int j = 0; j < 4; ++j) {
201 result +=
static_cast<uint8_t
>(recv_string[j + 1]) << (8 * j);
204 ESP_LOGI(TAG,
"New range: %" PRIu32, result);
206 range_start = result;
208 range_start = range_end + 1;
212 esp_http_client_close(http_client);
216 return range_end + 1;
217 }
else if (recv_string[0] != 0x05 and recv_string[0] != 0x08) {
220 TAG,
"Invalid response: [%s]",
221 format_hex_pretty_to(hex_buf,
reinterpret_cast<const uint8_t *
>(recv_string.data()), recv_string.size()));
229 }
else if (read_len == 0) {
230 ESP_LOGV(TAG,
"HTTP end");
233 ESP_LOGE(TAG,
"HTTP read failed: %" PRIu16, read_len);
237 range_start = range_end + 1;
241 return range_end + 1;
245 ESP_LOGD(TAG,
"TFT upload requested, exit reparse: %s, URL: %s", YESNO(exit_reparse), this->
tft_url_.c_str());
248 ESP_LOGW(TAG,
"Upload in progress");
253 ESP_LOGE(TAG,
"No network");
263 ESP_LOGD(TAG,
"Exit reparse mode");
265 ESP_LOGW(TAG,
"Exit reparse failed");
272 if (baud_rate <= 0) {
275 ESP_LOGD(TAG,
"Baud rate: %" PRIu32, baud_rate);
278 ESP_LOGV(TAG,
"Init HTTP client, heap: %" PRIu32, esp_get_free_heap_size());
279 esp_http_client_config_t config = {
282 .method = HTTP_METHOD_HEAD,
284 .disable_auto_redirect =
false,
285 .max_redirection_count = 10,
288 esp_http_client_handle_t http_client = esp_http_client_init(&config);
290 ESP_LOGE(TAG,
"HTTP init failed");
294 esp_err_t err = esp_http_client_set_header(http_client,
"Connection",
"keep-alive");
296 ESP_LOGE(TAG,
"Set header failed: %s", esp_err_to_name(err));
297 esp_http_client_cleanup(http_client);
302 ESP_LOGV(TAG,
"Check connection, heap: %" PRIu32, esp_get_free_heap_size());
303 err = esp_http_client_perform(http_client);
305 ESP_LOGE(TAG,
"HTTP failed: %s", esp_err_to_name(err));
306 esp_http_client_cleanup(http_client);
311 ESP_LOGV(TAG,
"Check status, heap: %" PRIu32, esp_get_free_heap_size());
312 int status_code = esp_http_client_get_status_code(http_client);
313 if (status_code != 200 && status_code != 206) {
314 ESP_LOGE(TAG,
"HTTP request failed with status %d", status_code);
318 this->
tft_size_ = esp_http_client_get_content_length(http_client);
320 ESP_LOGD(TAG,
"TFT size: %zu bytes", this->
tft_size_);
322 ESP_LOGE(TAG,
"Size check failed");
323 ESP_LOGD(TAG,
"Close HTTP");
324 esp_http_client_close(http_client);
325 esp_http_client_cleanup(http_client);
326 ESP_LOGV(TAG,
"Connection closed");
329 ESP_LOGV(TAG,
"Size check OK");
333 ESP_LOGD(TAG,
"Uploading");
336 ESP_LOGV(TAG,
"Wake-up");
340 vTaskDelay(pdMS_TO_TICKS(250));
341 ESP_LOGV(TAG,
"Heap: %" PRIu32, esp_get_free_heap_size());
348 snprintf(command,
sizeof(command),
"whmi-wris %" PRIu32
",%" PRIu32
",1", this->
content_length_, baud_rate);
351 ESP_LOGV(TAG,
"Clear RX buffer");
353 vTaskDelay(pdMS_TO_TICKS(250));
354 ESP_LOGV(TAG,
"Heap: %" PRIu32, esp_get_free_heap_size());
356 ESP_LOGV(TAG,
"Upload cmd: %s", command);
365 std::string response;
366 ESP_LOGV(TAG,
"Wait upload resp");
371 ESP_LOGD(TAG,
"Upload resp: [%s] %zu B",
372 format_hex_pretty_to(hex_buf,
reinterpret_cast<const uint8_t *
>(response.data()), response.size()),
374 ESP_LOGV(TAG,
"Heap: %" PRIu32, esp_get_free_heap_size());
376 if (response.find(0x05) != std::string::npos) {
377 ESP_LOGV(TAG,
"Upload prep done");
379 ESP_LOGE(TAG,
"Upload prep failed %d '%s'", response[0], response.c_str());
380 ESP_LOGD(TAG,
"Close HTTP");
381 esp_http_client_close(http_client);
382 esp_http_client_cleanup(http_client);
383 ESP_LOGV(TAG,
"Connection closed");
387 ESP_LOGV(TAG,
"Set method to GET");
388 esp_err_t set_method_result = esp_http_client_set_method(http_client, HTTP_METHOD_GET);
389 if (set_method_result != ESP_OK) {
390 ESP_LOGE(TAG,
"Set GET failed: %s", esp_err_to_name(set_method_result));
397 " Size: %" PRIu32
" bytes\n"
399 this->
tft_url_.c_str(), this->content_length_, esp_get_free_heap_size());
403 ESP_LOGV(TAG,
"Start chunk transfer");
408 if (upload_result < 0) {
409 ESP_LOGE(TAG,
"TFT upload error");
410 ESP_LOGD(TAG,
"Close HTTP");
411 esp_http_client_close(http_client);
412 esp_http_client_cleanup(http_client);
413 ESP_LOGV(TAG,
"Connection closed");
417 ESP_LOGV(TAG,
"Heap: %" PRIu32
" left: %" PRIu32, esp_get_free_heap_size(), this->
content_length_);
420 ESP_LOGD(TAG,
"TFT upload complete, closing HTTP");
421 esp_http_client_close(http_client);
422 esp_http_client_cleanup(http_client);
423 ESP_LOGV(TAG,
"Connection closed");
void feed_wdt()
Feed the task watchdog.
An STL allocator that uses SPI or internal RAM.
void deallocate(T *p, size_t n)
int upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &range_start)
will request 4096 bytes chunks from the web server and send each to Nextion
bool send_command_(const std::string &command)
Manually send a raw command to the display and don't wait for an acknowledgement packet.
uint8_t tft_upload_http_retries_
HTTP retry count (default: 5)
bool upload_tft(uint32_t baud_rate=0, bool exit_reparse=true)
Uploads the TFT file to the Nextion display.
uint16_t tft_upload_http_timeout_
HTTP timeout in ms (default: 4.5s)
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.
uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag)
struct esphome::nextion::Nextion::@147 connection_state_
Status flags for Nextion display state management.
void reset_(bool reset_nextion=true)
uint32_t original_baud_rate_
uint32_t tft_upload_watchdog_timeout_
WDT timeout in ms (0 = no adjustment)
bool upload_first_chunk_sent_
void set_baud_rate(uint32_t baud_rate)
virtual void load_settings(bool dump_config)=0
Load the UART settings.
uint32_t get_baud_rate() const
bool read_byte(uint8_t *data)
void write_array(const uint8_t *data, size_t len)
ESPHOME_ALWAYS_INLINE bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
uint32_t IRAM_ATTR HOT millis()
Application App
Global storage of Application pointer - only one Application can exist.