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 RANGE_STEP_COUNT = 4;
constexpr uint8_t DEFAULT_RANGE_INDEX = 1; 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 ALT_FILTER_COUNT = 3;
constexpr uint8_t DEFAULT_ALT_FILTER_INDEX = 0; constexpr uint8_t DEFAULT_ALT_FILTER_INDEX = 0;

View File

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

View File

@@ -253,8 +253,9 @@ static const char* const I18N_DE[] = {
"Die neue Firmware wurde erfolgreich installiert. Bitte jetzt neu starten, um sie zu verwenden.", "Die neue Firmware wurde erfolgreich installiert. Bitte jetzt neu starten, um sie zu verwenden.",
"Jetzt neu starten", "Jetzt neu starten",
"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",
"Streckenfilter", "Streckenfilter",
"ICAO eingeben", "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.", "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",
"Route Filter", "Route Filter",
"Enter ICAO", "Enter ICAO",
}; };

View File

@@ -253,8 +253,9 @@ static const char* const I18N_ES[] = {
"El nuevo firmware se instaló correctamente. Reinicia ahora para empezar a usarlo.", "El nuevo firmware se instaló correctamente. Reinicia ahora para empezar a usarlo.",
"Reiniciar ahora", "Reiniciar ahora",
"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",
"Filtro de ruta", "Filtro de ruta",
"Ingresa OACI", "Ingresa OACI",
}; };

View File

@@ -253,8 +253,9 @@ static const char* const I18N_FR[] = {
"Le nouveau micrologiciel a été installé avec succès. Veuillez redémarrer maintenant pour l'utiliser.", "Le nouveau micrologiciel a été installé avec succès. Veuillez redémarrer maintenant pour l'utiliser.",
"Redémarrer maintenant", "Redémarrer maintenant",
"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",
"Filtre d'itinéraire", "Filtre d'itinéraire",
"Entrer code OACI", "Entrer code OACI",
}; };

View File

@@ -253,8 +253,9 @@ static const char* const I18N_IT[] = {
"Il nuovo firmware è stato installato correttamente. Riavvia ora per iniziare a usarlo.", "Il nuovo firmware è stato installato correttamente. Riavvia ora per iniziare a usarlo.",
"Riavvia ora", "Riavvia ora",
"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",
"Filtro rotta", "Filtro rotta",
"Inserisci ICAO", "Inserisci ICAO",
}; };

View File

@@ -253,8 +253,9 @@ static const char* const I18N_TR[] = {
"Yeni donanım yazılımı başarıyla yüklendi. Kullanmaya başlamak için lütfen şimdi yeniden başlatın.", "Yeni donanım yazılımı başarıyla yüklendi. Kullanmaya başlamak için lütfen şimdi yeniden başlatın.",
"Şimdi yeniden başlat", "Şimdi yeniden başlat",
"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",
"Rota Filtresi", "Rota Filtresi",
"ICAO Girin", "ICAO Girin",
}; };

View File

@@ -664,23 +664,20 @@ void loop() {
lastInteractionMs = millis(); lastInteractionMs = millis();
if (screenDimmed) { if (screenDimmed) {
bool shouldNightDim = SettingsStore::nightDimmingEnabled() && isNightDimHours(); bool shouldNightDim = SettingsStore::nightDimmingEnabled() && isNightDimHours();
ledcWrite(BACKLIGHT_PWM_CHANNEL, shouldNightDim ? nightDimBacklightPwm() : normalBacklightPwm()); ledcWrite(BACKLIGHT_PWM_CHANNEL, shouldNightDim ? nightDimBacklightPwm() : normalBacklightPwm());
nightDimActive = shouldNightDim; nightDimActive = shouldNightDim;
screenDimmed = false; screenDimmed = false;
tapped = false; tapped = false;
if (screensaverShowing) { 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; screensaverShowing = false;
drawHeader(); drawHeader();
updateStatusLine(); updateStatusLine();
forceRedraw = true; forceRedraw = true;
} }
NetTask::setFetchInterval(Config::FETCH_INTERVAL_MS);
WiFi.setSleep(false);
} }
} }
@@ -746,29 +743,19 @@ forceRedraw = false;
if (!screenDimmed && timeoutMin > 0) { if (!screenDimmed && timeoutMin > 0) {
uint32_t timeoutMs = (uint32_t)timeoutMin * 60000UL; uint32_t timeoutMs = (uint32_t)timeoutMin * 60000UL;
if (nowMs - lastInteractionMs >= timeoutMs) { if (nowMs - lastInteractionMs >= timeoutMs) {
if (SettingsStore::screensaverEnabled()) { 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; screensaverShowing = true;
ledcWrite(BACKLIGHT_PWM_CHANNEL, nightDimBacklightPwm()); ledcWrite(BACKLIGHT_PWM_CHANNEL, nightDimBacklightPwm());
tft.fillScreen(TFT_BLACK); tft.fillScreen(TFT_BLACK);
MenuStars::reset(); MenuStars::reset();
lastScreensaverClockMs = 0; // sofortiges erstes Zeichnen erzwingen lastScreensaverClockMs = 0;
} else { } 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); ledcWrite(BACKLIGHT_PWM_CHANNEL, 0);
} }
if (SettingsStore::ecoModeEnabled()) {
NetTask::setFetchInterval(Config::ECO_FETCH_INTERVAL_MS);
WiFi.setSleep(true);
}
screenDimmed = true; screenDimmed = true;
} }
} }

View File

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

View File

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

View File

@@ -1,19 +1,17 @@
#pragma once #pragma once
#include <Arduino.h>
// Hintergrund-Task fuer WLAN-Status, Standortbestimmung und ADS-B-Abfragen. // Hintergrund-Task fuer WLAN-Status, Standortbestimmung und ADS-B-Abfragen.
// Laeuft auf Core 0, damit Core 1 (Rendering + Touch im main-Loop) nie durch // Laeuft auf Core 0, damit Core 1 (Rendering + Touch im main-Loop) nie durch
// Netzwerk-Wartezeiten (WLAN, HTTPS) blockiert wird. // Netzwerk-Wartezeiten (WLAN, HTTPS) blockiert wird.
namespace NetTask { namespace NetTask {
// Startet den Hintergrund-Task. Muss erst NACH WifiMgr::init() und
// LocationManager::init() aufgerufen werden.
void begin(); 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 pause();
void resume(); 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 nightDimmingOn = true;
bool screensaverOn = false; bool screensaverOn = false;
bool hideGroundVehiclesOn = true; bool hideGroundVehiclesOn = true;
bool ecoModeOn = false;
uint8_t languageIdx = 0; uint8_t languageIdx = 0;
uint8_t unitsModeVal = 0; uint8_t unitsModeVal = 0;
@@ -68,8 +69,10 @@ if (key == "range_index") {
nightDimmingOn = (value.toInt() != 0); nightDimmingOn = (value.toInt() != 0);
} else if (key == "screensaver") { } else if (key == "screensaver") {
screensaverOn = (value.toInt() != 0); screensaverOn = (value.toInt() != 0);
} else if (key == "hide_ground_vehicles") { } else if (key == "hide_ground_vehicles") {
hideGroundVehiclesOn = (value.toInt() != 0); hideGroundVehiclesOn = (value.toInt() != 0);
} else if (key == "eco_mode") {
ecoModeOn = (value.toInt() != 0);
} else if (key == "language") { } else if (key == "language") {
int v = value.toInt(); int v = value.toInt();
if (v >= 0 && v <= 5) languageIdx = (uint8_t)v; 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("night_dimming=%d\n", nightDimmingOn ? 1 : 0);
f.printf("screensaver=%d\n", screensaverOn ? 1 : 0); f.printf("screensaver=%d\n", screensaverOn ? 1 : 0);
f.printf("hide_ground_vehicles=%d\n", hideGroundVehiclesOn ? 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("language=%d\n", languageIdx);
f.printf("units_mode=%d\n", unitsModeVal); f.printf("units_mode=%d\n", unitsModeVal);
f.close(); f.close();
@@ -253,6 +257,13 @@ void setHideGroundVehicles(bool on) {
save(); save();
} }
bool ecoModeEnabled() { return ecoModeOn; }
void setEcoModeEnabled(bool on) {
ecoModeOn = on;
save();
}
uint8_t language() { return languageIdx; } uint8_t language() { return languageIdx; }
void setLanguage(uint8_t lang) { void setLanguage(uint8_t lang) {

View File

@@ -59,9 +59,12 @@ uint8_t rangeIndex();
bool screensaverEnabled(); bool screensaverEnabled();
void setScreensaverEnabled(bool on); void setScreensaverEnabled(bool on);
bool hideGroundVehicles(); bool hideGroundVehicles();
void setHideGroundVehicles(bool on); 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. // Sprache der Benutzeroberflaeche: 0=EN,1=DE,2=FR,3=TR,4=ES,5=IT.
uint8_t language(); uint8_t language();
void setLanguage(uint8_t lang); void setLanguage(uint8_t lang);