Coprocessor OTA changes (#425)

OTA changes to support C6/H2 OTA updates from configured repo.
This commit is contained in:
Skip Hansen
2025-01-25 14:11:39 -08:00
committed by GitHub
parent 19bbba5202
commit 4b667d0039
17 changed files with 564 additions and 295 deletions

View File

@@ -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:

Binary file not shown.

Binary file not shown.

View File

@@ -1,4 +1,16 @@
#include <Arduino.h>
#include <LittleFS.h>
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);

View File

@@ -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 =
+<*>-<usbflasher.cpp>-<swd.cpp>-<webflasher.cpp>

View File

@@ -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<JsonArray>();
for (JsonObject obj : jsonArray) {
String filename = "/" + obj["filename"].as<String>();
// 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<JsonArray>();
for(JsonObject obj : jsonArray) {
String filename = "/" + obj["filename"].as<String>();
String binaryUrl = RepoUrl + filename;
JsonArray jsonArray = jsonDoc.as<JsonArray>();
for (JsonObject obj : jsonArray) {
String filename = "/" + obj["filename"].as<String>();
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<JsonArray>();
for(JsonObject obj : jsonArray) {
String filename = "/" + obj["filename"].as<String>();
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;
}

View File

@@ -7,6 +7,7 @@
#include <MD5Builder.h>
#include <Update.h>
#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<String *>(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
}

View File

@@ -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

View File

@@ -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

View File

@@ -522,7 +522,10 @@ options:
<button id="confirmSelectRepo">Confirm</button><button id="cancelSelectRepo">Cancel</button>
</div>
<h4>Releases</h4>
<div id="releasetable"></div>
<div id="releasetable" class="releasetable"></div>
<h4 id="radio_release_title"></h4>
<div id="radio_releasetable" class="releasetable"></div>
<div id="radio_releasetable1" class="releasetable"></div>
<h4>Other actions</h4>
<div>
<p id="rollbackOption" style="display:none">

View File

@@ -825,37 +825,37 @@ h4 {
font-weight: bold;
}
#releasetable {
.releasetable {
margin: 10px 0px;
}
#releasetable table {
.releasetable table {
border-spacing: 1px;
}
#releasetable th {
.releasetable th {
text-align: left;
background-color: #ffffff;
padding: 1px 5px;
}
#releasetable td {
.releasetable td {
background-color: #ffffff;
padding: 1px 5px;
min-width: 70px;
vertical-align: baseline;
}
#releasetable td:nth-child(2) {
.releasetable td:nth-child(2) {
white-space: nowrap;
}
#releasetable button {
.releasetable button {
padding: 3px 10px;
background-color: #e0e0e0;
}
#releasetable button:hover {
.releasetable button:hover {
background-color: #a0a0a0;
}

View File

@@ -54,10 +54,9 @@ window.addEventListener("loadConfig", function () {
$(".logo").innerHTML = data.alias;
this.document.title = data.alias;
}
if (data.C6 == 1) {
if (data.C6 == 1 || (data.H2 && data.H2 == 1)) {
var optionToRemove = $("#apcfgchid").querySelector('option[value="27"]');
if (optionToRemove) $("#apcfgchid").removeChild(optionToRemove);
$('#updateC6Option').style.display = 'block';
}
if (data.hasFlasher == 1) {
$('[data-target="flashtab"]').style.display = 'block';

View File

@@ -7,8 +7,27 @@ let running = false;
let errors = 0;
let env = '', currentVer = '', currentBuildtime = 0;
let buttonState = false;
let gIsC6 = false;
let gIsH2 = false;
let gModuleType = '';
let gShortName = '';
let gCurrentRfVer = 0;
export async function initUpdate() {
if (apConfig.C6 == 1) {
gIsC6 = true;
gModuleType = "ESP32-C6";
gShortName = "C6";
}
else if(apConfig?.H2 && apConfig.H2 == 1) {
gIsH2 = true;
gModuleType = "ESP32-H2";
gShortName = "H2";
}
else {
gModuleType = "Unknown"
}
$('#radio_release_title').innerHTML = gModuleType + " Firmware";
const response = await fetch("version.txt");
let filesystemversion = await response.text();
@@ -30,7 +49,7 @@ export async function initUpdate() {
$('#selectRepo').style.display = 'inline-block';
$('#repoWarning').style.display = 'none';
const sysinfoPromise = fetch("sysinfo")
const sdata = await fetch("sysinfo")
.then(response => {
if (response.status != 200) {
print("Error fetching sysinfo: " + response.status, "red");
@@ -48,98 +67,170 @@ export async function initUpdate() {
print('Error fetching sysinfo: ' + error, "red");
});
const repoPromise = fetch(repoUrl)
.then(response => response.json())
if (sdata.env) {
print(`current env: ${sdata.env}`);
print(`build date: ${formatEpoch(sdata.buildtime)}`);
print(`esp32 version: ${sdata.buildversion}`);
if(gModuleType != '') {
var hex_ver = '0000' + sdata.ap_version.toString(16);
print(`${gModuleType} version: ${hex_ver.slice(-4)}`);
}
print(`filesystem version: ${filesystemversion}`);
print(`psram size: ${sdata.psramsize}`);
print(`flash size: ${sdata.flashsize}`);
print("--------------------------", "gray");
env = apConfig.env || sdata.env;
if (sdata.env != env) {
print(`Warning: you selected a build environment ${env} which is\ndifferent than the currently used ${sdata.env}.\nOnly update the firmware with a mismatched build environment if\nyou know what you're doing.`, "yellow");
}
currentVer = sdata.buildversion;
currentBuildtime = sdata.buildtime;
gCurrentRfVer = sdata.ap_version;
if (sdata.rollback) $("#rollbackOption").style.display = 'block';
$('#environment').value = env;
}
Promise.all([sysinfoPromise, repoPromise])
.then(([sdata, rdata]) => {
if (sdata.env) {
print(`current env: ${sdata.env}`);
print(`build date: ${formatEpoch(sdata.buildtime)}`);
print(`esp32 version: ${sdata.buildversion}`);
print(`filesystem version: ${filesystemversion}`);
print(`psram size: ${sdata.psramsize}`);
print(`flash size: ${sdata.flashsize}`);
if (sdata.hasC6) {
print(`ESP-C6/H2 version: 0x${parseInt(sdata.C6version).toString(16).toUpperCase()}`);
}
print("--------------------------", "gray");
env = apConfig.env || sdata.env;
if (sdata.env != env) {
print(`Warning: you selected a build environment ${env} which is\ndifferent than the currently used ${sdata.env}.\nOnly update the firmware with a mismatched build environment if\nyou know what you're doing.`, "yellow");
}
currentVer = sdata.buildversion;
currentBuildtime = sdata.buildtime;
if (sdata.rollback) $("#rollbackOption").style.display = 'block';
$('#environment').value = env;
const rdata = await fetch(repoUrl).then(response => response.json())
const JsonName = 'firmware_' + gShortName + '.json';
const releaseDetails = rdata.map(release => {
const assets = release.assets;
const filesJsonAsset = assets.find(asset => asset.name === 'filesystem.json');
const binariesJsonAsset = assets.find(asset => asset.name === 'binaries.json');
const containsEnv = assets.find(asset => asset.name === env + '.bin');
const firmwareAsset = assets.find(asset => asset.name === JsonName);
if (filesJsonAsset && binariesJsonAsset && containsEnv) {
return {
html_url: release.html_url,
tag_name: release.tag_name,
name: release.name,
date: formatDateTime(release.published_at),
author: release.author.login,
file_url: filesJsonAsset.browser_download_url,
bin_url: binariesJsonAsset.browser_download_url,
firmware_url: firmwareAsset?.browser_download_url,
}
};
})
const releaseDetails = rdata.map(release => {
const assets = release.assets;
const filesJsonAsset = assets.find(asset => asset.name === 'filesystem.json');
const binariesJsonAsset = assets.find(asset => asset.name === 'binaries.json');
const containsEnv = assets.find(asset => asset.name === env + '.bin');
if (filesJsonAsset && binariesJsonAsset && containsEnv) {
return {
html_url: release.html_url,
tag_name: release.tag_name,
name: release.name,
date: formatDateTime(release.published_at),
author: release.author.login,
file_url: filesJsonAsset.browser_download_url,
bin_url: binariesJsonAsset.browser_download_url
}
};
});
const easyupdate = $('#easyupdate');
if (releaseDetails.length === 0) {
easyupdate.innerHTML = ("No releases found.");
if (releaseDetails.length === 0) {
easyupdate.innerHTML = ("No releases found.");
} else {
const release = releaseDetails[0];
if (release?.tag_name) {
if (release.tag_name == currentVer) {
easyupdate.innerHTML = `Version ${currentVer}. You are up to date`;
} else if (release.date < formatEpoch(currentBuildtime - 30 * 60)) {
easyupdate.innerHTML = `Your version is newer than the latest release date.<br>Are you the developer? :-)`;
} else {
const release = releaseDetails[0];
if (release?.tag_name) {
if (normalizeVersion(release.tag_name) === normalizeVersion(currentVer)) {
easyupdate.innerHTML = `Version ${currentVer}. You are up to date`;
} else if (release.date < formatEpoch(currentBuildtime - 30 * 60)) {
easyupdate.innerHTML = `Your version is newer than the latest release date.<br>Are you the developer? :-)`;
} else {
easyupdate.innerHTML = `An update from version ${currentVer} to version ${release.tag_name} is available.<button onclick="otamodule.updateAll('${release.bin_url}','${release.file_url}','${release.tag_name}')">Update now!</button>`;
}
}
easyupdate.innerHTML = `An update from version ${currentVer} to version ${release.tag_name} is available.<button onclick="otamodule.updateAll('${release.bin_url}','${release.file_url}','${release.tag_name}')">Update now!</button>`;
}
}
}
const table = document.createElement('table');
const tableHeader = document.createElement('tr');
tableHeader.innerHTML = '<th>Release</th><th>Date</th><th>Name</th><th colspan="2">Update:</th><th>Remark</th>';
table.appendChild(tableHeader);
const table = document.createElement('table');
const tableHeader = document.createElement('tr');
tableHeader.innerHTML = '<th>Release</th><th>Date</th><th>Name</th><th colspan="2"><center>Update</center></th><th>Remark</th>';
table.appendChild(tableHeader);
let rowCounter = 0;
releaseDetails.forEach(release => {
if (rowCounter < 4 && release?.html_url) {
const tableRow = document.createElement('tr');
let tablerow = `<td><a href="${release.html_url}" target="_new">${release.tag_name}</a></td><td>${release.date}</td><td>${release.name}</td><td><button type="button" onclick="otamodule.updateWebpage('${release.file_url}','${release.tag_name}', true)">Filesystem</button></td><td><button type="button" onclick="otamodule.updateESP('${release.bin_url}', true)">ESP32</button></td>`;
if (release.tag_name == currentVer) {
tablerow += "<td>current version</td>";
} else if (release.date < formatEpoch(currentBuildtime)) {
tablerow += "<td>older</td>";
} else {
tablerow += "<td>newer</td>";
}
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 = `<td><a href="${release.html_url}" target="_new">${release.tag_name}</a></td><td>${release.date}</td><td>${release.name}</td><td><button type="button" onclick="otamodule.updateWebpage('${release.file_url}','${release.tag_name}', true)">Filesystem</button></td><td><button type="button" onclick="otamodule.updateESP('${release.bin_url}', true)">ESP32</button></td>`;
if (release.tag_name == currentVer) {
tablerow += "<td>current version</td>";
} else if (release.date < formatEpoch(currentBuildtime)) {
tablerow += "<td>older</td>";
} else {
tablerow += "<td>newer</td>";
}
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 = '<th>Release</th><th>Date</th><th>Name</th><th><center>Update</center></th><th>Version</th><th>Remark</th>';
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 = `<td><a href="${release.html_url}" target="_new">${release.tag_name}</a></td><td>${release.date}</td><td>${release.name}</td>`;
tablerow += `<td><button type="button" onclick="otamodule.updateC6H2('${release_url}')">${gModuleType}</button></td>`;
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 += '<td>' + firmwareVer + '</td><td>';
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 += '</td>';
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 = '<th>Firmware</th><th><center>Update</center></th>';
table2.appendChild(tableHeader2);
const tableRow = document.createElement('tr');
tablerow = '<td>Last uploaded version</td>';
tablerow += `<td><button type="button" onclick="otamodule.updateC6H2('')">${gModuleType}</button></td>`;
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 = `<td><a href="https://github.com/${repo}" target="_new">Latest version from repo</a></td>`;
tablerow += `<td><button type="button" onclick="otamodule.updateC6H2('${Url}')">${gModuleType}</button></td>`;
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');

View File

@@ -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"
},

View File

@@ -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"
},