cargo: add local OTA firmware upload
This commit is contained in:
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Cargo ESP local OTA firmware upload page at `/ota`.
|
||||||
|
- Cargo ESP OTA API endpoint at `POST /api/v1/system/ota`.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Cargo ESP starts AP and Web UI/API before attempting STA WiFi reconnects.
|
- Cargo ESP starts AP and Web UI/API before attempting STA WiFi reconnects.
|
||||||
- STA reconnect attempts now service the web server during short connection windows so AP mode remains usable when no saved WiFi is available.
|
- STA reconnect attempts now service the web server during short connection windows so AP mode remains usable when no saved WiFi is available.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <Preferences.h>
|
#include <Preferences.h>
|
||||||
#include <esp_wifi.h>
|
#include <esp_wifi.h>
|
||||||
#include <esp_system.h>
|
#include <esp_system.h>
|
||||||
|
#include <Update.h>
|
||||||
|
|
||||||
#define OLED_ENABLED 0
|
#define OLED_ENABLED 0
|
||||||
|
|
||||||
@@ -1648,6 +1649,98 @@ void sendSimpleJsonError(int status, const char* error, const char* detail) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const char OTA_HTML[] PROGMEM = R"rawliteral(
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<title>Cargo ESP OTA</title>
|
||||||
|
<style>
|
||||||
|
body{background:#0b0f14;color:#eef4ff;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Arial,sans-serif;margin:0}
|
||||||
|
.wrap{max-width:620px;margin:0 auto;padding:22px}
|
||||||
|
.card{background:#141b24;border:1px solid #223044;border-radius:18px;padding:18px}
|
||||||
|
h1{margin-top:0}
|
||||||
|
input,button{width:100%;box-sizing:border-box;margin-top:12px}
|
||||||
|
input{border:1px solid #223044;border-radius:12px;padding:12px;background:#0f151d;color:#eef4ff}
|
||||||
|
button{border:0;border-radius:12px;padding:13px;background:#1d324f;color:#eef4ff;font-weight:800}
|
||||||
|
.warn{color:#ffd166;font-size:14px;line-height:1.4}
|
||||||
|
.small{color:#7f8da3;font-size:13px}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="wrap">
|
||||||
|
<div class="card">
|
||||||
|
<h1>Cargo ESP OTA</h1>
|
||||||
|
<p class="warn">Upload only firmware built for the Cargo ESP. The controller will reboot after a successful update.</p>
|
||||||
|
<form method="POST" action="/api/v1/system/ota" enctype="multipart/form-data">
|
||||||
|
<input type="file" name="firmware" accept=".bin" required>
|
||||||
|
<button type="submit">Upload Firmware</button>
|
||||||
|
</form>
|
||||||
|
<p class="small">Tip: Arduino IDE can create this with Sketch → Export Compiled Binary.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)rawliteral";
|
||||||
|
|
||||||
|
static bool otaUploadHadError = false;
|
||||||
|
|
||||||
|
void handleOtaPage() {
|
||||||
|
server.send_P(200, "text/html", OTA_HTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleOtaUploadDone() {
|
||||||
|
DynamicJsonDocument doc(512);
|
||||||
|
doc["ok"] = !otaUploadHadError && !Update.hasError();
|
||||||
|
doc["firmware_name"] = FIRMWARE_NAME;
|
||||||
|
doc["message"] = doc["ok"].as<bool>() ? "OTA update complete. Rebooting." : "OTA update failed.";
|
||||||
|
|
||||||
|
String output;
|
||||||
|
serializeJson(doc, output);
|
||||||
|
|
||||||
|
if (doc["ok"].as<bool>()) {
|
||||||
|
server.send(200, "application/json", output);
|
||||||
|
delay(500);
|
||||||
|
ESP.restart();
|
||||||
|
} else {
|
||||||
|
server.send(500, "application/json", output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleOtaUploadStream() {
|
||||||
|
HTTPUpload& upload = server.upload();
|
||||||
|
|
||||||
|
if (upload.status == UPLOAD_FILE_START) {
|
||||||
|
otaUploadHadError = false;
|
||||||
|
Serial.print("OTA: upload start: ");
|
||||||
|
Serial.println(upload.filename);
|
||||||
|
|
||||||
|
if (!Update.begin(UPDATE_SIZE_UNKNOWN)) {
|
||||||
|
otaUploadHadError = true;
|
||||||
|
Update.printError(Serial);
|
||||||
|
}
|
||||||
|
} else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||||
|
if (!otaUploadHadError && Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
|
||||||
|
otaUploadHadError = true;
|
||||||
|
Update.printError(Serial);
|
||||||
|
}
|
||||||
|
} else if (upload.status == UPLOAD_FILE_END) {
|
||||||
|
if (!otaUploadHadError && Update.end(true)) {
|
||||||
|
Serial.print("OTA: success, bytes=");
|
||||||
|
Serial.println(upload.totalSize);
|
||||||
|
} else {
|
||||||
|
otaUploadHadError = true;
|
||||||
|
Update.printError(Serial);
|
||||||
|
}
|
||||||
|
} else if (upload.status == UPLOAD_FILE_ABORTED) {
|
||||||
|
otaUploadHadError = true;
|
||||||
|
Update.abort();
|
||||||
|
Serial.println("OTA: upload aborted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void handleResetApConfig() {
|
void handleResetApConfig() {
|
||||||
resetApConfigToDefaults();
|
resetApConfigToDefaults();
|
||||||
restartAccessPoint();
|
restartAccessPoint();
|
||||||
@@ -3406,6 +3499,7 @@ void handleCapabilities() {
|
|||||||
endpoints.add("POST /api/v1/bms/setup/exit");
|
endpoints.add("POST /api/v1/bms/setup/exit");
|
||||||
endpoints.add("POST /api/v1/bms/scan");
|
endpoints.add("POST /api/v1/bms/scan");
|
||||||
endpoints.add("POST /api/v1/bms/select");
|
endpoints.add("POST /api/v1/bms/select");
|
||||||
|
endpoints.add("POST /api/v1/system/ota");
|
||||||
|
|
||||||
JsonObject limits = doc.createNestedObject("limits");
|
JsonObject limits = doc.createNestedObject("limits");
|
||||||
limits["relay_count"] = MAX_RELAYS;
|
limits["relay_count"] = MAX_RELAYS;
|
||||||
@@ -4016,6 +4110,8 @@ void setup() {
|
|||||||
server.on("/", HTTP_GET, []() {
|
server.on("/", HTTP_GET, []() {
|
||||||
server.send_P(200, "text/html", INDEX_HTML);
|
server.send_P(200, "text/html", INDEX_HTML);
|
||||||
});
|
});
|
||||||
|
server.on("/ota", HTTP_GET, handleOtaPage);
|
||||||
|
server.on(API_V1("/system/ota"), HTTP_POST, handleOtaUploadDone, handleOtaUploadStream);
|
||||||
|
|
||||||
server.on(API_V1("/status"), handleStatus);
|
server.on(API_V1("/status"), handleStatus);
|
||||||
server.on(API_V1("/health"), HTTP_GET, handleHealth);
|
server.on(API_V1("/health"), HTTP_GET, handleHealth);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define FIRMWARE_VERSION "0.5.0"
|
#define FIRMWARE_VERSION "0.5.1-cargo-ota"
|
||||||
#define FIRMWARE_NAME "overland-controller"
|
#define FIRMWARE_NAME "overland-controller"
|
||||||
|
|||||||
Reference in New Issue
Block a user