Rebuilt upload_tft and replaced fonts

This commit is contained in:
Edward Firmo
2023-10-01 15:05:36 +02:00
parent 4830b58b13
commit 201428da47
12 changed files with 430 additions and 25 deletions

View File

@@ -7,8 +7,14 @@
substitutions:
################## Defaults ##################
# Just in case user forgets to set something #
nextion_update_url: "https://github.com/Blackymas/NSPanel_HA_Blueprint/raw/main/nspanel_eu.tft"
# nextion_update_blank_url: "https://github.com/Blackymas/NSPanel_HA_Blueprint/raw/main/custom_configuration/nspanel_blank.tft"
##############################################
##### DON'T CHANGE THIS #####
version: "4.0.2"
version: "4.1dev3"
#############################
##### WIFI SETUP #####
@@ -67,10 +73,10 @@ output:
##### UART FOR NEXTION DISPLAY #####
uart:
id: tf_uart
tx_pin: 16
rx_pin: 17
baud_rate: 115200
- id: tf_uart
tx_pin: 16
rx_pin: 17
baud_rate: 115200
##### Keeps time display updated #####
time:
@@ -108,7 +114,7 @@ button:
id: nextion_init
state: false
- delay: 16ms
- lambda: id(disp1).upload_tft();
- lambda: id(upload_tft).execute("${nextion_update_url}");
##### EXIT REPARSE TFT DISPLAY #####
- name: ${device_name} Exit reparse
@@ -139,7 +145,7 @@ api:
- binary_sensor.template.publish:
id: nextion_init
state: false
- lambda: 'id(disp1)->upload_tft();'
- lambda: 'id(upload_tft).execute("${nextion_update_url}");'
##### SERVICE TO UPDATE THE TFT FILE from URL #####
- service: upload_tft_url
@@ -150,8 +156,7 @@ api:
- binary_sensor.template.publish:
id: nextion_init
state: false
- lambda: 'id(disp1)->set_tft_url(url.c_str());'
- lambda: 'id(disp1)->upload_tft();'
- lambda: 'id(upload_tft).execute(url.c_str());'
##### Service to send a command "printf" directly to the display #####
- service: send_command_printf
@@ -1245,15 +1250,15 @@ text_sensor:
id(disp1).set_component_text_printf("keyb_num.bclear", "%s", "\uE641"); //mdi:eraser-variant
id(disp1).set_component_text_printf("keyb_num.benter", "%s", "\uE12B"); //mdi:check
}
else if (page=="weather01") id(disp1).set_component_text_printf("page_index", "%s", "\uE764\uE765\uE765\uE765\uE765"); // 1/5
else if (page=="weather02") id(disp1).set_component_text_printf("page_index", "%s", "\uE765\uE764\uE765\uE765\uE765"); // 2/5
else if (page=="weather03") id(disp1).set_component_text_printf("page_index", "%s", "\uE765\uE765\uE764\uE765\uE765"); // 3/5
else if (page=="weather04") id(disp1).set_component_text_printf("page_index", "%s", "\uE765\uE765\uE765\uE764\uE765"); // 4/5
else if (page=="weather05") id(disp1).set_component_text_printf("page_index", "%s", "\uE765\uE765\uE765\uE765\uE764"); // 5/5
else if (page=="buttonpage01" or page=="entitypage01") id(disp1).set_component_text_printf("page_index", "%s", "\uE764\uE765\uE765\uE765"); // 1/4
else if (page=="buttonpage02" or page=="entitypage02") id(disp1).set_component_text_printf("page_index", "%s", "\uE765\uE764\uE765\uE765"); // 2/4
else if (page=="buttonpage03" or page=="entitypage03") id(disp1).set_component_text_printf("page_index", "%s", "\uE765\uE765\uE764\uE765"); // 3/4
else if (page=="buttonpage04" or page=="entitypage04") id(disp1).set_component_text_printf("page_index", "%s", "\uE765\uE765\uE765\uE764"); // 4/4
else if (page=="weather01") id(disp1).set_component_text_printf("page_index", "%s", "●○○○○"); // 1/5
else if (page=="weather02") id(disp1).set_component_text_printf("page_index", "%s", "○●○○○"); // 2/5
else if (page=="weather03") id(disp1).set_component_text_printf("page_index", "%s", "○○●○○"); // 3/5
else if (page=="weather04") id(disp1).set_component_text_printf("page_index", "%s", "○○○●○"); // 4/5
else if (page=="weather05") id(disp1).set_component_text_printf("page_index", "%s", "○○○○●"); // 5/5
else if (page=="buttonpage01" or page=="entitypage01") id(disp1).set_component_text_printf("page_index", "%s", "●○○○"); // 1/4
else if (page=="buttonpage02" or page=="entitypage02") id(disp1).set_component_text_printf("page_index", "%s", "○●○○"); // 2/4
else if (page=="buttonpage03" or page=="entitypage03") id(disp1).set_component_text_printf("page_index", "%s", "○○●○"); // 3/4
else if (page=="buttonpage04" or page=="entitypage04") id(disp1).set_component_text_printf("page_index", "%s", "○○○●"); // 4/4
else if (page=="settings")
{
//id(disp1).set_component_text_printf("bt_sleep", "%s", (id(sleep_mode).state) ? "\uEA19" : "\uEA18"); //mdi:toggle-switch-outline or mdi:toggle-switch-off-outline
@@ -1665,11 +1670,13 @@ display:
uart_id: tf_uart
tft_url: ${nextion_update_url}
on_page: # I couldn't make this trigger to work, so used text_sensor nspanelevent and localevent instead
- lambda: ESP_LOGW("display.disp1", "NEXTION PAGE CHANGED");
then:
- lambda: ESP_LOGW("display.disp1", "NEXTION PAGE CHANGED");
on_setup:
then:
- lambda: |-
id(disp1).goto_page("boot");
id(disp1).send_command_printf("bkcmd=3");
id(disp1).set_component_text_printf("boot.esph_version", "%s", "${version}"); // ### esphome-version ###
id(disp1).show_component("bt_reboot");
id(timer_reset_all).execute("boot");
@@ -2274,7 +2281,402 @@ script:
else if (id(wakeup_page_name).state == "alarm") wakeup_page_id = 23;
id(disp1).set_component_value("orign", wakeup_page_id);
##### ADD-ONS ############################################################
- id: upload_tft
mode: single
parameters:
url: string
then:
- lambda: |-
ESP_LOGD("script.upload_tft", "Starting...");
std::vector<uint8_t> buffer_;
bool is_updating_ = false;
uint8_t *transfer_buffer_{nullptr};
size_t transfer_buffer_size_;
bool upload_first_chunk_sent_ = false;
int content_length_ = 0;
int tft_size_ = 0;
bool power_cycle_display = []()
{
id(screen_power).turn_off();
delay(1500);
id(screen_power).turn_on();
delay(1500);
return true;
};
auto send_nextion_command = [](const std::string &command) -> bool
{
ESP_LOGD("script.upload_tft.send_nextion_command", "Sending: %s", command.c_str());
id(tf_uart).write_str(command.c_str());
const uint8_t to_send[3] = {0xFF, 0xFF, 0xFF};
id(tf_uart).write_array(to_send, sizeof(to_send));
return true;
};
auto recv_ret_string_ = [](std::string &response, uint32_t timeout, bool recv_flag) -> uint16_t
{
uint16_t ret;
uint8_t c = 0;
uint8_t nr_of_ff_bytes = 0;
uint64_t start;
bool exit_flag = false;
bool ff_flag = false;
start = millis();
while ((timeout == 0 && id(tf_uart).available()) || millis() - start <= timeout)
{
if (!id(tf_uart).available())
{
App.feed_wdt();
continue;
}
id(tf_uart).read_byte(&c);
if (c == 0xFF)
{
nr_of_ff_bytes++;
}
else
{
nr_of_ff_bytes = 0;
ff_flag = false;
}
if (nr_of_ff_bytes >= 3)
ff_flag = true;
response += (char) c;
if (recv_flag)
{
if (response.find(0x05) != std::string::npos)
{
exit_flag = true;
}
}
App.feed_wdt();
delay(2);
if (exit_flag || ff_flag)
{
break;
}
}
if (ff_flag)
response = response.substr(0, response.length() - 3); // Remove last 3 0xFF
ret = response.length();
return ret;
};
auto upload_end_ = [&](bool retry) -> bool
{
ESP_LOGD("script.upload_tft.upload_end_", "Restarting Nextion");
send_nextion_command("rest");
if (is_updating_) is_updating_ = not retry;
if (retry)
{
ESP_LOGD("script.upload_tft.upload_end_", "Nextion TFT upload will try again");
send_nextion_command("rest");
delay(1500);
}
else
{
ESP_LOGD("script.upload_tft.upload_end_", "Turn off Nextion");
//id(screen_power).turn_off();
delay(1500);
ESP_LOGD("script.upload_tft.upload_end_", "Restarting esphome");
ESP.restart();
}
return not retry;
};
auto upload_by_chunks_ = [&](HTTPClient *http, const std::string &url, int range_start) -> int
{
int range_end;
if (range_start == 0 && transfer_buffer_size_ > 16384) { // Start small at the first run in case of a big skip
range_end = 16384 - 1;
} else {
range_end = range_start + transfer_buffer_size_ - 1;
}
if (range_end > tft_size_)
range_end = tft_size_;
char range_header[64];
sprintf(range_header, "bytes=%d-%d", range_start, range_end);
ESP_LOGD("script.upload_tft.upload_by_chunks", "Requesting range: %s", range_header);
int tries = 1;
int code;
bool begin_status;
while (tries <= 5) {
begin_status = http->begin(url.c_str());
++tries;
if (!begin_status) {
ESP_LOGD("script.upload_tft.upload_by_chunks", "upload_by_chunks_: connection failed");
continue;
};
http->addHeader("Range", range_header);
code = http->GET();
if (code == 200 || code == 206) {
break;
}
ESP_LOGW("script.upload_tft.upload_by_chunks", "HTTP Request failed; URL: %s; Error: %s, retries(%d/5)", url.c_str(),
HTTPClient::errorToString(code).c_str(), tries);
http->end();
delay(500);
}
if (tries > 5) {
return -1;
}
std::string recv_string;
size_t size;
int fetched = 0;
int range = range_end - range_start;
int write_len;
// fetch next segment from HTTP stream
while (fetched < range) {
size = http->getStreamPtr()->available();
if (!size) {
App.feed_wdt();
delay(2);
continue;
}
int c = http->getStreamPtr()->readBytes(
&transfer_buffer_[fetched], ((size > transfer_buffer_size_) ? transfer_buffer_size_ : size));
fetched += c;
}
http->end();
ESP_LOGD("script.upload_tft.upload_by_chunks", "fetched %d bytes", fetched);
// upload fetched segments to the display in 4KB chunks
for (int i = 0; i < range; i += 4096) {
App.feed_wdt();
write_len = content_length_ < 4096 ? content_length_ : 4096;
id(tf_uart).write_array(&transfer_buffer_[i], write_len);
content_length_ -= write_len;
ESP_LOGD("script.upload_tft.upload_by_chunks", "Uploaded %0.1f %%, remaining %d Bytes",
100.0 * (tft_size_ - content_length_) / tft_size_, content_length_);
if (!upload_first_chunk_sent_) {
upload_first_chunk_sent_ = true;
delay(500);
}
recv_ret_string_(recv_string, 5000, true);
if (recv_string[0] != 0x05) { // 0x05 == "ok"
ESP_LOGD("script.upload_tft.upload_by_chunks", "recv_string [%s]",
format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
}
// handle partial upload request
if (recv_string[0] == 0x08 && recv_string.size() == 5) {
uint32_t result = 0;
for (int j = 0; j < 4; ++j) {
result += static_cast<uint8_t>(recv_string[j + 1]) << (8 * j);
}
if (result > 0) {
ESP_LOGD("script.upload_tft.upload_by_chunks", "Nextion reported new range %d", result);
content_length_ = tft_size_ - result;
return result;
}
}
recv_string.clear();
}
return range_end + 1;
};
auto upload_tft = [&](const std::string &url, unsigned int update_baud_rate_) -> bool
{
ESP_LOGD("script.upload_tft.upload_tft", "Nextion TFT upload requested");
ESP_LOGD("script.upload_tft.upload_tft", "url: %s", url.c_str());
ESP_LOGD("script.upload_tft.upload_tft", "baud_rate: %i", update_baud_rate_);
if (is_updating_) {
ESP_LOGD("script.upload_tft.upload_tft", "Currently updating");
return upload_end_(false);
}
if (!network::is_connected()) {
ESP_LOGD("script.upload_tft.upload_tft", "network is not connected");
return upload_end_(false);
}
send_nextion_command("DRAKJHSUYDGBNCJHGJKSHBDN");
send_nextion_command("recmod=0");
send_nextion_command("recmod=0");
send_nextion_command("connect");
is_updating_ = true;
HTTPClient http;
http.setTimeout(15000); // Yes 15 seconds.... Helps 8266s along
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
bool begin_status = http.begin(url.c_str());
if (!begin_status) {
is_updating_ = false;
ESP_LOGD("script.upload_tft.upload_tft", "connection failed");
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
allocator.deallocate(transfer_buffer_, transfer_buffer_size_);
return upload_end_(true);
} else {
ESP_LOGD("script.upload_tft.upload_tft", "Connected");
}
http.addHeader("Range", "bytes=0-255");
const char *header_names[] = {"Content-Range"};
http.collectHeaders(header_names, 1);
ESP_LOGD("script.upload_tft.upload_tft", "Requesting URL: %s", url.c_str());
http.setReuse(true);
// try up to 5 times. DNS sometimes needs a second try or so
int tries = 1;
int code = http.GET();
delay(100);
while (code != 200 && code != 206 && tries <= 5) {
ESP_LOGW("script.upload_tft.upload_tft", "HTTP Request failed; URL: %s; Error: %s, retrying (%d/5)", url.c_str(),
HTTPClient::errorToString(code).c_str(), tries);
delay(250);
code = http.GET();
++tries;
}
if ((code != 200 && code != 206) || tries > 5) {
return upload_end_(true);
}
String content_range_string = http.header("Content-Range");
content_range_string.remove(0, 12);
content_length_ = content_range_string.toInt();
tft_size_ = content_length_;
http.end();
if (content_length_ < 4096) {
ESP_LOGE("script.upload_tft.upload_tft", "Failed to get file size");
return upload_end_(true);
}
ESP_LOGD("script.upload_tft.upload_tft", "Updating Nextion");
// The Nextion will ignore the update command if it is sleeping
char command[128];
// Tells the Nextion the content length of the tft file and baud rate it will be sent at
// Once the Nextion accepts the command it will wait until the file is successfully uploaded
// If it fails for any reason a power cycle of the display will be needed
sprintf(command, "whmi-wris %d,%d,1", content_length_, update_baud_rate_);
// Clear serial receive buffer
uint8_t d;
while (id(tf_uart).available()) {
id(tf_uart).read_byte(&d);
};
send_nextion_command(command);
if (update_baud_rate_ != id(tf_uart).get_baud_rate())
{
id(tf_uart).set_baud_rate(update_baud_rate_);
id(tf_uart).setup();
}
std::string response;
ESP_LOGD("script.upload_tft.upload_tft", "Waiting for upgrade response");
recv_ret_string_(response, 15000, true); // This can take some time to return
// The Nextion display will, if it's ready to accept data, send a 0x05 byte.
ESP_LOGD("script.upload_tft.upload_tft", "Upgrade response is [%s]",
format_hex_pretty(reinterpret_cast<const uint8_t *>(response.data()), response.size()).c_str());
if (response.find(0x05) != std::string::npos) {
ESP_LOGD("script.upload_tft.upload_tft", "preparation for tft update done");
} else {
ESP_LOGD("script.upload_tft.upload_tft", "preparation for tft update failed %d \"%s\"", response[0], response.c_str());
return upload_end_(true);
}
// Nextion wants 4096 bytes at a time. Make chunk_size a multiple of 4096
uint32_t chunk_size = 8192;
if (ESP.getFreeHeap() > 40960) { // 32K to keep on hand
chunk_size = ESP.getFreeHeap() - 32768;
chunk_size = chunk_size > 65536 ? 65536 : chunk_size;
} else if (ESP.getFreeHeap() < 10240) {
chunk_size = 4096;
}
if (transfer_buffer_ == nullptr) {
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
ESP_LOGD("script.upload_tft.upload_tft", "Allocating buffer size %d, Heap size is %u", chunk_size, ESP.getFreeHeap());
transfer_buffer_ = allocator.allocate(chunk_size);
if (transfer_buffer_ == nullptr) { // Try a smaller size
ESP_LOGD("script.upload_tft.upload_tft", "Could not allocate buffer size: %d trying 4096 instead", chunk_size);
chunk_size = 4096;
ESP_LOGD("script.upload_tft.upload_tft", "Allocating %d buffer", chunk_size);
transfer_buffer_ = allocator.allocate(chunk_size);
if (!transfer_buffer_)
{
return upload_end_(true);
}
}
transfer_buffer_size_ = chunk_size;
}
ESP_LOGD("script.upload_tft.upload_tft", "Updating tft from \"%s\" with a file size of %d using %zu chunksize, Heap Size %d",
url.c_str(), content_length_, transfer_buffer_size_, ESP.getFreeHeap());
int result = 0;
while (content_length_ > 0) {
result = upload_by_chunks_(&http, url, result);
if (result < 0) {
ESP_LOGD("script.upload_tft.upload_tft", "Error updating Nextion!");
return upload_end_(true);
}
App.feed_wdt();
ESP_LOGD("script.upload_tft.upload_tft", "Heap Size %d, Bytes left %d", ESP.getFreeHeap(), content_length_);
}
is_updating_ = false;
ESP_LOGD("script.upload_tft.upload_tft", "Successfully updated Nextion!");
return upload_end_(false);
};
if (upload_tft(url, id(tf_uart).get_baud_rate())) ESP.restart();
power_cycle_display;
if (upload_tft(url, 921600)) ESP.restart();
power_cycle_display;
if (upload_tft(url, 9600)) ESP.restart();
power_cycle_display;
ESP_LOGE("script.upload_tft", "TFT upload failed.");
ESP_LOGW("script.upload_tft", "Trying Nextion standard upload");
id(disp1)->set_tft_url(url.c_str());
id(disp1)->upload_tft();
ESP_LOGD("script.upload_tft.upload_end_", "Restarting esphome");
ESP.restart();
ESP_LOGD("script.upload_tft", "Finished!");
##### ADD-ONS ############################################################
##### Add-on - Climate #####
- id: addon_climate_service_call
mode: restart