From 02d53557c4407d26e1fd36e39036b9300374c4ba Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:37:58 +0200 Subject: [PATCH] Add HostedMCU file update using command `HostedLoad |` - Increased filesystem file name size from 48 to 50 characters --- CHANGELOG.md | 2 + RELEASENOTES.md | 2 + .../WiFiHelper/src/WiFiHelper_ESP32.cpp | 8 +- tasmota/include/i18n.h | 1 + .../xdrv_50_filesystem.ino | 12 +- .../xdrv_84_esp32_hosted.ino | 171 +++++++++++++++--- 6 files changed, 156 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb2a68881..770b559fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,14 @@ All notable changes to this project will be documented in this file. - TLS enabled ECDSA by default for ESP8266 (#24009) - Berry `cb.free_cb` for extension manager (#24014) - Berry `light.get()` direct access to values (#24033) +- HostedMCU file update using command `HostedLoad |` ### Breaking Changed ### Changed - Refactored library UDisplay (#24007) - LVGL library from v9.3.0 to v9.4.0 (#24028) +- Increased filesystem file name size from 48 to 50 characters ### Fixed - TLS fix ECDSA and add `SetOption165 1` to enable ECDSA in addition to RSA (#24000) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9d88b6dab..c86d8d5c9 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -114,6 +114,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm ## Changelog v15.1.0.1 ### Added +- HostedMCU file update using command `HostedLoad |` - TLS enabled ECDSA by default for ESP8266 [#24009](https://github.com/arendst/Tasmota/issues/24009) - Berry `cb.free_cb` for extension manager [#24014](https://github.com/arendst/Tasmota/issues/24014) - Berry `light.get()` direct access to values [#24033](https://github.com/arendst/Tasmota/issues/24033) @@ -123,6 +124,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm ### Changed - LVGL library from v9.3.0 to v9.4.0 [#24028](https://github.com/arendst/Tasmota/issues/24028) - Refactored library UDisplay [#24007](https://github.com/arendst/Tasmota/issues/24007) +- Increased filesystem file name size from 48 to 50 characters ### Fixed - InfluxDb receives IPAddress as a value regression from v15.0.1.3 [#24031](https://github.com/arendst/Tasmota/issues/24031) diff --git a/lib/default/WiFiHelper/src/WiFiHelper_ESP32.cpp b/lib/default/WiFiHelper/src/WiFiHelper_ESP32.cpp index 8c66211d6..670b3fc88 100644 --- a/lib/default/WiFiHelper/src/WiFiHelper_ESP32.cpp +++ b/lib/default/WiFiHelper/src/WiFiHelper_ESP32.cpp @@ -166,7 +166,7 @@ int WiFiHelper::getPhyMode() { WIFI_PHY_MODE_HE20, // PHY mode for Bandwidth HE20 (11ax) } wifi_phy_mode_t; */ - #ifndef SOC_WIFI_SUPPORTED +#ifndef SOC_WIFI_SUPPORTED // ESP32-P4 does not support PHY modes, return 0 return 0; #else @@ -179,13 +179,13 @@ int WiFiHelper::getPhyMode() { } } return phy_mode; -# endif +#endif } bool WiFiHelper::setPhyMode(WiFiPhyMode_t mode) { -# ifndef SOC_WIFI_SUPPORTED +#ifndef SOC_WIFI_SUPPORTED return false; // ESP32-P4 does not support PHY modes -# else +#else uint8_t protocol_bitmap = WIFI_PROTOCOL_11B; // 1 switch (mode) { #if ESP_IDF_VERSION_MAJOR >= 5 diff --git a/tasmota/include/i18n.h b/tasmota/include/i18n.h index 1082c29bf..5e7e846e4 100644 --- a/tasmota/include/i18n.h +++ b/tasmota/include/i18n.h @@ -334,6 +334,7 @@ #define D_JSON_ONE_OR_GT "1 or >%s to upgrade" #define D_CMND_OTAURL "OtaUrl" #define D_CMND_HOSTEDOTA "HostedOta" +#define D_CMND_HOSTEDLOAD "HostedLoad" #define D_CMND_SERIALLOG "SerialLog" #define D_CMND_SYSLOG "SysLog" #define D_CMND_FILELOG "FileLog" diff --git a/tasmota/tasmota_xdrv_driver/xdrv_50_filesystem.ino b/tasmota/tasmota_xdrv_driver/xdrv_50_filesystem.ino index 403625c26..137950fd7 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_50_filesystem.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_50_filesystem.ino @@ -87,6 +87,8 @@ ftp start stop ftp server: 0 = OFF, 1 = SDC, 2 = FlashFile #endif // ESP32 */ +const int UFS_FILENAME_SIZE = 50; + // Global file system pointer FS *ufsp; // Flash file system pointer @@ -94,7 +96,7 @@ FS *ffsp; // Local pointer for file managment FS *dfsp; -char ufs_path[48]; +char ufs_path[UFS_FILENAME_SIZE]; File ufs_upload_file; uint8_t ufs_dir; // 0 = None, 1 = SD, 2 = ffat, 3 = littlefs @@ -104,7 +106,7 @@ uint8_t ffs_type; uint8_t sd_type; struct { - char run_file[48]; + char run_file[UFS_FILENAME_SIZE]; int run_file_pos = -1; bool run_file_mutex = 0; bool download_busy; @@ -916,8 +918,6 @@ String UfsJsonSettingsRead(const char* key) { * Commands \*********************************************************************************************/ -const int UFS_FILENAME_SIZE = 48; - char* UfsFilename(char* fname, char* fname_in) { fname_in = Trim(fname_in); // Remove possible leading spaces snprintf_P(fname, UFS_FILENAME_SIZE, PSTR("%s%s"), ('/' == fname_in[0]) ? "" : "/", fname_in); @@ -1469,7 +1469,7 @@ void UfsDirectory(void) { } void UfsListDir(char *path, uint8_t depth) { - char name[48]; + char name[UFS_FILENAME_SIZE]; char npath[128]; char format[12]; sprintf(format, PSTR("%%-%ds"), 24 - depth); @@ -1712,7 +1712,7 @@ void download_task(void *path) { bool UfsUploadFileOpen(const char* upload_filename) { - char npath[48]; + char npath[UFS_FILENAME_SIZE]; snprintf_P(npath, sizeof(npath), PSTR("%s/%s"), ufs_path, upload_filename); dfsp->remove(npath); ufs_upload_file = dfsp->open(npath, UFS_FILE_WRITE); diff --git a/tasmota/tasmota_xdrv_driver/xdrv_84_esp32_hosted.ino b/tasmota/tasmota_xdrv_driver/xdrv_84_esp32_hosted.ino index b766b5efc..cb9a67ac2 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_84_esp32_hosted.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_84_esp32_hosted.ino @@ -29,8 +29,9 @@ extern "C" { enum EspHostTypes { ESP_HOST, ESP_HOSTED }; struct Hosted_t { - char *hosted_ota_url; // Hosted MCU OTA URL - int hosted_ota_state_flag; // Hosted MCU OTA initiated flag + char *ota_url; // Hosted MCU OTA URL + int ota_http_state_flag; // Hosted MCU OTA HTTP initiated flag + int ota_file_state_flag; // Hosted MCU OTA File initiated flag } Hosted; /*********************************************************************************************/ @@ -107,22 +108,94 @@ void HostedMCUStatus(void) { \*********************************************************************************************/ void HostedMCUEverySecond(void) { - if (Hosted.hosted_ota_state_flag && CommandsReady()) { - Hosted.hosted_ota_state_flag--; + if (!CommandsReady()) { return; } + +#ifdef ESP_HOSTED_NEW_OTA + if (Hosted.ota_file_state_flag) { + Hosted.ota_file_state_flag--; + if (Hosted.ota_file_state_flag <= 0) { + AddLog(LOG_LEVEL_DEBUG, PSTR("HST: File update %s"), Hosted.ota_url); + int ret = -1; + // Blocking + + if (!ffsp) { + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("HST: No file system")); + } else { + File file = ffsp->open(Hosted.ota_url, "r"); + if (!file) { + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("HST: File not found")); + } else { + // Start load on coprocessor + if ((ret = esp_hosted_slave_ota_begin()) != ESP_OK) { + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("HST: ota_begin failed %d"), ret); + file.close(); + } else { + const size_t bufSize = 1024; + uint8_t *buf = (uint8_t*)malloc(bufSize); + if (buf) { + int read; + bool write_ok = true; + int total_size = 0; + while ((read = file.read(buf, bufSize)) > 0) { + total_size += read; + if ((ret = esp_hosted_slave_ota_write(buf, (uint32_t)read)) != ESP_OK) { + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("HST: ota_write failed %d"), ret); + write_ok = false; + break; + } + if (total_size % 102400 == 0) { + AddLog(LOG_LEVEL_DEBUG, PSTR("HST: Progress %d KB"), total_size / 1024); + } + delay(1); + OsWatchLoop(); + } + free(buf); + file.close(); + if (write_ok) { + AddLog(LOG_LEVEL_DEBUG, PSTR("HST: Successful %d bytes"), total_size); + if ((ret = esp_hosted_slave_ota_end()) != ESP_OK) { + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("HST: ota_end failed %d"), ret); + } else { + // Activate will likely reboot the slave + ret = esp_hosted_slave_ota_activate(); + } + } + } + } + } + } + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("HST: Done with result %d"), ret); + free(Hosted.ota_url); + Hosted.ota_url = nullptr; + Response_P(PSTR("{\"" D_CMND_HOSTEDLOAD "\":\"")); + if (ret == ESP_OK) { + // next lines are questionable, because currently the system will reboot immediately on succesful upgrade + ResponseAppend_P(PSTR(D_JSON_SUCCESSFUL ". " D_JSON_RESTARTING)); + TasmotaGlobal.restart_flag = 5; // Allow time for webserver to update console + } else { + ResponseAppend_P(PSTR(D_JSON_FAILED " %d\"}"), ret); + } + ResponseAppend_P(PSTR("\"}")); + MqttPublishPrefixTopicRulesProcess_P(STAT, PSTR(D_CMND_HOSTEDLOAD)); + } + } +#endif // ESP_HOSTED_NEW_OTA + if (Hosted.ota_http_state_flag) { + Hosted.ota_http_state_flag--; /* - if (2 == Hosted.hosted_ota_state_flag) { + if (2 == Hosted.ota_http_state_flag) { SettingsSave(0); } */ - if (Hosted.hosted_ota_state_flag <= 0) { - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("HST: About to OTA update with %s"), Hosted.hosted_ota_url); + if (Hosted.ota_http_state_flag <= 0) { + AddLog(LOG_LEVEL_DEBUG, PSTR("HST: OTA update %s"), Hosted.ota_url); int ret = -1; // Blocking #ifdef ESP_HOSTED_NEW_OTA AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("HST: Using streaming OTA API")); HTTPClientLight http; - if (!http.begin(Hosted.hosted_ota_url)) { + if (!http.begin(Hosted.ota_url)) { AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("HST: HTTP begin failed")); ret = -1; } else { @@ -148,23 +221,24 @@ void HostedMCUEverySecond(void) { WiFiClient& stream = http.getStream(); int read; bool write_ok = true; - int chunk_count = 0; + int total_size = 0; while ((read = stream.readBytes((char*)buf, bufSize)) > 0) { + total_size += read; if ((ret = esp_hosted_slave_ota_write(buf, (uint32_t)read)) != ESP_OK) { AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("HST: ota_write failed %d"), ret); write_ok = false; break; } - chunk_count++; - if (chunk_count % 100 == 0) { - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("HST: ota_write %dK"), chunk_count); + if (total_size % 102400 == 0) { + AddLog(LOG_LEVEL_DEBUG, PSTR("HST: Progress %d KB"), total_size / 1024); } - yield(); + delay(1); + OsWatchLoop(); } free(buf); http.end(); - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("HST: ota_write done")); if (write_ok) { + AddLog(LOG_LEVEL_DEBUG, PSTR("HST: Successful %d bytes"), total_size); if ((ret = esp_hosted_slave_ota_end()) != ESP_OK) { AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("HST: ota_end failed %d"), ret); } else { @@ -178,12 +252,12 @@ void HostedMCUEverySecond(void) { } #else // OLD_OTA AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("HST: Using legacy OTA API")); - ret = esp_hosted_slave_ota(Hosted.hosted_ota_url); + ret = esp_hosted_slave_ota(Hosted.ota_url); #endif // ESP_HOSTED_NEW_OTA AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("HST: Done with result %d"), ret); - free(Hosted.hosted_ota_url); - Hosted.hosted_ota_url = nullptr; + free(Hosted.ota_url); + Hosted.ota_url = nullptr; Response_P(PSTR("{\"" D_CMND_HOSTEDOTA "\":\"")); if (ret == ESP_OK) { // next lines are questionable, because currently the system will reboot immediately on succesful upgrade @@ -203,10 +277,18 @@ void HostedMCUEverySecond(void) { \*********************************************************************************************/ const char kHostedCommands[] PROGMEM = "Hosted|" // Prefix - "|Ota"; + "|" +#ifdef ESP_HOSTED_NEW_OTA + "Load|" +#endif // ESP_HOSTED_NEW_OTA + "Ota"; void (* const HostedCommand[])(void) PROGMEM = { - &CmndHosted, &CmndHostedOta }; + &CmndHosted, +#ifdef ESP_HOSTED_NEW_OTA + &CmndHostedLoad, +#endif // ESP_HOSTED_NEW_OTA + &CmndHostedOta }; void CmndHosted(void) { Response_P(PSTR("{\"Hosted\":{\"Host\":\"%s\",\"Hosted\":\"%s\",\"MCU\":\"%s\"}}"), @@ -214,6 +296,35 @@ void CmndHosted(void) { ); } +#ifdef ESP_HOSTED_NEW_OTA +void CmndHostedLoad(void) { + /* + Expect files in folder /coprocessor/v2.0.17/network_adapter_esp32c6.bin + As an option allow user to enter file like: + HostedLoad /coprocessor/network_adapter_esp32c6.bin + Or allow user to enter required version like: + HostedLoad v2.0.17 + */ + if (GetHostedMCUFwVersion() < 0x00020600) { return; } + + Hosted.ota_url = (char*)calloc(200, sizeof(char)); + if (!Hosted.ota_url) { return; } // Unable to allocate memory + if (XdrvMailbox.data_len > 15) { + strlcpy(Hosted.ota_url, XdrvMailbox.data, 200); + } else { + char version[16] = { 0 }; + if (XdrvMailbox.data_len) { + snprintf_P(version, sizeof(version), PSTR("/%s"), XdrvMailbox.data); + } + snprintf_P(Hosted.ota_url, 200, PSTR("/coprocessor%s/network_adapter_" CONFIG_ESP_HOSTED_IDF_SLAVE_TARGET ".bin"), + version); + } + Hosted.ota_file_state_flag = 1; + Response_P(PSTR("{\"%s\":\"" D_JSON_VERSION " %s " D_JSON_FROM " %s\"}"), + XdrvMailbox.command, GetHostedFwVersion(ESP_HOSTED).c_str(), Hosted.ota_url); +} +#endif // ESP_HOSTED_NEW_OTA + void CmndHostedOta(void) { /* If OtaUrl = "https://ota.tasmota.com/tasmota32/tasmota32p4.bin" @@ -224,27 +335,27 @@ void CmndHostedOta(void) { Or allow user to enter required version like: HostedOta v2.0.17 */ - Hosted.hosted_ota_url = (char*)calloc(200, sizeof(char)); - if (!Hosted.hosted_ota_url) { return; } // Unable to allocate memory + Hosted.ota_url = (char*)calloc(200, sizeof(char)); + if (!Hosted.ota_url) { return; } // Unable to allocate memory if (XdrvMailbox.data_len > 15) { - strlcpy(Hosted.hosted_ota_url, XdrvMailbox.data, 200); + strlcpy(Hosted.ota_url, XdrvMailbox.data, 200); } else { // Replace https://ota.tasmota.com/tasmota32/tasmota32p4.bin with https://ota.tasmota.com/tasmota32/coprocessor/network_adapter_esp32c6.bin char ota_url[TOPSZ]; - strlcpy(Hosted.hosted_ota_url, GetOtaUrl(ota_url, sizeof(ota_url)), 200); - char *bch = strrchr(Hosted.hosted_ota_url, '/'); // Only consider filename after last backslash - if (bch == nullptr) { bch = Hosted.hosted_ota_url; } // No path found so use filename only - *bch = '\0'; // full_ota_url = https://ota.tasmota.com/tasmota32 + strlcpy(Hosted.ota_url, GetOtaUrl(ota_url, sizeof(ota_url)), 200); + char *bch = strrchr(Hosted.ota_url, '/'); // Only consider filename after last backslash + if (bch == nullptr) { bch = Hosted.ota_url; } // No path found so use filename only + *bch = '\0'; // full_ota_url = https://ota.tasmota.com/tasmota32 char version[16] = { 0 }; if (XdrvMailbox.data_len) { snprintf_P(version, sizeof(version), PSTR("/%s"), XdrvMailbox.data); } - snprintf_P(Hosted.hosted_ota_url, 200, PSTR("%s/coprocessor%s/network_adapter_" CONFIG_ESP_HOSTED_IDF_SLAVE_TARGET ".bin"), - Hosted.hosted_ota_url, version); + snprintf_P(Hosted.ota_url, 200, PSTR("%s/coprocessor%s/network_adapter_" CONFIG_ESP_HOSTED_IDF_SLAVE_TARGET ".bin"), + Hosted.ota_url, version); } - Hosted.hosted_ota_state_flag = 1; + Hosted.ota_http_state_flag = 1; Response_P(PSTR("{\"%s\":\"" D_JSON_VERSION " %s " D_JSON_FROM " %s\"}"), - XdrvMailbox.command, GetHostedFwVersion(ESP_HOSTED).c_str(), Hosted.hosted_ota_url); + XdrvMailbox.command, GetHostedFwVersion(ESP_HOSTED).c_str(), Hosted.ota_url); } /*********************************************************************************************\