From 4b667d0039a335de919867fd3c7b75573ddea41e Mon Sep 17 00:00:00 2001 From: Skip Hansen Date: Sat, 25 Jan 2025 14:11:39 -0800 Subject: [PATCH] Coprocessor OTA changes (#425) OTA changes to support C6/H2 OTA updates from configured repo. --- .github/workflows/release.yml | 62 +++-- ESP32_AP-Flasher/data/www/index.html.gz | Bin 6524 -> 6526 bytes ESP32_AP-Flasher/data/www/main.css.gz | Bin 3479 -> 3443 bytes ESP32_AP-Flasher/data/www/main.js.gz | Bin 16164 -> 16074 bytes ESP32_AP-Flasher/data/www/ota.js.gz | Bin 5672 -> 6420 bytes ESP32_AP-Flasher/include/espflasher.h | 14 +- ESP32_AP-Flasher/platformio.ini | 15 +- ESP32_AP-Flasher/src/espflasher.cpp | 291 ++++++++++++++---------- ESP32_AP-Flasher/src/ota.cpp | 77 +++++-- ESP32_AP-Flasher/src/serialap.cpp | 80 +++++-- ESP32_AP-Flasher/src/web.cpp | 16 +- ESP32_AP-Flasher/wwwroot/index.html | 5 +- ESP32_AP-Flasher/wwwroot/main.css | 14 +- ESP32_AP-Flasher/wwwroot/main.js | 3 +- ESP32_AP-Flasher/wwwroot/ota.js | 274 ++++++++++++++-------- binaries/ESP32-C6/firmware_C6.json | 4 +- binaries/ESP32-H2/firmware_H2.json | 4 +- 17 files changed, 564 insertions(+), 295 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b51e4d3c..6d50e258 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,6 +5,12 @@ on: tags: - '*' +env: + INCLUDE_C6_H2: true + INCLUDE_MINI_AP: false + INCLUDE_Nano_AP: false + INCLUDE_S2_Tag_Flasher: false + jobs: build: runs-on: ubuntu-22.04 @@ -41,30 +47,37 @@ jobs: run: | mkdir espbinaries - #- name: esp-idf build - # uses: espressif/esp-idf-ci-action@v1 - # with: - # esp_idf_version: latest - # target: esp32c6 - # path: 'ARM_Tag_FW/OpenEPaperLink_esp32_C6_AP/' + - name: build ESP32-C6 firmware + if: ${{ env.INCLUDE_C6_H2 == 'true' }} + uses: espressif/esp-idf-ci-action@v1 + with: + esp_idf_version: latest + target: esp32c6 + path: 'ARM_Tag_FW/OpenEPaperLink_esp32_C6_AP/' - # - name: esp-idf build - # uses: espressif/esp-idf-ci-action@v1 - # with: - # esp_idf_version: latest - # target: esp32h2 - # path: 'ARM_Tag_FW/OpenEPaperLink_esp32_H2_AP/' + - name: Add C6 files to release + if: ${{ env.INCLUDE_C6_H2 == 'true' }} + run: | + cp /home/runner/work/OpenEPaperLink/OpenEPaperLink/ARM_Tag_FW/OpenEPaperLink_esp32_C6_AP/build/OpenEPaperLink_esp32_C6.bin espbinaries/OpenEPaperLink_esp32_C6.bin + cp /home/runner/work/OpenEPaperLink/OpenEPaperLink/ARM_Tag_FW/OpenEPaperLink_esp32_C6_AP/build/bootloader/bootloader.bin espbinaries/bootloader_C6.bin + cp /home/runner/work/OpenEPaperLink/OpenEPaperLink/ARM_Tag_FW/OpenEPaperLink_esp32_C6_AP/build/partition_table/partition-table.bin espbinaries/partition-table_C6.bin + cp /home/runner/work/OpenEPaperLink/OpenEPaperLink//binaries/ESP32-C6/firmware_C6.json espbinaries - #- name: Add C6 files to release - # run: | - # cp /home/runner/work/OpenEPaperLink/OpenEPaperLink/ARM_Tag_FW/OpenEPaperLink_esp32_C6_AP/build/OpenEPaperLink_esp32_C6.bin espbinaries/OpenEPaperLink_esp32_C6.bin + - name: build ESP32-H2 firmware + if: ${{ env.INCLUDE_C6_H2 == 'true' }} + uses: espressif/esp-idf-ci-action@v1 + with: + esp_idf_version: latest + target: esp32h2 + path: 'ARM_Tag_FW/OpenEPaperLink_esp32_H2_AP/' - #- name: Add H2 files to release - # run: | - # cd ARM_Tag_FW/OpenEPaperLink_esp32_H2_AP/ - # dir build - # esptool.py --chip esp32h2 merge_bin -o merged-firmware.bin --flash_mode dio --flash_size 4MB --flash_freq 48m 0x0 build/bootloader/bootloader.bin 0x8000 build/partition_table/partition-table.bin 0x10000 build/OpenEPaperLink_esp32_H2.bin - # cp merged-firmware.bin ../../espbinaries/OpenEPaperLink_esp32_H2.bin + - name: Add H2 files to release + if: ${{ env.INCLUDE_C6_H2 == 'true' }} + run: | + cp /home/runner/work/OpenEPaperLink/OpenEPaperLink/ARM_Tag_FW/OpenEPaperLink_esp32_H2_AP/build/OpenEPaperLink_esp32_H2.bin espbinaries/OpenEPaperLink_esp32_H2.bin + cp /home/runner/work/OpenEPaperLink/OpenEPaperLink/ARM_Tag_FW/OpenEPaperLink_esp32_H2_AP/build/bootloader/bootloader.bin espbinaries/bootloader_H2.bin + cp /home/runner/work/OpenEPaperLink/OpenEPaperLink/ARM_Tag_FW/OpenEPaperLink_esp32_H2_AP/build/partition_table/partition-table.bin espbinaries/partition-table_H2.bin + cp /home/runner/work/OpenEPaperLink/OpenEPaperLink//binaries/ESP32-H2/firmware_H2.json espbinaries # - name: Zip web files # run: | @@ -72,6 +85,7 @@ jobs: # python gzip_wwwfiles.py - name: Build firmware for OpenEPaperLink_Mini_AP + if: ${{ env.INCLUDE_MINI_AP == 'true' }} run: | cd ESP32_AP-Flasher export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA" @@ -90,6 +104,7 @@ jobs: cp OpenEPaperLink_Mini_AP/merged-firmware.bin espbinaries/OpenEPaperLink_Mini_AP_full.bin - name: Build firmware for OpenEPaperLink_Nano_AP + if: ${{ env.INCLUDE_Nano_AP == 'true' }} run: | cd ESP32_AP-Flasher export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA" @@ -106,7 +121,6 @@ jobs: cd /home/runner/work/OpenEPaperLink/OpenEPaperLink cp OpenEPaperLink_Nano_AP/firmware.bin espbinaries/OpenEPaperLink_Nano_AP.bin cp OpenEPaperLink_Nano_AP/merged-firmware.bin espbinaries/OpenEPaperLink_Nano_AP_full.bin - # - name: move files for big APs # run: | # cp -a binaries/ESP32-C6/. ESP32_AP-Flasher/data/ @@ -128,7 +142,6 @@ jobs: cd /home/runner/work/OpenEPaperLink/OpenEPaperLink cp OpenEPaperLink_AP_and_Flasher/firmware.bin espbinaries/OpenEPaperLink_AP_and_Flasher.bin cp OpenEPaperLink_AP_and_Flasher/merged-firmware.bin espbinaries/OpenEPaperLink_AP_and_Flasher_full.bin - - name: Build firmware for ESP32_S3_16_8_YELLOW_AP run: | cd ESP32_AP-Flasher @@ -254,7 +267,6 @@ jobs: cd /home/runner/work/OpenEPaperLink/OpenEPaperLink cp BLE_ONLY_AP/firmware.bin espbinaries/BLE_ONLY_AP.bin cp BLE_ONLY_AP/merged-firmware.bin espbinaries/BLE_ONLY_AP_full.bin - - name: generate release json file run: | mkdir jsonfiles @@ -272,6 +284,7 @@ jobs: # this is down here intentionally to be able to modify the binary folder before adding it to the Tag_Flasher later (ota binaries can be removed) - name: Build firmware for Tag_Flasher + if: ${{ env.INCLUDE_S2_Tag_Flasher == 'true' }} run: | cd Tag_Flasher/ESP32_Flasher export PLATFORMIO_BUILD_FLAGS="-D BUILD_VERSION=${{ github.ref_name }} -D SHA=$GITHUB_SHA" @@ -288,7 +301,6 @@ jobs: cd /home/runner/work/OpenEPaperLink/OpenEPaperLink cp S2_Tag_Flasher/firmware.bin espbinaries/S2_Tag_Flasher.bin cp S2_Tag_Flasher/merged-firmware.bin espbinaries/S2_Tag_Flasher_full.bin - - name: Add esp bins to release uses: svenstaro/upload-release-action@v2 with: diff --git a/ESP32_AP-Flasher/data/www/index.html.gz b/ESP32_AP-Flasher/data/www/index.html.gz index 2e56d8435831698c42b28dfb6367a55ff3b66c2f..0540a2651cdd41ac27d429e0a29786a38cc746f0 100644 GIT binary patch literal 6526 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 diff --git a/ESP32_AP-Flasher/data/www/main.css.gz b/ESP32_AP-Flasher/data/www/main.css.gz index 7f103aa00ea950997b78b5f9711568945c163777..9ba872243567fdc63864da24fba36d412de172d0 100644 GIT binary patch literal 3443 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 Vy-_!6ue7|r{TonBUYp=E002J4tx*5~ literal 3479 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 F007Gfr(Xa7 diff --git a/ESP32_AP-Flasher/data/www/main.js.gz b/ESP32_AP-Flasher/data/www/main.js.gz index 307f2eb1eea2a01f7550eefbc14a995eb3e8b830..ce140bfd0b554088f09ec1ce494139c1fb661ca9 100644 GIT binary patch literal 16074 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 Q3cW%2f0GqC47K_I069@tWdHyG literal 16164 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 C=7iz^ diff --git a/ESP32_AP-Flasher/data/www/ota.js.gz b/ESP32_AP-Flasher/data/www/ota.js.gz index 3802564ee311d7db485295c74f406637db7d945f..f3d27e17bac6efddf98ea1a45ae1d89323e65760 100644 GIT binary patch literal 6420 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

d{@_<8ah#j% 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 OrT+`DEN9sCWdH#2ECi4M diff --git a/ESP32_AP-Flasher/include/espflasher.h b/ESP32_AP-Flasher/include/espflasher.h index 4185705f..4fb9206b 100644 --- a/ESP32_AP-Flasher/include/espflasher.h +++ b/ESP32_AP-Flasher/include/espflasher.h @@ -1,4 +1,16 @@ #include #include -bool doC6flash(uint8_t doDownload); +#if defined HAS_H2 + #define SHORT_CHIP_NAME "H2" + #define OTA_BIN_DIR "ESP32-H2" + #define ESP_CHIP_TYPE ESP32H2_CHIP +#elif defined HAS_TSLR + #define SHORT_CHIP_NAME "TSLR" +#elif defined C6_OTA_FLASHING + #define SHORT_CHIP_NAME "C6" + #define OTA_BIN_DIR "ESP32-C6" + #define ESP_CHIP_TYPE ESP32C6_CHIP +#endif + +bool FlashC6_H2(const char *Url); diff --git a/ESP32_AP-Flasher/platformio.ini b/ESP32_AP-Flasher/platformio.ini index 28ab9db5..9fe5647b 100644 --- a/ESP32_AP-Flasher/platformio.ini +++ b/ESP32_AP-Flasher/platformio.ini @@ -180,24 +180,29 @@ build_flags = -D HAS_TFT -D HAS_LILYGO_TPANEL -D CORE_DEBUG_LEVEL=1 - -D ARDUINO_USB_CDC_ON_BOOT=1 + -D ARDUINO_USB_CDC_ON_BOOT=1 -D CONFIG_ESP32S3_SPIRAM_SUPPORT=1 -D CONFIG_SPIRAM_USE_MALLOC=1 -D POWER_NO_SOFT_POWER -D BOARD_HAS_PSRAM -D CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y -D HAS_BLE_WRITER - -D FLASHER_AP_SS=-1 + -D FLASHER_AP_SS=-1 -D FLASHER_AP_CLK=-1 -D FLASHER_AP_MOSI=-1 -D FLASHER_AP_MISO=-1 -D FLASHER_AP_RESET=34 -D FLASHER_AP_POWER={-1} -D FLASHER_AP_TEST=-1 +; NB: FLASHER_DEBUG_TXD and FLASHER_DEBUG_RXD use the same pins as +; FLASHER_AP_TXD and FLASHER_AP_RXD but the naming convention is different + -D FLASHER_DEBUG_SHARED + -D FLASHER_DEBUG_PORT=1 -D FLASHER_AP_TXD=48 + -D FLASHER_DEBUG_RXD=48 -D FLASHER_AP_RXD=47 - -D FLASHER_DEBUG_TXD=43 - -D FLASHER_DEBUG_RXD=44 + -D FLASHER_DEBUG_TXD=47 +; -D FLASHER_DEBUG_PROG=33 -D FLASHER_LED=-1 -D TFT_HEIGHT=480 @@ -207,6 +212,8 @@ build_flags = -D SERIAL_FLASHER_INTERFACE_UART=1 -D SERIAL_FLASHER_BOOT_HOLD_TIME_MS=200 -D SERIAL_FLASHER_RESET_HOLD_TIME_MS=200 + -D HAS_H2 + -D C6_OTA_FLASHING -D HAS_SUBGHZ build_src_filter = +<*>--- diff --git a/ESP32_AP-Flasher/src/espflasher.cpp b/ESP32_AP-Flasher/src/espflasher.cpp index 3d55df34..a3011583 100644 --- a/ESP32_AP-Flasher/src/espflasher.cpp +++ b/ESP32_AP-Flasher/src/espflasher.cpp @@ -9,6 +9,14 @@ #include "storage.h" #include "tag_db.h" #include "web.h" +#include "espflasher.h" +#include "util.h" + +#define LOG(format, ... ) Serial.printf(format,## __VA_ARGS__) + +#ifndef FLASHER_DEBUG_PORT +#define FLASHER_DEBUG_PORT 2 +#endif esp_loader_error_t connect_to_target(uint32_t higher_transmission_rate) { esp_loader_connect_args_t connect_config = ESP_LOADER_CONNECT_DEFAULT(); @@ -120,150 +128,197 @@ esp_loader_error_t flash_binary(String &file_path, size_t address) { bool downloadAndWriteBinary(String &filename, const char *url) { HTTPClient binaryHttp; - Serial.println(url); + bool Ret = false; + bool bHaveFsMutex = false; + + LOG("downloadAndWriteBinary: url %s\n",url); binaryHttp.begin(url); binaryHttp.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS); - int binaryResponseCode = binaryHttp.GET(); - Serial.println(binaryResponseCode); - if (binaryResponseCode == HTTP_CODE_OK) { + do { + int binaryResponseCode = binaryHttp.GET(); + if(binaryResponseCode != HTTP_CODE_OK) { + wsSerial("http error " + String(binaryResponseCode) + " fetching " + String(url)); + break; + } int contentLength = binaryHttp.getSize(); - Serial.println(contentLength); + LOG("contentLength %d\r\n",contentLength); + if(contentLength < 0) { + wsSerial("Couldn't get contentLength"); + break; + } xSemaphoreTake(fsMutex, portMAX_DELAY); + bHaveFsMutex = true; File file = contentFS->open(filename, "wb"); - if (file) { - wsSerial("downloading " + String(filename)); - WiFiClient *stream = binaryHttp.getStreamPtr(); - uint8_t buffer[1024]; - size_t totalBytesRead = 0; - time_t timeOut = millis() + 5000; - // while (stream->available()) { - while (millis() < timeOut) { - size_t bytesRead = stream->readBytes(buffer, sizeof(buffer)); + if(!file) { + wsSerial("file open error " + String(filename)); + break; + } + wsSerial("downloading " + String(filename)); + WiFiClient *stream = binaryHttp.getStreamPtr(); + uint8_t buffer[1024]; + size_t totalBytesRead = 0; + // timeout if we don't average at least 1k bytes/second + unsigned long timeOut = millis() + contentLength; + while(stream->connected() && totalBytesRead < contentLength) { + size_t bytesRead; + size_t bytesToRead; + if(stream->available()) { + bytesToRead = min(sizeof(buffer), (size_t) stream->available()); + bytesRead = stream->readBytes(buffer, bytesToRead); + if(bytesRead == 0 || millis() > timeOut) { + wsSerial("Download time out"); + break; + } file.write(buffer, bytesRead); totalBytesRead += bytesRead; - if (totalBytesRead == contentLength) break; + vTaskDelay(1 / portTICK_PERIOD_MS); + } else { + vTaskDelay(10 / portTICK_PERIOD_MS); } - file.close(); - xSemaphoreGive(fsMutex); - binaryHttp.end(); - - file = contentFS->open(filename, "r"); - if (file) { - if (totalBytesRead == contentLength || (contentLength == 0 && file.size() > 0)) { - file.close(); - return true; - } - wsSerial("Download failed, " + String(file.size()) + " bytes"); - file.close(); - } - } else { - xSemaphoreGive(fsMutex); - wsSerial("file open error " + String(filename)); } - } else { - wsSerial("http error " + String(binaryResponseCode) + " fetching " + String(url)); - } + file.close(); + + if(!stream->connected()) { + wsSerial("Connection dropped during transfer"); + break; + } + file = contentFS->open(filename, "r"); + if(!file) { + wsSerial("file open error " + String(filename)); + break; + } + if(file.size() == contentLength) { + Ret = true; + } else { + wsSerial("Download failed, " + String(file.size()) + " bytes"); + } + file.close(); + } while(false); + binaryHttp.setReuse(false); binaryHttp.end(); - return false; + if(bHaveFsMutex) { + xSemaphoreGive(fsMutex); + } + + return Ret; } -bool doC6flash(uint8_t doDownload) { - String filenameFirmwareLocal = "/firmware.json"; +bool FlashC6_H2(const char *RepoUrl) { + String JsonFilename = "/firmware_" SHORT_CHIP_NAME ".json" ; + bool Ret = false; + bool bLoaderInit = false; + bool bDownload = strlen(RepoUrl) > 0; + int retry; DynamicJsonDocument jsonDoc(1024); - if (doDownload) { - const String githubUrl = "https://raw.githubusercontent.com/" + config.repo + "/master/binaries/ESP32-C6/firmware.json"; - if (downloadAndWriteBinary(filenameFirmwareLocal, githubUrl.c_str())) { - File readfile = contentFS->open(filenameFirmwareLocal, "r"); - if (!readfile) { - Serial.println("load firmware.json: Failed to open file"); - return false; - } - DeserializationError jsonError = deserializeJson(jsonDoc, readfile); - if (!jsonError) { - JsonArray jsonArray = jsonDoc.as(); - for (JsonObject obj : jsonArray) { - String filename = "/" + obj["filename"].as(); - // String binaryUrl = "https://raw.githubusercontent.com/" + config.repo + "/master/binaries/ESP32-C6" + String(filename); - String binaryUrl = "http://www.openepaperlink.eu/binaries/ESP32-C6" + String(filename); - for (int retry = 0; retry < 10; retry++) { - if (downloadAndWriteBinary(filename, binaryUrl.c_str())) { - break; - } - wsSerial("Retry " + String(retry)); - if (retry < 9) { - delay(1000); - } else { - return false; - } - } - } - } else { - wsSerial("json error fetching " + String(githubUrl)); - return false; + LOG("%s#%d: ",__FUNCTION__,__LINE__); util::printHeap(); + + do { + if(bDownload) { + String FileUrl = RepoUrl + JsonFilename; + if(!downloadAndWriteBinary(JsonFilename, FileUrl.c_str())) { + LOG("%s#%d: ",__FUNCTION__,__LINE__); util::printHeap(); + break; } - } else { - return false; } - } else { - File readfile = contentFS->open(filenameFirmwareLocal, "r"); - if (!readfile) { - Serial.println("load local firmware.json: Failed to open file"); - return false; + + File readfile = contentFS->open(JsonFilename, "r"); + if(!readfile) { + Serial.println("load " + JsonFilename + ": Failed to open file"); + break; } DeserializationError jsonError = deserializeJson(jsonDoc, readfile); - } - const loader_esp32_config_t config = { - .baud_rate = 115200, - .uart_port = 2, - .uart_rx_pin = FLASHER_DEBUG_TXD, - .uart_tx_pin = FLASHER_DEBUG_RXD, - .reset_trigger_pin = FLASHER_AP_RESET, - .gpio0_trigger_pin = FLASHER_DEBUG_PROG, - }; + if(jsonError) { + wsSerial(String("json error parsing") + JsonFilename); + break; + } - if (loader_port_esp32_init(&config) != ESP_LOADER_SUCCESS) { - wsSerial("Serial initialization failed"); - loader_port_esp32_deinit(); - return false; - } + if(!bDownload) { + Ret = true; + break; + } - if (connect_to_target(115200) == ESP_LOADER_SUCCESS) { - if (esp_loader_get_target() == ESP32C6_CHIP) { - wsSerial("Connected to ESP32-C6"); - int maxRetries = 5; - esp_loader_error_t err; + JsonArray jsonArray = jsonDoc.as(); + for(JsonObject obj : jsonArray) { + String filename = "/" + obj["filename"].as(); + String binaryUrl = RepoUrl + filename; - JsonArray jsonArray = jsonDoc.as(); - for (JsonObject obj : jsonArray) { - String filename = "/" + obj["filename"].as(); - const char *addressStr = obj["address"]; - uint32_t address = strtoul(addressStr, NULL, 16); - - for (int retry = 0; retry < maxRetries; retry++) { - err = flash_binary(filename, address); - if (err == ESP_LOADER_SUCCESS) break; - Serial.printf("Flash failed with error %d. Retrying...\n", err); + for(retry = 0; retry < 10; retry++) { + if(downloadAndWriteBinary(filename, binaryUrl.c_str())) { + break; + } + wsSerial("Retry " + String(retry)); + if(retry < 9) { delay(1000); } - if (err != ESP_LOADER_SUCCESS) { - loader_port_esp32_deinit(); - return false; - } } - - Serial.println("Done!"); - } else { - wsSerial("Connected to wrong ESP32 type"); - loader_port_esp32_deinit(); - return false; + if(retry == 10) { + break; + } } - } else { - wsSerial("Connection to the C6 failed"); + if(retry < 10) { + Ret = true; + } + } while(false); + + if(Ret == true) do { + Ret = false; + const loader_esp32_config_t config = { + .baud_rate = 115200, + .uart_port = FLASHER_DEBUG_PORT, + .uart_rx_pin = FLASHER_DEBUG_TXD, + .uart_tx_pin = FLASHER_DEBUG_RXD, + .reset_trigger_pin = FLASHER_AP_RESET, + .gpio0_trigger_pin = FLASHER_DEBUG_PROG, + }; + + bLoaderInit = true; + if(loader_port_esp32_init(&config) != ESP_LOADER_SUCCESS) { + wsSerial("Serial initialization failed"); + break; + } + + if(connect_to_target(115200) != ESP_LOADER_SUCCESS) { + wsSerial("Connection to the " SHORT_CHIP_NAME " failed"); + break; + } + + if(esp_loader_get_target() != ESP_CHIP_TYPE) { + wsSerial("Connected to wrong ESP32 type"); + break; + } + wsSerial("Connected to ESP32-" SHORT_CHIP_NAME); + int maxRetries = 5; + esp_loader_error_t err; + + JsonArray jsonArray = jsonDoc.as(); + for(JsonObject obj : jsonArray) { + String filename = "/" + obj["filename"].as(); + const char *addressStr = obj["address"]; + uint32_t address = strtoul(addressStr, NULL, 16); + + for(int retry = 0; retry < maxRetries; retry++) { + err = flash_binary(filename, address); + if(err == ESP_LOADER_SUCCESS) { + Ret = true; + break; + } + Serial.printf("Flash failed with error %d. Retrying...\n", err); + delay(1000); + } + if(err != ESP_LOADER_SUCCESS) { + break; + } + } + Serial.println("Done!"); + } while(false); + + if(bLoaderInit) { loader_port_esp32_deinit(); - return false; } - loader_port_esp32_deinit(); - return true; + + LOG("%s#%d: ",__FUNCTION__,__LINE__); util::printHeap(); + return Ret; } + diff --git a/ESP32_AP-Flasher/src/ota.cpp b/ESP32_AP-Flasher/src/ota.cpp index 96ad558e..879575e6 100644 --- a/ESP32_AP-Flasher/src/ota.cpp +++ b/ESP32_AP-Flasher/src/ota.cpp @@ -7,6 +7,7 @@ #include #include +#include "flasher.h" #include "espflasher.h" #include "leds.h" #include "serialap.h" @@ -15,6 +16,7 @@ #include "util.h" #include "web.h" + #ifndef BUILD_ENV_NAME #define BUILD_ENV_NAME unknown #endif @@ -30,6 +32,8 @@ #define STR_IMPL(x) #x #define STR(x) STR_IMPL(x) +#define LOG(format, ... ) Serial.printf(format,## __VA_ARGS__) + void handleSysinfoRequest(AsyncWebServerRequest* request) { StaticJsonDocument<250> doc; @@ -41,12 +45,20 @@ void handleSysinfoRequest(AsyncWebServerRequest* request) { doc["psramsize"] = ESP.getPsramSize(); doc["flashsize"] = ESP.getFlashChipSize(); doc["rollback"] = Update.canRollBack(); -#if defined C6_OTA_FLASHING - doc["hasC6"] = 1; - doc["C6version"] = apInfo.version; -#else + doc["ap_version"] = apInfo.version; + doc["hasC6"] = 0; + doc["hasH2"] = 0; + doc["hasTslr"] = 0; + +#if defined HAS_H2 + doc["hasH2"] = 1; +#elif defined HAS_TSLR + doc["hasTslr"] = 1; +#elif defined C6_OTA_FLASHING + doc["hasC6"] = 1; #endif + #ifdef HAS_EXT_FLASHER doc["hasFlasher"] = 1; #else @@ -290,22 +302,26 @@ void handleRollback(AsyncWebServerRequest* request) { } } +#ifdef C6_OTA_FLASHING void C6firmwareUpdateTask(void* parameter) { - uint8_t doDownload = *((uint8_t*)parameter); + String *Url = reinterpret_cast(parameter); + LOG("C6firmwareUpdateTask: url '%s'\n",Url->c_str()); wsSerial("Stopping AP service"); setAPstate(false, AP_STATE_FLASHING); config.runStatus = RUNSTATUS_STOP; +#ifndef FLASHER_DEBUG_SHARED extern bool rxSerialStopTask2; rxSerialStopTask2 = true; +#endif vTaskDelay(500 / portTICK_PERIOD_MS); Serial1.end(); - wsSerial("C6 flash starting"); + wsSerial(SHORT_CHIP_NAME " flash starting"); - bool result = doC6flash(doDownload); + bool result = FlashC6_H2(Url->c_str()); - wsSerial("C6 flash end"); + wsSerial(SHORT_CHIP_NAME " flash end"); if (result) { setAPstate(false, AP_STATE_OFFLINE); @@ -315,12 +331,13 @@ void C6firmwareUpdateTask(void* parameter) { wsSerial("starting monitor"); Serial1.begin(115200, SERIAL_8N1, FLASHER_AP_RXD, FLASHER_AP_TXD); +#ifndef FLASHER_DEBUG_SHARED rxSerialStopTask2 = false; -#ifdef FLASHER_DEBUG_RXD xTaskCreate(rxSerialTask2, "rxSerialTask2", 1750, NULL, 2, NULL); #endif vTaskDelay(1000 / portTICK_PERIOD_MS); + apInfo.version = 0; wsSerial("resetting AP"); APTagReset(); vTaskDelay(1000 / portTICK_PERIOD_MS); @@ -328,23 +345,47 @@ void C6firmwareUpdateTask(void* parameter) { wsSerial("bringing AP online"); if (bringAPOnline()) config.runStatus = RUNSTATUS_RUN; - wsSerial("Finished!"); - } else { - wsSerial("Flashing failed. :-("); + // Wait for version info to arrive + vTaskDelay(50 / portTICK_PERIOD_MS); + if(apInfo.version == 0) { + result = false; + } + } + + if (result) { + wsSerial("Finished!"); + char buffer[50]; + snprintf(buffer,sizeof(buffer), + "ESP32-" SHORT_CHIP_NAME " version is now %04x",apInfo.version); + wsSerial(String(buffer)); } + else if(apInfo.version == 0) { + wsSerial("AP failed failed to come online. :-("); + } + else { + wsSerial("Flashing failed. :-("); + } + delete Url; vTaskDelete(NULL); } +#endif + void handleUpdateC6(AsyncWebServerRequest* request) { #if defined C6_OTA_FLASHING - uint8_t doDownload = 1; - if (request->hasParam("download", true)) { - doDownload = atoi(request->getParam("download", true)->value().c_str()); + if (request->hasParam("url",true)) { + String *Url = new String(request->getParam("url",true)->value()); + xTaskCreate(C6firmwareUpdateTask, "OTAUpdateTask", 6400, Url, 10, NULL); + request->send(200, "Ok"); } - xTaskCreate(C6firmwareUpdateTask, "OTAUpdateTask", 6144, &doDownload, 10, NULL); - request->send(200, "Ok"); + else { + LOG("Sending bad request"); + request->send(400, "Bad request"); + } +#elif defined(SHORT_CHIP_NAME) + request->send(400, SHORT_CHIP_NAME " flashing not implemented"); #else - request->send(400, "C6 flashing not implemented"); + request->send(400, "C6/H2 flashing not implemented"); #endif } diff --git a/ESP32_AP-Flasher/src/serialap.cpp b/ESP32_AP-Flasher/src/serialap.cpp index ce225e47..811e78f2 100644 --- a/ESP32_AP-Flasher/src/serialap.cpp +++ b/ESP32_AP-Flasher/src/serialap.cpp @@ -14,6 +14,8 @@ #include "web.h" #include "zbs_interface.h" +#define LOG(format, ... ) printf(format,## __VA_ARGS__) + QueueHandle_t rxCmdQueue; SemaphoreHandle_t txActive; @@ -25,7 +27,9 @@ SemaphoreHandle_t txActive; volatile uint8_t cmdReplyValue = CMD_REPLY_WAIT; #define AP_SERIAL_PORT Serial1 +#ifndef FLASHER_DEBUG_SHARED volatile bool rxSerialStopTask2 = false; +#endif uint8_t channelList[6]; struct espSetChannelPower curChannel = {0, 11, 10}; @@ -42,6 +46,17 @@ struct espSetChannelPower curChannel = {0, 11, 10}; volatile uint32_t lastAPActivity = 0; struct APInfoS apInfo; +enum ApSerialState { + SERIAL_STATE_NONE, + SERIAL_STATE_INITIALIZED, + SERIAL_STATE_STARTING, + SERIAL_STATE_RUNNING, + SERIAL_STATE_STOP, + SERIAL_STATE_STOPPED +}; + +volatile ApSerialState gSerialTaskState; + struct rxCmd { uint8_t* data; uint8_t len; @@ -156,6 +171,21 @@ void setAPstate(bool isOnline, uint8_t state) { rgbIdlePeriod = (isOnline ? 767 : 255); if (isOnline) rgbIdle(); #endif +#ifdef FLASHER_DEBUG_SHARED +// Flasher shares port with AP comms + if(state == AP_STATE_FLASHING) { + LOG("Shared COM port, gSerialTaskState %d\n",gSerialTaskState); + gSerialTaskState = SERIAL_STATE_STOP; + for(int i = 0; i < 100; i++) { + vTaskDelay(1 / portTICK_RATE_MS); + if(gSerialTaskState == SERIAL_STATE_STOPPED) { + gSerialTaskState = SERIAL_STATE_NONE; + break; + } + } + LOG("gSerialTaskState %d\n",gSerialTaskState); + } +#endif } // Reset the tag @@ -435,7 +465,9 @@ void rxSerialTask(void* parameter) { static char lastchar = 0; static uint8_t charindex = 0; - while (1) { + gSerialTaskState = SERIAL_STATE_RUNNING; + LOG("rxSerialTask starting\n"); + while (gSerialTaskState == SERIAL_STATE_RUNNING) { while (AP_SERIAL_PORT.available()) { lastchar = AP_SERIAL_PORT.read(); switch (RXState) { @@ -666,9 +698,14 @@ void rxSerialTask(void* parameter) { } vTaskDelay(1 / portTICK_PERIOD_MS); } // end of while(1) + + AP_SERIAL_PORT.end(false); + gSerialTaskState = SERIAL_STATE_STOPPED; + LOG("rxSerialTask stopped\n"); + vTaskDelete(NULL); } -#ifdef FLASHER_DEBUG_RXD +#if defined(FLASHER_DEBUG_RXD) && !defined(FLASHER_DEBUG_SHARED) void rxSerialTask2(void* parameter) { char lastchar = 0; time_t startTime = millis(); @@ -747,11 +784,29 @@ void segmentedShowIp() { } bool bringAPOnline() { - #ifdef BLE_ONLY +#ifdef BLE_ONLY apInfo.state = AP_STATE_NORADIO; - #endif +#endif if (apInfo.state == AP_STATE_NORADIO) return true; if (apInfo.state == AP_STATE_FLASHING) return false; + + if(gSerialTaskState != SERIAL_STATE_INITIALIZED) { +#if (AP_PROCESS_PORT == FLASHER_AP_PORT) + AP_SERIAL_PORT.begin(115200, SERIAL_8N1, FLASHER_AP_RXD, FLASHER_AP_TXD); +#elif defined(HAS_EXT_FLASHER) + #if (AP_PROCESS_PORT == FLASHER_EXT_PORT) + AP_SERIAL_PORT.begin(115200, SERIAL_8N1, FLASHER_EXT_RXD, FLASHER_EXT_TXD); + #elif (AP_PROCESS_PORT == FLASHER_ALTRADIO_PORT) + AP_SERIAL_PORT.begin(115200, SERIAL_8N1, FLASHER_AP_RXD, FLASHER_AP_TXD); + #endif +#endif + gSerialTaskState = SERIAL_STATE_INITIALIZED; + } + if(gSerialTaskState != SERIAL_STATE_RUNNING) { + gSerialTaskState = SERIAL_STATE_STARTING; + xTaskCreate(rxSerialTask, "rxSerialTask", 1750, NULL, 11, NULL); + vTaskDelay(500 / portTICK_PERIOD_MS); + } setAPstate(false, AP_STATE_OFFLINE); // try without rebooting AP_SERIAL_PORT.updateBaudRate(115200); @@ -823,25 +878,12 @@ void APTask(void* parameter) { return; } -#if (AP_PROCESS_PORT == FLASHER_AP_PORT) - AP_SERIAL_PORT.begin(115200, SERIAL_8N1, FLASHER_AP_RXD, FLASHER_AP_TXD); -#endif -#ifdef HAS_EXT_FLASHER -#if (AP_PROCESS_PORT == FLASHER_EXT_PORT) - AP_SERIAL_PORT.begin(115200, SERIAL_8N1, FLASHER_EXT_RXD, FLASHER_EXT_TXD); -#endif -#if (AP_PROCESS_PORT == FLASHER_ALTRADIO_PORT) - AP_SERIAL_PORT.begin(115200, SERIAL_8N1, FLASHER_AP_RXD, FLASHER_AP_TXD); -#endif -#endif xTaskCreate(rxCmdProcessor, "rxCmdProcessor", 6000, NULL, 15, NULL); - xTaskCreate(rxSerialTask, "rxSerialTask", 1750, NULL, 11, NULL); -#ifdef FLASHER_DEBUG_RXD +#if defined(FLASHER_DEBUG_RXD) && !defined(FLASHER_DEBUG_SHARED) xTaskCreate(rxSerialTask2, "rxSerialTask2", 1750, NULL, 2, NULL); -#endif - vTaskDelay(500 / portTICK_PERIOD_MS); +#endif bringAPOnline(); #ifndef C6_OTA_FLASHING diff --git a/ESP32_AP-Flasher/src/web.cpp b/ESP32_AP-Flasher/src/web.cpp index 5c856fc4..584baa21 100644 --- a/ESP32_AP-Flasher/src/web.cpp +++ b/ESP32_AP-Flasher/src/web.cpp @@ -514,13 +514,21 @@ void init_web() { UDPcomm udpsync; udpsync.getAPList(); AsyncResponseStream *response = request->beginResponseStream("application/json"); + String HasC6 = "0"; + String HasH2 = "0"; + String HasTSLR = "0"; response->print("{"); -#ifdef C6_OTA_FLASHING - response->print("\"C6\": \"1\", "); -#else - response->print("\"C6\": \"0\", "); +#ifdef HAS_H2 + HasH2 = "1"; +#elif defined(HAS_TSLR) + HasTSLR = "1"; +#elif defined(C6_OTA_FLASHING) + HasC6 = "1"; #endif + response->print("\"C6\": \"" + HasC6 + "\", "); + response->print("\"H2\": \"" + HasH2 + "\", "); + response->print("\"TLSR\": \"" + HasTSLR + "\", "); #ifdef SAVE_SPACE response->print("\"savespace\": \"1\", "); #else diff --git a/ESP32_AP-Flasher/wwwroot/index.html b/ESP32_AP-Flasher/wwwroot/index.html index f69af71e..6360e21b 100644 --- a/ESP32_AP-Flasher/wwwroot/index.html +++ b/ESP32_AP-Flasher/wwwroot/index.html @@ -522,7 +522,10 @@ options:

Releases

-
+
+

+
+

Other actions

Update
Remark'; + table.appendChild(tableHeader); - let rowCounter = 0; - releaseDetails.forEach(release => { - if (rowCounter < 4 && release?.html_url) { - const tableRow = document.createElement('tr'); - let tablerow = `
${release.tag_name}${release.date}${release.name}`; - if (release.tag_name == currentVer) { - tablerow += "current version"; - } else if (release.date < formatEpoch(currentBuildtime)) { - tablerow += "older"; - } else { - tablerow += "newer"; - } - tableRow.innerHTML = tablerow; - table.appendChild(tableRow); - rowCounter++; - } - }); + let rowCounter = 0; + let radioFwCounter = 0; + releaseDetails.forEach(release => { + if (rowCounter < 4 && release?.html_url) { + const tableRow = document.createElement('tr'); + let tablerow = `${release.tag_name}${release.date}${release.name}`; + if (release.tag_name == currentVer) { + tablerow += "current version"; + } else if (release.date < formatEpoch(currentBuildtime)) { + tablerow += "older"; + } else { + tablerow += "newer"; + } + tableRow.innerHTML = tablerow; + table.appendChild(tableRow); + rowCounter++; + } + if (release?.firmware_url) { + radioFwCounter++; + } + }); + $('#releasetable').innerHTML = ""; + $('#releasetable').appendChild(table); - $('#releasetable').innerHTML = ""; - $('#releasetable').appendChild(table); - disableButtons(buttonState); - }) - .catch(error => { - print('Error fetching releases:' + error, "red"); - }); + if(radioFwCounter > 0) { + const table1 = document.createElement('table'); + const tableHeader1 = document.createElement('tr'); + + tableHeader1.innerHTML = 'ReleaseDateName
Update
VersionRemark'; + table1.appendChild(tableHeader1); + + rowCounter = 0; + for (const release of releaseDetails) { + if (rowCounter < 4 && release?.firmware_url) { + const tableRow = document.createElement('tr'); + var tablerow; + var firmwareVer = "unknown"; + var release_url = release.firmware_url; + + tablerow = `${release.tag_name}${release.date}${release.name}`; + tablerow += ``; + const firmwareUrl = 'http://proxy.openepaperlink.org/proxy.php?url=' + release.firmware_url; + firmwareVer = await fetch(firmwareUrl, { method: 'GET'}) + .then(function (response) { return response.json(); }) + .then(function (response) { + return response[2]['version']; }) + .catch(error => { + print('Error fetching releases:' + error, "red"); + }); + tablerow += '' + firmwareVer + ''; + if(firmwareVer != 'unknown') { + let Ver = Number('0x' + firmwareVer); + if(Ver > gCurrentRfVer) { + tablerow += 'Newer'; + } + else if (Ver < gCurrentRfVer) { + tablerow += 'Older'; + } + else if(!Number.isNaN(Ver)){ + tablerow += 'Same'; + } + } + tablerow += ''; + tableRow.innerHTML = tablerow; + table1.appendChild(tableRow); + rowCounter++; + } + }; + + $('#radio_releasetable').innerHTML = ""; + $('#radio_releasetable').appendChild(table1); + } + + const table2 = document.createElement('table'); + { + const tableHeader2 = document.createElement('tr'); + tableHeader2.innerHTML = 'Firmware
Update
'; + table2.appendChild(tableHeader2); + const tableRow = document.createElement('tr'); + tablerow = 'Last uploaded version'; + tablerow += ``; + tableRow.innerHTML = tablerow; + table2.appendChild(tableRow); + } + { + const tableRow = document.createElement('tr'); + const Url = "https://raw.githubusercontent.com/" + repo + + "/master/binaries/ESP32-" + gShortName + + "/firmware_" + gShortName + ".json"; + + tablerow = `Latest version from repo`; + tablerow += ``; + tableRow.innerHTML = tablerow; + table2.appendChild(tableRow); + } + $('#radio_releasetable1').innerHTML = ""; + $('#radio_releasetable1').appendChild(table2); + + disableButtons(buttonState); } export function updateAll(binUrl, fileUrl, tagname) { @@ -358,19 +449,18 @@ $('#rollbackBtn').onclick = function () { disableButtons(false); } -$('#updateC6Btn').onclick = function () { +export async function updateC6H2(Url) { if (running) return; disableButtons(true); running = true; errors = 0; + const ReleaseUrl = Url.substring(0,Url.lastIndexOf('/')); const consoleDiv = document.getElementById('updateconsole'); consoleDiv.scrollTop = consoleDiv.scrollHeight; - - print("Flashing ESP32-C6..."); - - const isChecked = $('#c6download').checked; const formData = new FormData(); - formData.append('download', isChecked ? '1' : '0'); + + print("Flashing " + gModuleType + " ..."); + formData.append('url', ReleaseUrl); fetch("update_c6", { method: "POST", @@ -418,7 +508,7 @@ $('#selectRepo').onclick = function (event) { if (!responseBody.trim().startsWith("[")) { throw new Error("Failed to fetch the release info file"); } - const updateData = JSON.parse(responseBody).filter(item => !item.name.endsWith('_full.bin')); + const updateData = JSON.parse(responseBody).filter(item => !item.name.endsWith('_full.bin') && !item.name.includes('_H2.') && !item.name.includes('_C6.')); const inputParent = $('#environment').parentNode; const selectElement = document.createElement('select'); diff --git a/binaries/ESP32-C6/firmware_C6.json b/binaries/ESP32-C6/firmware_C6.json index a76fa879..3fe50694 100644 --- a/binaries/ESP32-C6/firmware_C6.json +++ b/binaries/ESP32-C6/firmware_C6.json @@ -1,10 +1,10 @@ [{ - "filename": "bootloader.bin", + "filename": "bootloader_C6.bin", "address": "0x0", "version": "0001" }, { - "filename": "partition-table.bin", + "filename": "partition-table_C6.bin", "address": "0x8000", "version": "0001" }, diff --git a/binaries/ESP32-H2/firmware_H2.json b/binaries/ESP32-H2/firmware_H2.json index 8037c452..2a44a1ae 100644 --- a/binaries/ESP32-H2/firmware_H2.json +++ b/binaries/ESP32-H2/firmware_H2.json @@ -1,10 +1,10 @@ [{ - "filename": "bootloader.bin", + "filename": "bootloader_H2.bin", "address": "0x0", "version": "0001" }, { - "filename": "partition-table.bin", + "filename": "partition-table_H2.bin", "address": "0x8000", "version": "0001" },