2 Commits
v1.0.0 ... v1.1

Author SHA1 Message Date
3648538cbc eco mode menu toggle
Some checks failed
Build Release / build (push) Failing after 1m31s
2026-08-16 16:24:02 +02:00
a02e280f42 ota update 2026-08-16 14:52:23 +02:00
11 changed files with 76 additions and 29 deletions

View File

@@ -0,0 +1,29 @@
name: Build Release
on:
push:
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install PlatformIO
run: pip install platformio
- name: Build firmware
run: pio run
- name: Upload firmware.bin to release
uses: actions/upload-release-asset@v1
with:
upload_url: ${{ gitea.release.upload_url }}
asset_path: .pio/build/esp32dev/firmware.bin
asset_name: firmware.bin
asset_content_type: application/octet-stream

View File

@@ -37,7 +37,7 @@ namespace Config {
// require a meaningful User-Agent instead of the HTTPClient // require a meaningful User-Agent instead of the HTTPClient
// default, see https://operations.osmfoundation.org/policies/nominatim/. // default, see https://operations.osmfoundation.org/policies/nominatim/.
constexpr const char* NOMINATIM_HOST = "nominatim.openstreetmap.org"; constexpr const char* NOMINATIM_HOST = "nominatim.openstreetmap.org";
constexpr const char* NOMINATIM_USER_AGENT = "EiswolfsFlightradarCYD (github.com/Eiswolf-BG/eiswolfs-flightradar-CYD)"; constexpr const char* NOMINATIM_USER_AGENT = "EiswolfsFlightradarCYD (git.rasmusbendtsen.dk/rasmus/flyradar)";
struct GpsPinPair { uint8_t rx; uint8_t tx; const char* label; }; struct GpsPinPair { uint8_t rx; uint8_t tx; const char* label; };
constexpr GpsPinPair GPS_PIN_CANDIDATES[] = { constexpr GpsPinPair GPS_PIN_CANDIDATES[] = {
@@ -115,5 +115,12 @@ constexpr float RANGE_STEPS_KM[] = {10.0f, 25.0f, 50.0f, 100.0f};
constexpr const char* EMERGENCY_SQUAWKS[] = {"7500", "7600", "7700"}; constexpr const char* EMERGENCY_SQUAWKS[] = {"7500", "7600", "7700"};
constexpr uint8_t EMERGENCY_SQUAWK_COUNT = 3; constexpr uint8_t EMERGENCY_SQUAWK_COUNT = 3;
constexpr uint8_t MAX_WIFI_NETWORKS = 3; constexpr uint8_t MAX_WIFI_NETWORKS = 3;
// Gitea OTA update (Menu > System > "Check for update") - checks
// the latest release in a private Gitea repository and downloads
// firmware.bin from there. See ota_update.cpp.
constexpr const char* GITEA_API_BASE = "git.rasmusbendtsen.dk";
constexpr const char* GITEA_API_PATH = "/api/v1/repos/rasmus/flyradar/releases/latest";
constexpr const char* GITEA_API_TOKEN = "YOUR_GITEA_TOKEN_HERE"; // set this!
} }

View File

@@ -255,7 +255,7 @@ static const char* const I18N_DE[] = {
"Das Update konnte nicht heruntergeladen oder installiert werden. Das Gerät läuft unverändert mit der bisherigen Firmware weiter. Bitte WLAN-Verbindung prüfen und es später erneut versuchen.", "Das Update konnte nicht heruntergeladen oder installiert werden. Das Gerät läuft unverändert mit der bisherigen Firmware weiter. Bitte WLAN-Verbindung prüfen und es später erneut versuchen.",
"Bildschirm-Timeout", "Bildschirm-Timeout",
"Nach dieser Zeit ohne Berührung schaltet sich das Display ab, um Strom zu sparen. Ist der Ruhebildschirm aktiviert, zeigt es stattdessen einen gedimmten Sternenhimmel mit Uhrzeit.", "Nach dieser Zeit ohne Berührung schaltet sich das Display ab, um Strom zu sparen. Ist der Ruhebildschirm aktiviert, zeigt es stattdessen einen gedimmten Sternenhimmel mit Uhrzeit.",
"Eco Mode", "Eco Mode ",
"Streckenfilter", "Streckenfilter",
"ICAO eingeben", "ICAO eingeben",
}; };

View File

@@ -256,7 +256,7 @@ static const char* const I18N_EN[] = {
"The update could not be downloaded or installed. The device is still running the previous firmware, unchanged. Please check your WiFi connection and try again later.", "The update could not be downloaded or installed. The device is still running the previous firmware, unchanged. Please check your WiFi connection and try again later.",
"Screen Timeout", "Screen Timeout",
"After this time without touch, the display turns off to save power. With the screensaver on, it dims to a starfield with a clock instead.", "After this time without touch, the display turns off to save power. With the screensaver on, it dims to a starfield with a clock instead.",
"Eco Mode", "Eco Mode ",
"Route Filter", "Route Filter",
"Enter ICAO", "Enter ICAO",
}; };

View File

@@ -255,7 +255,7 @@ static const char* const I18N_ES[] = {
"No se pudo descargar o instalar la actualización. El dispositivo sigue funcionando sin cambios con el firmware anterior. Comprueba tu conexión WiFi e inténtalo de nuevo más tarde.", "No se pudo descargar o instalar la actualización. El dispositivo sigue funcionando sin cambios con el firmware anterior. Comprueba tu conexión WiFi e inténtalo de nuevo más tarde.",
"Tiempo de espera de pantalla", "Tiempo de espera de pantalla",
"Tras este tiempo sin toques, la pantalla se apaga para ahorrar energía. Con el salvapantallas activado, muestra en su lugar un cielo estrellado atenuado con la hora.", "Tras este tiempo sin toques, la pantalla se apaga para ahorrar energía. Con el salvapantallas activado, muestra en su lugar un cielo estrellado atenuado con la hora.",
"Eco Mode", "Eco Mode ",
"Filtro de ruta", "Filtro de ruta",
"Ingresa OACI", "Ingresa OACI",
}; };

View File

@@ -255,7 +255,7 @@ static const char* const I18N_FR[] = {
"La mise à jour n'a pas pu être téléchargée ou installée. L'appareil continue de fonctionner normalement avec le micrologiciel précédent. Merci de vérifier votre connexion WiFi et de réessayer plus tard.", "La mise à jour n'a pas pu être téléchargée ou installée. L'appareil continue de fonctionner normalement avec le micrologiciel précédent. Merci de vérifier votre connexion WiFi et de réessayer plus tard.",
"Délai d'inactivité", "Délai d'inactivité",
"Après ce délai sans contact, l'écran s'éteint pour économiser l'énergie. Avec l'écran de veille activé, il affiche à la place un ciel étoilé tamisé avec l'heure.", "Après ce délai sans contact, l'écran s'éteint pour économiser l'énergie. Avec l'écran de veille activé, il affiche à la place un ciel étoilé tamisé avec l'heure.",
"Eco Mode", "Eco Mode ",
"Filtre d'itinéraire", "Filtre d'itinéraire",
"Entrer code OACI", "Entrer code OACI",
}; };

View File

@@ -255,7 +255,7 @@ static const char* const I18N_IT[] = {
"Non è stato possibile scaricare o installare l'aggiornamento. Il dispositivo continua a funzionare senza modifiche con il firmware precedente. Controlla la connessione WiFi e riprova più tardi.", "Non è stato possibile scaricare o installare l'aggiornamento. Il dispositivo continua a funzionare senza modifiche con il firmware precedente. Controlla la connessione WiFi e riprova più tardi.",
"Timeout schermo", "Timeout schermo",
"Dopo questo tempo senza tocchi, lo schermo si spegne per risparmiare energia. Con il salvaschermo attivo, mostra invece un cielo stellato attenuato con l'orario.", "Dopo questo tempo senza tocchi, lo schermo si spegne per risparmiare energia. Con il salvaschermo attivo, mostra invece un cielo stellato attenuato con l'orario.",
"Eco Mode", "Eco Mode ",
"Filtro rotta", "Filtro rotta",
"Inserisci ICAO", "Inserisci ICAO",
}; };

View File

@@ -255,7 +255,7 @@ static const char* const I18N_TR[] = {
"Güncelleme indirilemedi veya yüklenemedi. Cihaz değişmeden önceki donanım yazılımıyla çalışmaya devam ediyor. Lütfen WiFi bağlantınızı kontrol edip daha sonra tekrar deneyin.", "Güncelleme indirilemedi veya yüklenemedi. Cihaz değişmeden önceki donanım yazılımıyla çalışmaya devam ediyor. Lütfen WiFi bağlantınızı kontrol edip daha sonra tekrar deneyin.",
"Ekran Zaman Aşımı", "Ekran Zaman Aşımı",
"Bu süre boyunca dokunulmazsa ekran güç tasarrufu için kapanır. Ekran koruyucu açıksa, bunun yerine saatli, sönük bir yıldızlı gökyüzü gösterir.", "Bu süre boyunca dokunulmazsa ekran güç tasarrufu için kapanır. Ekran koruyucu açıksa, bunun yerine saatli, sönük bir yıldızlı gökyüzü gösterir.",
"Eco Mode", "Eco Mode ",
"Rota Filtresi", "Rota Filtresi",
"ICAO Girin", "ICAO Girin",
}; };

View File

@@ -687,6 +687,7 @@ void run(TFT_eSPI& tft) {
drawButton(tft, brightnessBtn, brightnessLabel(SettingsStore::brightnessPercent())); drawButton(tft, brightnessBtn, brightnessLabel(SettingsStore::brightnessPercent()));
drawButton(tft, timeoutBtn, screenTimeoutLabel(SettingsStore::screenTimeoutMinutes())); drawButton(tft, timeoutBtn, screenTimeoutLabel(SettingsStore::screenTimeoutMinutes()));
drawButton(tft, nightDimBtn, I18n::t(StringId::MENU_NIGHT_DIMMING) + onOff(SettingsStore::nightDimmingEnabled())); drawButton(tft, nightDimBtn, I18n::t(StringId::MENU_NIGHT_DIMMING) + onOff(SettingsStore::nightDimmingEnabled()));
drawButton(tft, ecoModeBtn, I18n::t(StringId::MENU_ECO_MODE) + onOff(SettingsStore::ecoModeEnabled()));
drawButton(tft, webuiBtn, I18n::t(StringId::MENU_LOGBOOK_WEBUI)); drawButton(tft, webuiBtn, I18n::t(StringId::MENU_LOGBOOK_WEBUI));
drawButton(tft, backupResetBtn, I18n::t(StringId::MENU_BACKUP_RESET)); drawButton(tft, backupResetBtn, I18n::t(StringId::MENU_BACKUP_RESET));
drawButton(tft, checkUpdateBtn, I18n::t(StringId::MENU_CHECK_UPDATE)); drawButton(tft, checkUpdateBtn, I18n::t(StringId::MENU_CHECK_UPDATE));

View File

@@ -13,12 +13,11 @@ namespace OtaUpdate {
namespace { namespace {
constexpr const char* RELEASES_API_URL = constexpr const char* RELEASES_API_URL =
"https://api.github.com/repos/Eiswolf-BG/eiswolfs-flightradar-CYD/releases/latest"; "https://git.rasmusbendtsen.dk/api/v1/repos/rasmus/flyradar/releases/latest";
// GitHub requires a meaningful User-Agent for API requests // Gitea API uses Authorization: token header instead of
// (otherwise HTTP 403) - same principle as Config::NOMINATIM_USER_AGENT // User-Agent. The token is set in Config::GITEA_API_TOKEN.
// for address search. // Gitea serves downloads directly (no redirect), so
constexpr const char* USER_AGENT = // setFollowRedirects is not needed.
"EiswolfsFlightradarCYD-OTA/1.0 (+https://github.com/Eiswolf-BG/eiswolfs-flightradar-CYD)";
bool parseVersion(const char* s, int& major, int& minor, int& patch) { bool parseVersion(const char* s, int& major, int& minor, int& patch) {
major = minor = patch = 0; major = minor = patch = 0;
@@ -56,7 +55,12 @@ CheckInfo checkForUpdate() {
Serial.println("[OTA] Check failed: http.begin() returned false."); Serial.println("[OTA] Check failed: http.begin() returned false.");
return info; return info;
} }
http.addHeader("User-Agent", USER_AGENT); 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);
}
int code = http.GET(); int code = http.GET();
if (code != HTTP_CODE_OK) { if (code != HTTP_CODE_OK) {
@@ -121,21 +125,25 @@ bool performUpdate(const char* url, void (*onProgress)(uint8_t percent)) {
Serial.printf("[OTA] Starting: url=%s freeHeap=%u RSSI=%ddBm\n", url, Serial.printf("[OTA] Starting: url=%s freeHeap=%u RSSI=%ddBm\n", url,
(unsigned)ESP.getFreeHeap(), WiFi.RSSI()); (unsigned)ESP.getFreeHeap(), WiFi.RSSI());
WiFiClientSecure client; WiFiClientSecure client;
client.setInsecure(); client.setInsecure();
client.setTimeout(15000); client.setTimeout(15000);
// IMPORTANT: GitHub's "browser_download_url" for release assets is NOT // Gitea serves downloads directly (no redirect like GitHub's S3),
// a direct download link, but first returns an HTTP 301/302 redirect // but private repos still need the Authorization header. Use the
// to a signed objects.githubusercontent.com URL. HTTPUpdate does NOT // HTTPClient overload of httpUpdate.update() so we can set it.
// follow redirects by default (HTTPC_DISABLE_FOLLOW_REDIRECTS is the HTTPClient http;
// default) - without this line the download aborts with HTTP_UPDATE_FAILED if (!http.begin(client, url)) {
// because only the redirect response arrives instead of the .bin file. See Serial.println("[OTA] Failed: http.begin() returned false for download URL.");
// e.g. espressif/arduino-esp32#3020. HTTPC_STRICT_FOLLOW_REDIRECTS return false;
// suffices since we only use GET. }
httpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); if (Config::GITEA_API_TOKEN[0]) {
String auth = "token ";
auth += Config::GITEA_API_TOKEN;
http.addHeader("Authorization", auth);
}
// We show a brief success message ourselves after successful installation, // We show a brief success message ourselves after successful installation,
// before the device reboots - see // before the device reboots - see
// menu_screen.cpp::runOtaUpdateScreen(). // menu_screen.cpp::runOtaUpdateScreen().
httpUpdate.rebootOnUpdate(false); httpUpdate.rebootOnUpdate(false);
@@ -156,7 +164,9 @@ bool performUpdate(const char* url, void (*onProgress)(uint8_t percent)) {
} }
}); });
t_httpUpdate_return result = httpUpdate.update(client, url); t_httpUpdate_return result = httpUpdate.update(http);
http.end();
if (result != HTTP_UPDATE_OK) { if (result != HTTP_UPDATE_OK) {
Serial.printf("[OTA] Failed: result=%d error=%d (%s) freeHeap=%u\n", (int)result, Serial.printf("[OTA] Failed: result=%d error=%d (%s) freeHeap=%u\n", (int)result,

View File

@@ -2,7 +2,7 @@
#include <Arduino.h> #include <Arduino.h>
// Firmware update over WiFi (Menu > System > "Check for update") - // Firmware update over WiFi (Menu > System > "Check for update") -
// checks against the latest version in the GitHub repository (Releases API) and // checks against the latest version in a private Gitea repository (Releases API) and
// installs it on the device directly upon confirmation, without cable/ // installs it on the device directly upon confirmation, without cable/
// web flasher. Blocking, synchronous HTTPS calls - only executed on // web flasher. Blocking, synchronous HTTPS calls - only executed on
// explicit button press (no background polling), analogous to // explicit button press (no background polling), analogous to
@@ -23,7 +23,7 @@ namespace OtaUpdate {
char downloadUrl[192] = {0}; char downloadUrl[192] = {0};
}; };
// Queries the GitHub Releases API for the latest release, compares // Queries the Gitea Releases API for the latest release, compares
// its version number (tag name, "v" prefix is ignored) against // its version number (tag name, "v" prefix is ignored) against
// Config::APP_VERSION and looks for the "firmware.bin" attachment in the release. // Config::APP_VERSION and looks for the "firmware.bin" attachment in the release.
// CheckResult::Error on any error along the way (no WiFi, timeout, // CheckResult::Error on any error along the way (no WiFi, timeout,