Implement silent reboot
This commit is contained in:
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// ESP.restart() with an RTC_NOINIT flag that survives the reboot, so setup()
|
||||||
|
// skips the boot splash and routes straight to a destination. Used to clear
|
||||||
|
// heap fragmentation accumulated during a wifi session — WiFi/LWIP/netif
|
||||||
|
// teardown scatters long-lived allocations across the heap, leaving ~50KB of
|
||||||
|
// contiguous space unrecoverable without a reboot.
|
||||||
|
|
||||||
|
void silentRestart(); // home screen
|
||||||
|
void silentRestartToReader(); // currently-open EPUB (APP_STATE.openEpubPath)
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include <Epub.h>
|
#include <Epub.h>
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HalClock.h>
|
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
#include <I18n.h>
|
#include <I18n.h>
|
||||||
#include <Logging.h>
|
#include <Logging.h>
|
||||||
@@ -17,6 +16,7 @@
|
|||||||
|
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
#include "OpdsFormatLabel.h"
|
#include "OpdsFormatLabel.h"
|
||||||
|
#include "SilentRestart.h"
|
||||||
#include "activities/network/WifiSelectionActivity.h"
|
#include "activities/network/WifiSelectionActivity.h"
|
||||||
#include "activities/util/KeyboardEntryActivity.h"
|
#include "activities/util/KeyboardEntryActivity.h"
|
||||||
#include "components/UITheme.h"
|
#include "components/UITheme.h"
|
||||||
@@ -133,12 +133,16 @@ void OpdsBookBrowserActivity::onEnter() {
|
|||||||
void OpdsBookBrowserActivity::onExit() {
|
void OpdsBookBrowserActivity::onExit() {
|
||||||
Activity::onExit();
|
Activity::onExit();
|
||||||
|
|
||||||
HalClock::wifiOff();
|
|
||||||
|
|
||||||
entryOffsets.clear();
|
entryOffsets.clear();
|
||||||
navigationHistory.clear();
|
navigationHistory.clear();
|
||||||
formatSelectionLabels.clear();
|
formatSelectionLabels.clear();
|
||||||
Storage.remove("/.tmp_opds_cache.dat");
|
Storage.remove("/.tmp_opds_cache.dat");
|
||||||
|
|
||||||
|
if (WiFi.getMode() != WIFI_MODE_NULL) {
|
||||||
|
WiFi.disconnect(false);
|
||||||
|
delay(30);
|
||||||
|
silentRestart();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpdsBookBrowserActivity::loop() {
|
void OpdsBookBrowserActivity::loop() {
|
||||||
@@ -701,8 +705,7 @@ void OpdsBookBrowserActivity::onWifiSelectionComplete(const bool connected) {
|
|||||||
requestUpdate(true);
|
requestUpdate(true);
|
||||||
fetchFeed(currentPath);
|
fetchFeed(currentPath);
|
||||||
} else {
|
} else {
|
||||||
WiFi.disconnect();
|
// Leave WiFi up; onExit's silent reboot handles teardown without further fragmenting.
|
||||||
WiFi.mode(WIFI_OFF);
|
|
||||||
state = BrowserState::ERROR;
|
state = BrowserState::ERROR;
|
||||||
errorMessage = tr(STR_WIFI_CONN_FAILED);
|
errorMessage = tr(STR_WIFI_CONN_FAILED);
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <esp_task_wdt.h>
|
#include <esp_task_wdt.h>
|
||||||
|
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
|
#include "SilentRestart.h"
|
||||||
#include "WifiSelectionActivity.h"
|
#include "WifiSelectionActivity.h"
|
||||||
#include "activities/network/SignalStrengthWidget.h"
|
#include "activities/network/SignalStrengthWidget.h"
|
||||||
#include "components/UITheme.h"
|
#include "components/UITheme.h"
|
||||||
@@ -54,14 +55,11 @@ void CalibreConnectActivity::onEnter() {
|
|||||||
void CalibreConnectActivity::onExit() {
|
void CalibreConnectActivity::onExit() {
|
||||||
Activity::onExit();
|
Activity::onExit();
|
||||||
|
|
||||||
stopWebServer();
|
if (WiFi.getMode() != WIFI_MODE_NULL) {
|
||||||
MDNS.end();
|
|
||||||
|
|
||||||
delay(50);
|
|
||||||
WiFi.disconnect(false);
|
WiFi.disconnect(false);
|
||||||
delay(30);
|
delay(30);
|
||||||
WiFi.mode(WIFI_OFF);
|
silentRestart();
|
||||||
delay(30);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CalibreConnectActivity::onWifiSelectionComplete(const bool connected) {
|
void CalibreConnectActivity::onWifiSelectionComplete(const bool connected) {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
#include <DNSServer.h>
|
#include <DNSServer.h>
|
||||||
#include <ESPmDNS.h>
|
#include <ESPmDNS.h>
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HalClock.h>
|
|
||||||
#include <I18n.h>
|
#include <I18n.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <esp_task_wdt.h>
|
#include <esp_task_wdt.h>
|
||||||
@@ -12,6 +11,7 @@
|
|||||||
|
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
#include "NetworkModeSelectionActivity.h"
|
#include "NetworkModeSelectionActivity.h"
|
||||||
|
#include "SilentRestart.h"
|
||||||
#include "WifiSelectionActivity.h"
|
#include "WifiSelectionActivity.h"
|
||||||
#include "activities/network/CalibreConnectActivity.h"
|
#include "activities/network/CalibreConnectActivity.h"
|
||||||
#include "activities/network/SignalStrengthWidget.h"
|
#include "activities/network/SignalStrengthWidget.h"
|
||||||
@@ -64,45 +64,17 @@ void CrossPointWebServerActivity::onEnter() {
|
|||||||
void CrossPointWebServerActivity::onExit() {
|
void CrossPointWebServerActivity::onExit() {
|
||||||
Activity::onExit();
|
Activity::onExit();
|
||||||
|
|
||||||
LOG_DBG("WEBACT", "Free heap at onExit start: %d bytes", ESP.getFreeHeap());
|
|
||||||
|
|
||||||
state = WebServerActivityState::SHUTTING_DOWN;
|
state = WebServerActivityState::SHUTTING_DOWN;
|
||||||
|
|
||||||
// Stop the web server first (before disconnecting WiFi)
|
// Skip reboot if WiFi was never activated (e.g. user backed out of mode selection).
|
||||||
stopWebServer();
|
if (WiFi.getMode() != WIFI_MODE_NULL) {
|
||||||
|
|
||||||
// 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) {
|
if (isApMode) {
|
||||||
LOG_DBG("WEBACT", "Stopping WiFi AP...");
|
|
||||||
WiFi.softAPdisconnect(true);
|
WiFi.softAPdisconnect(true);
|
||||||
delay(30);
|
|
||||||
WiFi.mode(WIFI_OFF);
|
|
||||||
delay(30);
|
|
||||||
} else {
|
} else {
|
||||||
HalClock::wifiOff();
|
WiFi.disconnect(false);
|
||||||
}
|
}
|
||||||
|
delay(30);
|
||||||
LOG_DBG("WEBACT", "Free heap at onExit end: %d bytes", ESP.getFreeHeap());
|
silentRestart();
|
||||||
requestUpdate();
|
|
||||||
|
|
||||||
if (webServerStarted) {
|
|
||||||
GUI.drawPopup(renderer, tr(STR_RESTART_DEVICE));
|
|
||||||
LOG_DBG("WEBACT", "Restarting device after webserver exit to regain heap...");
|
|
||||||
ESP.restart();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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() {
|
void CrossPointWebServerActivity::loop() {
|
||||||
// Handle different states
|
// Handle different states
|
||||||
if (state == WebServerActivityState::SERVER_RUNNING) {
|
if (state == WebServerActivityState::SERVER_RUNNING) {
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ class CrossPointWebServerActivity final : public Activity {
|
|||||||
void onWifiSelectionComplete(bool connected);
|
void onWifiSelectionComplete(bool connected);
|
||||||
void startAccessPoint();
|
void startAccessPoint();
|
||||||
void startWebServer();
|
void startWebServer();
|
||||||
void stopWebServer();
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit CrossPointWebServerActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
|
explicit CrossPointWebServerActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
|
||||||
|
|||||||
@@ -8,10 +8,12 @@
|
|||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <esp_heap_caps.h>
|
#include <esp_heap_caps.h>
|
||||||
#include <esp_system.h>
|
#include <esp_system.h>
|
||||||
|
#include <esp_wifi.h>
|
||||||
|
|
||||||
#include "KOReaderCredentialStore.h"
|
#include "KOReaderCredentialStore.h"
|
||||||
#include "KOReaderDocumentId.h"
|
#include "KOReaderDocumentId.h"
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
|
#include "SilentRestart.h"
|
||||||
#include "activities/network/WifiSelectionActivity.h"
|
#include "activities/network/WifiSelectionActivity.h"
|
||||||
#include "components/UITheme.h"
|
#include "components/UITheme.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
@@ -140,7 +142,8 @@ bool KOReaderSyncActivity::handleAutoPushPreflight() {
|
|||||||
LOG_DBG("KOSync", "AUTO_PUSH skipped: remote %.4f >= local %.4f", warmupProgress.percentage,
|
LOG_DBG("KOSync", "AUTO_PUSH skipped: remote %.4f >= local %.4f", warmupProgress.percentage,
|
||||||
localProgress.percentage);
|
localProgress.percentage);
|
||||||
KOReaderSyncClient::endPersistentSession();
|
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.koReaderSyncSession.outcome = KOReaderSyncOutcomeState::UPLOAD_COMPLETE;
|
||||||
APP_STATE.saveToFile();
|
APP_STATE.saveToFile();
|
||||||
resumeReader(KOReaderSyncOutcomeState::UPLOAD_COMPLETE);
|
resumeReader(KOReaderSyncOutcomeState::UPLOAD_COMPLETE);
|
||||||
@@ -180,7 +183,7 @@ void KOReaderSyncActivity::performFetchAndCompare() {
|
|||||||
if (syncIntent == KOReaderSyncIntentState::AUTO_PULL) {
|
if (syncIntent == KOReaderSyncIntentState::AUTO_PULL) {
|
||||||
// Auto-pull at book open: nothing to apply, just open the book with local progress.
|
// Auto-pull at book open: nothing to apply, just open the book with local progress.
|
||||||
KOReaderSyncClient::endPersistentSession();
|
KOReaderSyncClient::endPersistentSession();
|
||||||
HalClock::wifiOff(true);
|
esp_wifi_stop();
|
||||||
resumeReader(KOReaderSyncOutcomeState::CANCELLED);
|
resumeReader(KOReaderSyncOutcomeState::CANCELLED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -230,7 +233,7 @@ void KOReaderSyncActivity::performFetchAndCompare() {
|
|||||||
if (!ensureRemotePositionMapped()) {
|
if (!ensureRemotePositionMapped()) {
|
||||||
if (syncIntent == KOReaderSyncIntentState::AUTO_PULL) {
|
if (syncIntent == KOReaderSyncIntentState::AUTO_PULL) {
|
||||||
// Auto-pull was best-effort. Fail silently and just open the book.
|
// Auto-pull was best-effort. Fail silently and just open the book.
|
||||||
HalClock::wifiOff(true);
|
esp_wifi_stop();
|
||||||
resumeReader(KOReaderSyncOutcomeState::CANCELLED);
|
resumeReader(KOReaderSyncOutcomeState::CANCELLED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -258,7 +261,7 @@ void KOReaderSyncActivity::performFetchAndCompare() {
|
|||||||
if (syncIntent == KOReaderSyncIntentState::AUTO_PULL) {
|
if (syncIntent == KOReaderSyncIntentState::AUTO_PULL) {
|
||||||
// Auto-pull skips the success-screen dwell — the reader will render the new
|
// Auto-pull skips the success-screen dwell — the reader will render the new
|
||||||
// position immediately, which is the only visible feedback the user needs.
|
// position immediately, which is the only visible feedback the user needs.
|
||||||
HalClock::wifiOff(true);
|
esp_wifi_stop();
|
||||||
resumeReader(KOReaderSyncOutcomeState::APPLIED_REMOTE);
|
resumeReader(KOReaderSyncOutcomeState::APPLIED_REMOTE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -418,7 +421,8 @@ void KOReaderSyncActivity::performUpload() {
|
|||||||
logSyncMemSnapshot("after_updateProgress");
|
logSyncMemSnapshot("after_updateProgress");
|
||||||
|
|
||||||
if (result != KOReaderSyncClient::OK) {
|
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);
|
RenderLock lock(*this);
|
||||||
state = SYNC_FAILED;
|
state = SYNC_FAILED;
|
||||||
@@ -435,7 +439,8 @@ void KOReaderSyncActivity::performUpload() {
|
|||||||
return;
|
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.koReaderSyncSession.outcome = KOReaderSyncOutcomeState::UPLOAD_COMPLETE;
|
||||||
APP_STATE.saveToFile();
|
APP_STATE.saveToFile();
|
||||||
if (syncIntent == KOReaderSyncIntentState::AUTO_PUSH) {
|
if (syncIntent == KOReaderSyncIntentState::AUTO_PUSH) {
|
||||||
@@ -465,6 +470,9 @@ void KOReaderSyncActivity::onEnter() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Past this point every path uses WiFi.
|
||||||
|
wifiActivated = true;
|
||||||
|
|
||||||
// Check if already connected (e.g. from settings page auth)
|
// Check if already connected (e.g. from settings page auth)
|
||||||
if (WiFi.status() == WL_CONNECTED) {
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
LOG_DBG("KOSync", "Already connected to WiFi");
|
LOG_DBG("KOSync", "Already connected to WiFi");
|
||||||
@@ -483,9 +491,18 @@ void KOReaderSyncActivity::onExit() {
|
|||||||
|
|
||||||
logSyncMemSnapshot("onExit_before_cleanup");
|
logSyncMemSnapshot("onExit_before_cleanup");
|
||||||
KOReaderSyncClient::endPersistentSession();
|
KOReaderSyncClient::endPersistentSession();
|
||||||
HalClock::wifiOff(true);
|
|
||||||
releaseEpubForMapping();
|
releaseEpubForMapping();
|
||||||
logSyncMemSnapshot("onExit_after_cleanup");
|
logSyncMemSnapshot("onExit_after_cleanup");
|
||||||
|
|
||||||
|
if (wifiActivated) {
|
||||||
|
WiFi.disconnect(false);
|
||||||
|
delay(30);
|
||||||
|
if (exitToHomeAfterSync) {
|
||||||
|
silentRestart();
|
||||||
|
} else {
|
||||||
|
silentRestartToReader();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void KOReaderSyncActivity::closeCancelled() {
|
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
|
// 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).
|
// home destination's normal flow (no reader to apply it to in this case).
|
||||||
const bool exitToHome = sync.exitToHomeAfterSync;
|
const bool exitToHome = sync.exitToHomeAfterSync;
|
||||||
|
exitToHomeAfterSync = exitToHome;
|
||||||
if (exitToHome) {
|
if (exitToHome) {
|
||||||
sync.clear();
|
sync.clear();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,6 +102,16 @@ class KOReaderSyncActivity final : public Activity {
|
|||||||
unsigned long uploadCompleteTime = 0;
|
unsigned long uploadCompleteTime = 0;
|
||||||
bool closeRequested = false;
|
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 onWifiSelectionComplete(bool success);
|
||||||
void performSync();
|
void performSync();
|
||||||
bool calculateDocumentHash();
|
bool calculateDocumentHash();
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
#include "SdCardFontGlobals.h"
|
#include "SdCardFontGlobals.h"
|
||||||
|
#include "SilentRestart.h"
|
||||||
#include "activities/network/WifiSelectionActivity.h"
|
#include "activities/network/WifiSelectionActivity.h"
|
||||||
#include "activities/util/ConfirmationActivity.h"
|
#include "activities/util/ConfirmationActivity.h"
|
||||||
#include "components/UITheme.h"
|
#include "components/UITheme.h"
|
||||||
@@ -32,10 +33,12 @@ void FontDownloadActivity::onEnter() {
|
|||||||
|
|
||||||
void FontDownloadActivity::onExit() {
|
void FontDownloadActivity::onExit() {
|
||||||
Activity::onExit();
|
Activity::onExit();
|
||||||
|
|
||||||
|
if (WiFi.getMode() != WIFI_MODE_NULL) {
|
||||||
WiFi.disconnect(false);
|
WiFi.disconnect(false);
|
||||||
delay(100);
|
delay(30);
|
||||||
WiFi.mode(WIFI_OFF);
|
silentRestart();
|
||||||
delay(100);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FontDownloadActivity::onWifiSelectionComplete(const bool success) {
|
void FontDownloadActivity::onWifiSelectionComplete(const bool success) {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
#include "KOReaderAuthActivity.h"
|
#include "KOReaderAuthActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HalClock.h>
|
|
||||||
#include <I18n.h>
|
#include <I18n.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
|
|
||||||
#include "KOReaderCredentialStore.h"
|
#include "KOReaderCredentialStore.h"
|
||||||
#include "KOReaderSyncClient.h"
|
#include "KOReaderSyncClient.h"
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
|
#include "SilentRestart.h"
|
||||||
#include "activities/network/WifiSelectionActivity.h"
|
#include "activities/network/WifiSelectionActivity.h"
|
||||||
#include "components/UITheme.h"
|
#include "components/UITheme.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
@@ -104,7 +104,11 @@ void KOReaderAuthActivity::onEnter() {
|
|||||||
void KOReaderAuthActivity::onExit() {
|
void KOReaderAuthActivity::onExit() {
|
||||||
Activity::onExit();
|
Activity::onExit();
|
||||||
|
|
||||||
HalClock::wifiOff();
|
if (WiFi.getMode() != WIFI_MODE_NULL) {
|
||||||
|
WiFi.disconnect(false);
|
||||||
|
delay(30);
|
||||||
|
silentRestart();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void KOReaderAuthActivity::render(RenderLock&&) {
|
void KOReaderAuthActivity::render(RenderLock&&) {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
#include "OtaUpdateActivity.h"
|
#include "OtaUpdateActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HalClock.h>
|
|
||||||
#include <I18n.h>
|
#include <I18n.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
|
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
|
#include "SilentRestart.h"
|
||||||
#include "activities/network/WifiSelectionActivity.h"
|
#include "activities/network/WifiSelectionActivity.h"
|
||||||
#include "components/UITheme.h"
|
#include "components/UITheme.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
@@ -68,7 +68,11 @@ void OtaUpdateActivity::onEnter() {
|
|||||||
void OtaUpdateActivity::onExit() {
|
void OtaUpdateActivity::onExit() {
|
||||||
Activity::onExit();
|
Activity::onExit();
|
||||||
|
|
||||||
HalClock::wifiOff();
|
if (WiFi.getMode() != WIFI_MODE_NULL) {
|
||||||
|
WiFi.disconnect(false);
|
||||||
|
delay(30);
|
||||||
|
silentRestart();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void OtaUpdateActivity::render(RenderLock&&) {
|
void OtaUpdateActivity::render(RenderLock&&) {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
|
#include "SilentRestart.h"
|
||||||
#include "WeatherSettingsActivity.h"
|
#include "WeatherSettingsActivity.h"
|
||||||
#include "activities/network/WifiSelectionActivity.h"
|
#include "activities/network/WifiSelectionActivity.h"
|
||||||
#include "components/UITheme.h"
|
#include "components/UITheme.h"
|
||||||
@@ -222,7 +223,11 @@ void WeatherActivity::onExit() {
|
|||||||
// Weather screen is always landscape; restore app UI to portrait on exit.
|
// Weather screen is always landscape; restore app UI to portrait on exit.
|
||||||
renderer.setOrientation(GfxRenderer::Orientation::Portrait);
|
renderer.setOrientation(GfxRenderer::Orientation::Portrait);
|
||||||
|
|
||||||
WiFi.mode(WIFI_OFF);
|
if (WiFi.getMode() != WIFI_MODE_NULL) {
|
||||||
|
WiFi.disconnect(false);
|
||||||
|
delay(30);
|
||||||
|
silentRestart();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WeatherActivity::loadAndDisplay() {
|
void WeatherActivity::loadAndDisplay() {
|
||||||
@@ -298,8 +303,7 @@ void WeatherActivity::onWifiSelectionComplete(bool connected) {
|
|||||||
LOG_DBG("WEA", "state -> CHECK_WIFI (waitStart=%lu)", (unsigned long)wifiWaitStartedAtMs);
|
LOG_DBG("WEA", "state -> CHECK_WIFI (waitStart=%lu)", (unsigned long)wifiWaitStartedAtMs);
|
||||||
requestUpdate(true);
|
requestUpdate(true);
|
||||||
} else {
|
} else {
|
||||||
WiFi.disconnect();
|
// Leave WiFi up; onExit's silent reboot handles teardown without further fragmenting.
|
||||||
WiFi.mode(WIFI_OFF);
|
|
||||||
state = State::ERROR;
|
state = State::ERROR;
|
||||||
errorMessage = tr(STR_WIFI_CONN_FAILED);
|
errorMessage = tr(STR_WIFI_CONN_FAILED);
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
|
|||||||
+54
-1
@@ -29,6 +29,7 @@
|
|||||||
#include "OpdsServerStore.h"
|
#include "OpdsServerStore.h"
|
||||||
#include "RecentBooksStore.h"
|
#include "RecentBooksStore.h"
|
||||||
#include "SdCardFontSystem.h"
|
#include "SdCardFontSystem.h"
|
||||||
|
#include "SilentRestart.h"
|
||||||
#include "WeatherSettingsStore.h"
|
#include "WeatherSettingsStore.h"
|
||||||
#include "activities/Activity.h"
|
#include "activities/Activity.h"
|
||||||
#include "activities/ActivityManager.h"
|
#include "activities/ActivityManager.h"
|
||||||
@@ -124,6 +125,29 @@ EpdFont ui12RegularFont(&inter_ui_12_regular);
|
|||||||
EpdFont ui12BoldFont(&inter_ui_12_bold);
|
EpdFont ui12BoldFont(&inter_ui_12_bold);
|
||||||
EpdFontFamily ui12FontFamily(&ui12RegularFont, &ui12BoldFont);
|
EpdFontFamily ui12FontFamily(&ui12RegularFont, &ui12BoldFont);
|
||||||
|
|
||||||
|
// SilentRestart.h definitions. RTC_NOINIT survives ESP.restart() but not power loss.
|
||||||
|
RTC_NOINIT_ATTR uint32_t silentRebootMagic;
|
||||||
|
RTC_NOINIT_ATTR uint32_t silentRebootTarget;
|
||||||
|
constexpr uint32_t SILENT_REBOOT_MAGIC = 0xC1EAB007;
|
||||||
|
constexpr uint32_t SILENT_REBOOT_TARGET_HOME = 0;
|
||||||
|
constexpr uint32_t SILENT_REBOOT_TARGET_READER = 1;
|
||||||
|
|
||||||
|
void silentRestart() {
|
||||||
|
silentRebootTarget = SILENT_REBOOT_TARGET_HOME;
|
||||||
|
silentRebootMagic = SILENT_REBOOT_MAGIC;
|
||||||
|
LOG_DBG("MAIN", "Silent restart (target=home)");
|
||||||
|
delay(50);
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
void silentRestartToReader() {
|
||||||
|
silentRebootTarget = SILENT_REBOOT_TARGET_READER;
|
||||||
|
silentRebootMagic = SILENT_REBOOT_MAGIC;
|
||||||
|
LOG_DBG("MAIN", "Silent restart (target=reader)");
|
||||||
|
delay(50);
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
|
|
||||||
// Enter deep sleep mode
|
// Enter deep sleep mode
|
||||||
void enterDeepSleep() {
|
void enterDeepSleep() {
|
||||||
LOG_DBG("MAIN", "enterDeepSleep called at millis=%lu, powerBtn isPressed=%d, rawPin=%d", millis(),
|
LOG_DBG("MAIN", "enterDeepSleep called at millis=%lu, powerBtn isPressed=%d, rawPin=%d", millis(),
|
||||||
@@ -233,6 +257,15 @@ void setup() {
|
|||||||
esp_ota_mark_app_valid_cancel_rollback();
|
esp_ota_mark_app_valid_cancel_rollback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read-and-clear so a panic later in setup() doesn't loop into silent reboot.
|
||||||
|
// Bound the target range too — RTC_NOINIT memory is uninitialized on cold boot.
|
||||||
|
const bool isSilentReboot = (silentRebootMagic == SILENT_REBOOT_MAGIC);
|
||||||
|
const uint32_t silentRebootTargetSnapshot =
|
||||||
|
(isSilentReboot && silentRebootTarget <= SILENT_REBOOT_TARGET_READER) ? silentRebootTarget : 0;
|
||||||
|
silentRebootMagic = 0;
|
||||||
|
silentRebootTarget = 0;
|
||||||
|
|
||||||
HalSystem::begin();
|
HalSystem::begin();
|
||||||
gpio.begin();
|
gpio.begin();
|
||||||
powerManager.begin();
|
powerManager.begin();
|
||||||
@@ -330,7 +363,16 @@ void setup() {
|
|||||||
|
|
||||||
setupDisplayAndFonts();
|
setupDisplayAndFonts();
|
||||||
|
|
||||||
|
if (!isSilentReboot) {
|
||||||
activityManager.goToBoot();
|
activityManager.goToBoot();
|
||||||
|
} else {
|
||||||
|
// After a silent reboot the panel still shows the previous session's pixels but
|
||||||
|
// the SDK's RED-RAM diff buffer was cleared by begin(). A FAST refresh would only
|
||||||
|
// flip pixels the SDK *thinks* changed, leaving the old screen visible. Force the
|
||||||
|
// first paint to HALF_REFRESH so the panel cleanly repaints; subsequent paints
|
||||||
|
// resume FAST as normal.
|
||||||
|
renderer.setNextDisplayRefreshMode(HalDisplay::HALF_REFRESH);
|
||||||
|
}
|
||||||
|
|
||||||
APP_STATE.loadFromFile();
|
APP_STATE.loadFromFile();
|
||||||
HalClock::restore();
|
HalClock::restore();
|
||||||
@@ -341,6 +383,14 @@ void setup() {
|
|||||||
// Skip normal home/reader routing: jump straight into the SD firmware picker.
|
// Skip normal home/reader routing: jump straight into the SD firmware picker.
|
||||||
activityManager.replaceActivity(
|
activityManager.replaceActivity(
|
||||||
std::make_unique<SdFirmwareUpdateActivity>(renderer, mappedInputManager, /*recoveryMode=*/true));
|
std::make_unique<SdFirmwareUpdateActivity>(renderer, mappedInputManager, /*recoveryMode=*/true));
|
||||||
|
} else if (isSilentReboot && silentRebootTargetSnapshot == SILENT_REBOOT_TARGET_READER &&
|
||||||
|
!APP_STATE.openEpubPath.empty()) {
|
||||||
|
activityManager.goToReader(APP_STATE.openEpubPath);
|
||||||
|
} else if (isSilentReboot) {
|
||||||
|
// target == home (or reader with no open book): land on home — don't fall
|
||||||
|
// through to the sleep-wake "resume reader" logic, which fires on stale
|
||||||
|
// openEpubPath + lastSleepFromReader from a prior session.
|
||||||
|
activityManager.goHome();
|
||||||
} else if (APP_STATE.openEpubPath.empty() || !APP_STATE.lastSleepFromReader ||
|
} else if (APP_STATE.openEpubPath.empty() || !APP_STATE.lastSleepFromReader ||
|
||||||
mappedInputManager.isPressed(MappedInputManager::Button::Back) || APP_STATE.readerActivityLoadCount > 0) {
|
mappedInputManager.isPressed(MappedInputManager::Button::Back) || APP_STATE.readerActivityLoadCount > 0) {
|
||||||
activityManager.goHome();
|
activityManager.goHome();
|
||||||
@@ -353,9 +403,12 @@ void setup() {
|
|||||||
activityManager.goToReader(path);
|
activityManager.goToReader(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we're not still holding the power button before leaving setup
|
// Ensure we're not still holding the power button before leaving setup.
|
||||||
// waitForStablePowerRelease protects against switch bounce that might register as a false double-press.
|
// waitForStablePowerRelease protects against switch bounce that might register as a false double-press.
|
||||||
|
// Skip on silent reboot: the firmware triggered the restart, so the button isn't held.
|
||||||
|
if (!isSilentReboot) {
|
||||||
gpio.waitForStablePowerRelease();
|
gpio.waitForStablePowerRelease();
|
||||||
|
}
|
||||||
// Flush any pin state transitions that occurred during boot before entering the main loop
|
// Flush any pin state transitions that occurred during boot before entering the main loop
|
||||||
mappedInputManager.update();
|
mappedInputManager.update();
|
||||||
buttonEventManager.drain();
|
buttonEventManager.drain();
|
||||||
|
|||||||
Reference in New Issue
Block a user