Add Eco Mode power saving feature (60s ADS-B poll, WiFi sleep)

This commit is contained in:
2026-08-15 22:53:05 +02:00
parent b807b51f1f
commit df8330d1e3
14 changed files with 71 additions and 52 deletions

View File

@@ -50,6 +50,11 @@ constexpr float RANGE_STEPS_KM[] = {10.0f, 25.0f, 50.0f, 100.0f};
constexpr uint8_t RANGE_STEP_COUNT = 4;
constexpr uint8_t DEFAULT_RANGE_INDEX = 1;
// Eco-Mode: reduziert die ADS-B-Abfragefrequenz auf dieses Intervall
// (statt FETCH_INTERVAL_MS), wenn der Bildschirm durch Inaktivitaets-
// Timeout abgeschaltet ist (siehe main.cpp).
constexpr uint32_t ECO_FETCH_INTERVAL_MS = 60000;
constexpr uint8_t ALT_FILTER_COUNT = 3;
constexpr uint8_t DEFAULT_ALT_FILTER_INDEX = 0;

View File

@@ -354,6 +354,7 @@ enum class StringId : uint8_t {
TIMEOUT_SCREEN_TITLE,
TIMEOUT_SCREENSAVER_DESC,
MENU_ECO_MODE,
MENU_ROUTE_FILTER,
ROUTE_FILTER_PROMPT,

View File

@@ -255,6 +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.",
"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.",
"Eco Mode",
"Streckenfilter",
"ICAO eingeben",
};

View File

@@ -256,6 +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.",
"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.",
"Eco Mode",
"Route Filter",
"Enter ICAO",
};

View File

@@ -255,6 +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.",
"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.",
"Eco Mode",
"Filtro de ruta",
"Ingresa OACI",
};

View File

@@ -255,6 +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.",
"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.",
"Eco Mode",
"Filtre d'itinéraire",
"Entrer code OACI",
};

View File

@@ -255,6 +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.",
"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.",
"Eco Mode",
"Filtro rotta",
"Inserisci ICAO",
};

View File

@@ -255,6 +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.",
"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.",
"Eco Mode",
"Rota Filtresi",
"ICAO Girin",
};

View File

@@ -671,16 +671,13 @@ void loop() {
tapped = false;
if (screensaverShowing) {
// Der Ruhebildschirm hat den kompletten Bildschirminhalt
// ueberschrieben (Sternenhimmel + Uhrzeit) - anders als beim
// normalen Timeout (nur Backlight aus, Inhalt blieb im
// TFT-Speicher unveraendert stehen) muss hier explizit neu
// gezeichnet werden.
screensaverShowing = false;
drawHeader();
updateStatusLine();
forceRedraw = true;
}
NetTask::setFetchInterval(Config::FETCH_INTERVAL_MS);
WiFi.setSleep(false);
}
}
@@ -747,28 +744,18 @@ forceRedraw = false;
uint32_t timeoutMs = (uint32_t)timeoutMin * 60000UL;
if (nowMs - lastInteractionMs >= timeoutMs) {
if (SettingsStore::screensaverEnabled()) {
// Ruhebildschirm statt komplett dunkel (Menue > System >
// Ruhebildschirm, AUS per Default) - gedimmter
// Sternenhimmel mit grosser Uhrzeit statt des Backlights
// ganz aus, fuer alle, die trotz Inaktivitaets-Timeout noch
// etwas auf dem Display sehen wollen.
screensaverShowing = true;
ledcWrite(BACKLIGHT_PWM_CHANNEL, nightDimBacklightPwm());
tft.fillScreen(TFT_BLACK);
MenuStars::reset();
lastScreensaverClockMs = 0; // sofortiges erstes Zeichnen erzwingen
lastScreensaverClockMs = 0;
} else {
// Bewusst ganz aus (0) statt nur gedimmt - fuer den
// Bildschirm-Timeout (Menue > System > Timeout). Dimmen
// uebernimmt bereits der separate Nachtmodus
// (updateNightDimming()); der Inaktivitaets-Timeout soll das
// Display wirklich abschalten, z.B. fuer Plane-Spotter, die nur
// das Flugbuch mitlaufen lassen wollen, ohne den Screen zu
// brauchen. Ein Antippen weckt ihn ueber den screenDimmed-Zweig
// oben wieder auf die eingestellte Helligkeit (bzw. Nachtmodus-
// Helligkeit, falls gerade Nachtstunden sind).
ledcWrite(BACKLIGHT_PWM_CHANNEL, 0);
}
if (SettingsStore::ecoModeEnabled()) {
NetTask::setFetchInterval(Config::ECO_FETCH_INTERVAL_MS);
WiFi.setSleep(true);
}
screenDimmed = true;
}
}

View File

@@ -93,7 +93,7 @@ namespace {
// Ruhebildschirm-Umschalter, der zwischenzeitlich hier eine eigene
// Zeile hatte, ist in den neuen Bildschirm-Timeout-Screen umgezogen
// (siehe timeout_screen.cpp) - Zeilenzahl bleibt dadurch bei 10.
constexpr uint8_t SYSTEM_ROW_COUNT = 10;
constexpr uint8_t SYSTEM_ROW_COUNT = 11; // +1 fuer Eco-Mode
constexpr int16_t SYSTEM_ROW_GAP = 4;
constexpr int16_t SYSTEM_START_Y = 18;
constexpr int16_t SYSTEM_END_Y = Config::SCREEN_HEIGHT - 10;
@@ -672,17 +672,18 @@ void run(TFT_eSPI& tft) {
Rect brightnessBtn = systemRowRect(2);
Rect timeoutBtn = systemRowRect(3);
Rect nightDimBtn = systemRowRect(4);
Rect webuiBtn = systemRowRect(5);
Rect webuiBtn = systemRowRect(6);
// "Sicherung & Reset" (Backup/Restore/Werksreset, siehe
// Page::BackupReset unten) und "Nach Update suchen" stehen
// bewusst direkt ueber "Info", das seinerseits als letzter
// Punkt direkt ueber dem Zurueck-Button steht - so bleiben
// beide Positionen stabil, egal wie viele weitere Punkte davor
// noch dazukommen.
Rect backupResetBtn = systemRowRect(6);
Rect checkUpdateBtn = systemRowRect(7);
Rect aboutBtn = systemRowRect(8);
Rect backBtn = systemRowRect(9);
Rect ecoModeBtn = systemRowRect(5);
Rect backupResetBtn = systemRowRect(7);
Rect checkUpdateBtn = systemRowRect(8);
Rect aboutBtn = systemRowRect(9);
Rect backBtn = systemRowRect(10);
drawButton(tft, calibBtn, I18n::t(StringId::MENU_CALIBRATE));
@@ -725,14 +726,16 @@ void run(TFT_eSPI& tft) {
TimeoutScreen::run(tft);
} else if (nightDimBtn.contains(tap.x, tap.y)) {
SettingsStore::setNightDimmingEnabled(!SettingsStore::nightDimmingEnabled());
} else if (ecoModeBtn.contains(tap.x, tap.y)) {
SettingsStore::setEcoModeEnabled(!SettingsStore::ecoModeEnabled());
} else if (webuiBtn.contains(tap.x, tap.y)) {
WebUiScreen::run(tft);
} else if (backupResetBtn.contains(tap.x, tap.y)) {
page = Page::BackupReset;
} else if (checkUpdateBtn.contains(tap.x, tap.y)) {
runOtaUpdateScreen(tft);
} else if (aboutBtn.contains(tap.x, tap.y)) {
AboutScreen::run(tft);
} else if (webuiBtn.contains(tap.x, tap.y)) {
WebUiScreen::run(tft);
} else if (backBtn.contains(tap.x, tap.y)) {
page = Page::Main;
}

View File

@@ -21,6 +21,7 @@ namespace NetTask {
namespace {
TaskHandle_t taskHandle = nullptr;
uint32_t lastFetchMs = 0;
uint32_t fetchIntervalMs = Config::FETCH_INTERVAL_MS;
Aircraft tempTable[Config::MAX_TRACKED_AIRCRAFT];
@@ -42,7 +43,7 @@ namespace {
AircraftDetails::update();
Weather::update();
if (millis() - lastFetchMs >= Config::FETCH_INTERVAL_MS) {
if (millis() - lastFetchMs >= fetchIntervalMs) {
lastFetchMs = millis();
if (WifiMgr::getState() == WifiMgr::State::Connected) {
@@ -103,4 +104,8 @@ void resume() {
if (taskHandle) vTaskResume(taskHandle);
}
void setFetchInterval(uint32_t ms) {
fetchIntervalMs = ms;
}
}

View File

@@ -1,19 +1,17 @@
#pragma once
#include <Arduino.h>
// Hintergrund-Task fuer WLAN-Status, Standortbestimmung und ADS-B-Abfragen.
// Laeuft auf Core 0, damit Core 1 (Rendering + Touch im main-Loop) nie durch
// Netzwerk-Wartezeiten (WLAN, HTTPS) blockiert wird.
namespace NetTask {
// Startet den Hintergrund-Task. Muss erst NACH WifiMgr::init() und
// LocationManager::init() aufgerufen werden.
void begin();
// Haelt den Hintergrund-Task an bzw. setzt ihn fort - z.B. waehrend
// eines OTA-Updates, da der ESP32 nur eine WLAN-Funkeinheit/einen
// gemeinsamen Netzwerk-Stack fuer beide Cores hat und gleichzeitige
// Anfragen (ADS-B-Polling im Hintergrund vs. OTA-Download) sich in die
// Quere kommen koennen. Beide sicher mehrfach aufrufbar bzw. ohne
// Wirkung, falls der Task noch nicht gestartet ist.
void pause();
void resume();
// Eco-Mode: aendert das ADS-B-Abfrage-Intervall zur Laufzeit.
// Wird von main.cpp beim Bildschirm-Timeout (screenDimmed) auf
// Config::ECO_FETCH_INTERVAL_MS und beim Aufwachen wieder auf
// Config::FETCH_INTERVAL_MS gesetzt.
void setFetchInterval(uint32_t ms);
}

View File

@@ -25,6 +25,7 @@ namespace {
bool nightDimmingOn = true;
bool screensaverOn = false;
bool hideGroundVehiclesOn = true;
bool ecoModeOn = false;
uint8_t languageIdx = 0;
uint8_t unitsModeVal = 0;
@@ -70,6 +71,8 @@ if (key == "range_index") {
screensaverOn = (value.toInt() != 0);
} else if (key == "hide_ground_vehicles") {
hideGroundVehiclesOn = (value.toInt() != 0);
} else if (key == "eco_mode") {
ecoModeOn = (value.toInt() != 0);
} else if (key == "language") {
int v = value.toInt();
if (v >= 0 && v <= 5) languageIdx = (uint8_t)v;
@@ -134,6 +137,7 @@ void save() {
f.printf("night_dimming=%d\n", nightDimmingOn ? 1 : 0);
f.printf("screensaver=%d\n", screensaverOn ? 1 : 0);
f.printf("hide_ground_vehicles=%d\n", hideGroundVehiclesOn ? 1 : 0);
f.printf("eco_mode=%d\n", ecoModeOn ? 1 : 0);
f.printf("language=%d\n", languageIdx);
f.printf("units_mode=%d\n", unitsModeVal);
f.close();
@@ -253,6 +257,13 @@ void setHideGroundVehicles(bool on) {
save();
}
bool ecoModeEnabled() { return ecoModeOn; }
void setEcoModeEnabled(bool on) {
ecoModeOn = on;
save();
}
uint8_t language() { return languageIdx; }
void setLanguage(uint8_t lang) {

View File

@@ -62,6 +62,9 @@ uint8_t rangeIndex();
bool hideGroundVehicles();
void setHideGroundVehicles(bool on);
bool ecoModeEnabled();
void setEcoModeEnabled(bool on);
// Sprache der Benutzeroberflaeche: 0=EN,1=DE,2=FR,3=TR,4=ES,5=IT.
uint8_t language();
void setLanguage(uint8_t lang);