Implement silent reboot

This commit is contained in:
jpirnay
2026-05-19 19:38:04 +02:00
parent ffa8ab0911
commit c55a36c702
12 changed files with 150 additions and 81 deletions
@@ -2,7 +2,6 @@
#include <Epub.h>
#include <GfxRenderer.h>
#include <HalClock.h>
#include <HalStorage.h>
#include <I18n.h>
#include <Logging.h>
@@ -17,6 +16,7 @@
#include "MappedInputManager.h"
#include "OpdsFormatLabel.h"
#include "SilentRestart.h"
#include "activities/network/WifiSelectionActivity.h"
#include "activities/util/KeyboardEntryActivity.h"
#include "components/UITheme.h"
@@ -133,12 +133,16 @@ void OpdsBookBrowserActivity::onEnter() {
void OpdsBookBrowserActivity::onExit() {
Activity::onExit();
HalClock::wifiOff();
entryOffsets.clear();
navigationHistory.clear();
formatSelectionLabels.clear();
Storage.remove("/.tmp_opds_cache.dat");
if (WiFi.getMode() != WIFI_MODE_NULL) {
WiFi.disconnect(false);
delay(30);
silentRestart();
}
}
void OpdsBookBrowserActivity::loop() {
@@ -701,8 +705,7 @@ void OpdsBookBrowserActivity::onWifiSelectionComplete(const bool connected) {
requestUpdate(true);
fetchFeed(currentPath);
} else {
WiFi.disconnect();
WiFi.mode(WIFI_OFF);
// Leave WiFi up; onExit's silent reboot handles teardown without further fragmenting.
state = BrowserState::ERROR;
errorMessage = tr(STR_WIFI_CONN_FAILED);
requestUpdate();
@@ -7,6 +7,7 @@
#include <esp_task_wdt.h>
#include "MappedInputManager.h"
#include "SilentRestart.h"
#include "WifiSelectionActivity.h"
#include "activities/network/SignalStrengthWidget.h"
#include "components/UITheme.h"
@@ -54,14 +55,11 @@ void CalibreConnectActivity::onEnter() {
void CalibreConnectActivity::onExit() {
Activity::onExit();
stopWebServer();
MDNS.end();
delay(50);
WiFi.disconnect(false);
delay(30);
WiFi.mode(WIFI_OFF);
delay(30);
if (WiFi.getMode() != WIFI_MODE_NULL) {
WiFi.disconnect(false);
delay(30);
silentRestart();
}
}
void CalibreConnectActivity::onWifiSelectionComplete(const bool connected) {
@@ -3,7 +3,6 @@
#include <DNSServer.h>
#include <ESPmDNS.h>
#include <GfxRenderer.h>
#include <HalClock.h>
#include <I18n.h>
#include <WiFi.h>
#include <esp_task_wdt.h>
@@ -12,6 +11,7 @@
#include "MappedInputManager.h"
#include "NetworkModeSelectionActivity.h"
#include "SilentRestart.h"
#include "WifiSelectionActivity.h"
#include "activities/network/CalibreConnectActivity.h"
#include "activities/network/SignalStrengthWidget.h"
@@ -64,45 +64,17 @@ void CrossPointWebServerActivity::onEnter() {
void CrossPointWebServerActivity::onExit() {
Activity::onExit();
LOG_DBG("WEBACT", "Free heap at onExit start: %d bytes", ESP.getFreeHeap());
state = WebServerActivityState::SHUTTING_DOWN;
// Stop the web server first (before disconnecting WiFi)
stopWebServer();
// Stop mDNS
MDNS.end();
// Stop DNS server if running (AP mode)
if (dnsServer) {
LOG_DBG("WEBACT", "Stopping DNS server...");
dnsServer->stop();
delete dnsServer;
dnsServer = nullptr;
}
// Brief wait for LWIP stack to flush pending packets
delay(50);
// Disconnect WiFi gracefully
if (isApMode) {
LOG_DBG("WEBACT", "Stopping WiFi AP...");
WiFi.softAPdisconnect(true);
// Skip reboot if WiFi was never activated (e.g. user backed out of mode selection).
if (WiFi.getMode() != WIFI_MODE_NULL) {
if (isApMode) {
WiFi.softAPdisconnect(true);
} else {
WiFi.disconnect(false);
}
delay(30);
WiFi.mode(WIFI_OFF);
delay(30);
} else {
HalClock::wifiOff();
}
LOG_DBG("WEBACT", "Free heap at onExit end: %d bytes", ESP.getFreeHeap());
requestUpdate();
if (webServerStarted) {
GUI.drawPopup(renderer, tr(STR_RESTART_DEVICE));
LOG_DBG("WEBACT", "Restarting device after webserver exit to regain heap...");
ESP.restart();
silentRestart();
}
}
@@ -271,15 +243,6 @@ void CrossPointWebServerActivity::startWebServer() {
}
}
void CrossPointWebServerActivity::stopWebServer() {
if (webServer && webServer->isRunning()) {
LOG_DBG("WEBACT", "Stopping web server...");
webServer->stop();
LOG_DBG("WEBACT", "Web server stopped");
}
webServer.reset();
}
void CrossPointWebServerActivity::loop() {
// Handle different states
if (state == WebServerActivityState::SERVER_RUNNING) {
@@ -55,7 +55,6 @@ class CrossPointWebServerActivity final : public Activity {
void onWifiSelectionComplete(bool connected);
void startAccessPoint();
void startWebServer();
void stopWebServer();
public:
explicit CrossPointWebServerActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
+25 -7
View File
@@ -8,10 +8,12 @@
#include <WiFi.h>
#include <esp_heap_caps.h>
#include <esp_system.h>
#include <esp_wifi.h>
#include "KOReaderCredentialStore.h"
#include "KOReaderDocumentId.h"
#include "MappedInputManager.h"
#include "SilentRestart.h"
#include "activities/network/WifiSelectionActivity.h"
#include "components/UITheme.h"
#include "fontIds.h"
@@ -140,7 +142,8 @@ bool KOReaderSyncActivity::handleAutoPushPreflight() {
LOG_DBG("KOSync", "AUTO_PUSH skipped: remote %.4f >= local %.4f", warmupProgress.percentage,
localProgress.percentage);
KOReaderSyncClient::endPersistentSession();
HalClock::wifiOff(true);
// Drop the radio while user reads the result; full teardown happens at silent reboot.
esp_wifi_stop();
APP_STATE.koReaderSyncSession.outcome = KOReaderSyncOutcomeState::UPLOAD_COMPLETE;
APP_STATE.saveToFile();
resumeReader(KOReaderSyncOutcomeState::UPLOAD_COMPLETE);
@@ -180,7 +183,7 @@ void KOReaderSyncActivity::performFetchAndCompare() {
if (syncIntent == KOReaderSyncIntentState::AUTO_PULL) {
// Auto-pull at book open: nothing to apply, just open the book with local progress.
KOReaderSyncClient::endPersistentSession();
HalClock::wifiOff(true);
esp_wifi_stop();
resumeReader(KOReaderSyncOutcomeState::CANCELLED);
return;
}
@@ -230,7 +233,7 @@ void KOReaderSyncActivity::performFetchAndCompare() {
if (!ensureRemotePositionMapped()) {
if (syncIntent == KOReaderSyncIntentState::AUTO_PULL) {
// Auto-pull was best-effort. Fail silently and just open the book.
HalClock::wifiOff(true);
esp_wifi_stop();
resumeReader(KOReaderSyncOutcomeState::CANCELLED);
return;
}
@@ -258,7 +261,7 @@ void KOReaderSyncActivity::performFetchAndCompare() {
if (syncIntent == KOReaderSyncIntentState::AUTO_PULL) {
// Auto-pull skips the success-screen dwell — the reader will render the new
// position immediately, which is the only visible feedback the user needs.
HalClock::wifiOff(true);
esp_wifi_stop();
resumeReader(KOReaderSyncOutcomeState::APPLIED_REMOTE);
return;
}
@@ -418,7 +421,8 @@ void KOReaderSyncActivity::performUpload() {
logSyncMemSnapshot("after_updateProgress");
if (result != KOReaderSyncClient::OK) {
HalClock::wifiOff(true);
// Drop the radio while user reads the result; full teardown happens at silent reboot.
esp_wifi_stop();
{
RenderLock lock(*this);
state = SYNC_FAILED;
@@ -435,7 +439,8 @@ void KOReaderSyncActivity::performUpload() {
return;
}
HalClock::wifiOff(true);
// Drop the radio while user reads the success screen; full teardown happens at silent reboot.
esp_wifi_stop();
APP_STATE.koReaderSyncSession.outcome = KOReaderSyncOutcomeState::UPLOAD_COMPLETE;
APP_STATE.saveToFile();
if (syncIntent == KOReaderSyncIntentState::AUTO_PUSH) {
@@ -465,6 +470,9 @@ void KOReaderSyncActivity::onEnter() {
return;
}
// Past this point every path uses WiFi.
wifiActivated = true;
// Check if already connected (e.g. from settings page auth)
if (WiFi.status() == WL_CONNECTED) {
LOG_DBG("KOSync", "Already connected to WiFi");
@@ -483,9 +491,18 @@ void KOReaderSyncActivity::onExit() {
logSyncMemSnapshot("onExit_before_cleanup");
KOReaderSyncClient::endPersistentSession();
HalClock::wifiOff(true);
releaseEpubForMapping();
logSyncMemSnapshot("onExit_after_cleanup");
if (wifiActivated) {
WiFi.disconnect(false);
delay(30);
if (exitToHomeAfterSync) {
silentRestart();
} else {
silentRestartToReader();
}
}
}
void KOReaderSyncActivity::closeCancelled() {
@@ -526,6 +543,7 @@ void KOReaderSyncActivity::resumeReader(const KOReaderSyncOutcomeState outcome,
// the user just left would be jarring. The session state is consumed and cleared by the
// home destination's normal flow (no reader to apply it to in this case).
const bool exitToHome = sync.exitToHomeAfterSync;
exitToHomeAfterSync = exitToHome;
if (exitToHome) {
sync.clear();
}
@@ -102,6 +102,16 @@ class KOReaderSyncActivity final : public Activity {
unsigned long uploadCompleteTime = 0;
bool closeRequested = false;
// Tracks whether this session activated WiFi. Set in onEnter past the credentials
// check; checked in onExit to decide whether to silent-reboot. Can't rely on
// WiFi.getMode() because intermediate paths call esp_wifi_stop() to drop the
// radio while user reads the result, which makes WiFi.getMode() return WIFI_MODE_NULL.
bool wifiActivated = false;
// Captured from sync.exitToHomeAfterSync in resumeReader() so onExit can route the
// silent reboot to home instead of the reader when reader-close auto-sync triggered.
bool exitToHomeAfterSync = false;
void onWifiSelectionComplete(bool success);
void performSync();
bool calculateDocumentHash();
@@ -12,6 +12,7 @@
#include "MappedInputManager.h"
#include "SdCardFontGlobals.h"
#include "SilentRestart.h"
#include "activities/network/WifiSelectionActivity.h"
#include "activities/util/ConfirmationActivity.h"
#include "components/UITheme.h"
@@ -32,10 +33,12 @@ void FontDownloadActivity::onEnter() {
void FontDownloadActivity::onExit() {
Activity::onExit();
WiFi.disconnect(false);
delay(100);
WiFi.mode(WIFI_OFF);
delay(100);
if (WiFi.getMode() != WIFI_MODE_NULL) {
WiFi.disconnect(false);
delay(30);
silentRestart();
}
}
void FontDownloadActivity::onWifiSelectionComplete(const bool success) {
@@ -1,13 +1,13 @@
#include "KOReaderAuthActivity.h"
#include <GfxRenderer.h>
#include <HalClock.h>
#include <I18n.h>
#include <WiFi.h>
#include "KOReaderCredentialStore.h"
#include "KOReaderSyncClient.h"
#include "MappedInputManager.h"
#include "SilentRestart.h"
#include "activities/network/WifiSelectionActivity.h"
#include "components/UITheme.h"
#include "fontIds.h"
@@ -104,7 +104,11 @@ void KOReaderAuthActivity::onEnter() {
void KOReaderAuthActivity::onExit() {
Activity::onExit();
HalClock::wifiOff();
if (WiFi.getMode() != WIFI_MODE_NULL) {
WiFi.disconnect(false);
delay(30);
silentRestart();
}
}
void KOReaderAuthActivity::render(RenderLock&&) {
@@ -1,11 +1,11 @@
#include "OtaUpdateActivity.h"
#include <GfxRenderer.h>
#include <HalClock.h>
#include <I18n.h>
#include <WiFi.h>
#include "MappedInputManager.h"
#include "SilentRestart.h"
#include "activities/network/WifiSelectionActivity.h"
#include "components/UITheme.h"
#include "fontIds.h"
@@ -68,7 +68,11 @@ void OtaUpdateActivity::onEnter() {
void OtaUpdateActivity::onExit() {
Activity::onExit();
HalClock::wifiOff();
if (WiFi.getMode() != WIFI_MODE_NULL) {
WiFi.disconnect(false);
delay(30);
silentRestart();
}
}
void OtaUpdateActivity::render(RenderLock&&) {
+7 -3
View File
@@ -14,6 +14,7 @@
#include <vector>
#include "MappedInputManager.h"
#include "SilentRestart.h"
#include "WeatherSettingsActivity.h"
#include "activities/network/WifiSelectionActivity.h"
#include "components/UITheme.h"
@@ -222,7 +223,11 @@ void WeatherActivity::onExit() {
// Weather screen is always landscape; restore app UI to portrait on exit.
renderer.setOrientation(GfxRenderer::Orientation::Portrait);
WiFi.mode(WIFI_OFF);
if (WiFi.getMode() != WIFI_MODE_NULL) {
WiFi.disconnect(false);
delay(30);
silentRestart();
}
}
void WeatherActivity::loadAndDisplay() {
@@ -298,8 +303,7 @@ void WeatherActivity::onWifiSelectionComplete(bool connected) {
LOG_DBG("WEA", "state -> CHECK_WIFI (waitStart=%lu)", (unsigned long)wifiWaitStartedAtMs);
requestUpdate(true);
} else {
WiFi.disconnect();
WiFi.mode(WIFI_OFF);
// Leave WiFi up; onExit's silent reboot handles teardown without further fragmenting.
state = State::ERROR;
errorMessage = tr(STR_WIFI_CONN_FAILED);
requestUpdate();