160 lines
5.5 KiB
C++
160 lines
5.5 KiB
C++
#include "ota_update.h"
|
|
#include "config.h"
|
|
#include <WiFi.h>
|
|
#include <WiFiClientSecure.h>
|
|
#include <HTTPClient.h>
|
|
#include <HTTPUpdate.h>
|
|
#include <ArduinoJson.h>
|
|
#include <cstdlib>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
|
|
namespace OtaUpdate {
|
|
|
|
namespace {
|
|
// Gitea API URL constructed from config constants at runtime
|
|
String releasesApiUrl() {
|
|
return String("https://") + Config::GITEA_API_HOST + Config::GITEA_API_PATH;
|
|
}
|
|
|
|
bool parseVersion(const char* s, int& major, int& minor, int& patch) {
|
|
major = minor = patch = 0;
|
|
if (!s || !s[0]) return false;
|
|
if (s[0] == 'v' || s[0] == 'V') s++;
|
|
return sscanf(s, "%d.%d.%d", &major, &minor, &patch) == 3;
|
|
}
|
|
|
|
// Numeric version comparison (not string-based, so e.g. "3.10.0" > "3.9.0").
|
|
int compareVersions(const char* a, const char* b) {
|
|
int aMaj, aMin, aPat, bMaj, bMin, bPat;
|
|
if (!parseVersion(a, aMaj, aMin, aPat) || !parseVersion(b, bMaj, bMin, bPat)) return 0;
|
|
if (aMaj != bMaj) return aMaj - bMaj;
|
|
if (aMin != bMin) return aMin - bMin;
|
|
return aPat - bPat;
|
|
}
|
|
|
|
void addAuthHeader(HTTPClient& http) {
|
|
http.addHeader("User-Agent", "EiswolfsFlightradarCYD-OTA/1.0");
|
|
if (Config::GITEA_API_TOKEN[0]) {
|
|
String auth = "token ";
|
|
auth += Config::GITEA_API_TOKEN;
|
|
http.addHeader("Authorization", auth);
|
|
}
|
|
}
|
|
}
|
|
|
|
CheckInfo checkForUpdate() {
|
|
CheckInfo info;
|
|
String url = releasesApiUrl();
|
|
|
|
Serial.printf("[OTA] Checking for update: url=%s freeHeap=%u RSSI=%ddBm\n", url.c_str(),
|
|
(unsigned)ESP.getFreeHeap(), WiFi.RSSI());
|
|
|
|
WiFiClientSecure client;
|
|
client.setInsecure();
|
|
client.setTimeout(8000);
|
|
|
|
HTTPClient http;
|
|
http.setTimeout(8000);
|
|
if (!http.begin(client, url)) {
|
|
Serial.println("[OTA] Check failed: http.begin() returned false.");
|
|
return info;
|
|
}
|
|
addAuthHeader(http);
|
|
|
|
int code = http.GET();
|
|
if (code != HTTP_CODE_OK) {
|
|
if (code < 0) {
|
|
Serial.printf("[OTA] Check failed: HTTP error=%d (%s)\n", code,
|
|
HTTPClient::errorToString(code).c_str());
|
|
} else {
|
|
Serial.printf("[OTA] Check failed: HTTP status=%d\n", code);
|
|
}
|
|
http.end();
|
|
return info;
|
|
}
|
|
String body = http.getString();
|
|
http.end();
|
|
|
|
JsonDocument doc;
|
|
DeserializationError jsonErr = deserializeJson(doc, body);
|
|
if (jsonErr) {
|
|
Serial.printf("[OTA] Check failed: JSON error (%s)\n", jsonErr.c_str());
|
|
return info;
|
|
}
|
|
|
|
const char* tag = doc["tag_name"] | "";
|
|
if (!tag[0]) {
|
|
Serial.println("[OTA] Check failed: no tag_name in release JSON.");
|
|
return info;
|
|
}
|
|
|
|
strncpy(info.latestVersion, (tag[0] == 'v' || tag[0] == 'V') ? tag + 1 : tag,
|
|
sizeof(info.latestVersion) - 1);
|
|
|
|
// Construct download URL from the tag name using Gitea's standard
|
|
// release asset pattern. browser_download_url from the API contains
|
|
// the internal IP (http://192.168.0.37:3000/...) which the ESP32
|
|
// cannot use — it needs the external proxy URL.
|
|
String dlUrl = String("https://git.rasmusbendtsen.dk/rasmus/flyradar/releases/download/")
|
|
+ String(tag) + "/firmware.bin";
|
|
strncpy(info.downloadUrl, dlUrl.c_str(), sizeof(info.downloadUrl) - 1);
|
|
|
|
Serial.printf("[OTA] Download URL: %s\n", info.downloadUrl);
|
|
|
|
int cmp = compareVersions(info.latestVersion, Config::APP_VERSION);
|
|
info.result = (cmp > 0) ? CheckResult::UpdateAvailable : CheckResult::UpToDate;
|
|
Serial.printf("[OTA] Check successful: installed=v%s latest=v%s -> %s\n", Config::APP_VERSION,
|
|
info.latestVersion,
|
|
info.result == CheckResult::UpdateAvailable ? "Update available" : "already up-to-date");
|
|
return info;
|
|
}
|
|
|
|
bool performUpdate(const char* url, void (*onProgress)(uint8_t percent)) {
|
|
Serial.printf("[OTA] Starting: url=%s freeHeap=%u RSSI=%ddBm\n", url,
|
|
(unsigned)ESP.getFreeHeap(), WiFi.RSSI());
|
|
|
|
WiFiClientSecure client;
|
|
client.setInsecure();
|
|
client.setTimeout(15000);
|
|
|
|
// Use HTTPClient overload so we can add the Authorization header
|
|
// (required for private repos even during download).
|
|
HTTPClient http;
|
|
if (!http.begin(client, url)) {
|
|
Serial.println("[OTA] Failed: http.begin() returned false for download URL.");
|
|
return false;
|
|
}
|
|
addAuthHeader(http);
|
|
|
|
httpUpdate.rebootOnUpdate(false);
|
|
httpUpdate.onProgress([onProgress](int cur, int total) {
|
|
if (onProgress && total > 0) {
|
|
onProgress((uint8_t)((cur * 100) / total));
|
|
}
|
|
static int8_t lastLoggedPercent = -1;
|
|
if (total > 0) {
|
|
int8_t pct = (int8_t)((cur * 100) / total);
|
|
if (pct != lastLoggedPercent && pct % 10 == 0) {
|
|
lastLoggedPercent = pct;
|
|
Serial.printf("[OTA] Progress: %d%% (%d/%d bytes) freeHeap=%u\n", pct, cur, total,
|
|
(unsigned)ESP.getFreeHeap());
|
|
}
|
|
}
|
|
});
|
|
|
|
t_httpUpdate_return result = httpUpdate.update(http);
|
|
http.end();
|
|
|
|
if (result != HTTP_UPDATE_OK) {
|
|
Serial.printf("[OTA] Failed: result=%d error=%d (%s) freeHeap=%u\n", (int)result,
|
|
httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str(),
|
|
(unsigned)ESP.getFreeHeap());
|
|
} else {
|
|
Serial.println("[OTA] Successfully downloaded and flashed.");
|
|
}
|
|
|
|
return result == HTTP_UPDATE_OK;
|
|
}
|
|
|
|
} |