fix: Wi-Fi Selection on Calibre Library launch (#313)

## Summary

* **What is the goal of this PR?** 
Fixes the Wi-Fi connection issue when launching the Calibre Library
(OPDS browser). The previous implementation always attempted to connect
using the first saved WiFi credential, which caused connection failures
when users were in locations where only other saved networks (not the
first one) were available. Now, the activity launches a WiFi selection
screen allowing users to choose from available networks.

* **What changes are included?**

## Additional Context
**Bug Fixed**: Previously, the code used `credentials[0]` (always the
first saved WiFi), so users in areas with only their secondary/tertiary
saved networks available could never connect.

---------

Co-authored-by: danoooob <danoooob@example.com>
This commit is contained in:
danoob
2026-01-12 17:49:42 +07:00
committed by GitHub
parent 41bda43899
commit 66b100c6ca
2 changed files with 54 additions and 40 deletions

View File

@@ -7,7 +7,7 @@
#include "CrossPointSettings.h" #include "CrossPointSettings.h"
#include "MappedInputManager.h" #include "MappedInputManager.h"
#include "ScreenComponents.h" #include "ScreenComponents.h"
#include "WifiCredentialStore.h" #include "activities/network/WifiSelectionActivity.h"
#include "fontIds.h" #include "fontIds.h"
#include "network/HttpDownloader.h" #include "network/HttpDownloader.h"
#include "util/StringUtils.h" #include "util/StringUtils.h"
@@ -25,7 +25,7 @@ void OpdsBookBrowserActivity::taskTrampoline(void* param) {
} }
void OpdsBookBrowserActivity::onEnter() { void OpdsBookBrowserActivity::onEnter() {
Activity::onEnter(); ActivityWithSubactivity::onEnter();
renderingMutex = xSemaphoreCreateMutex(); renderingMutex = xSemaphoreCreateMutex();
state = BrowserState::CHECK_WIFI; state = BrowserState::CHECK_WIFI;
@@ -49,7 +49,7 @@ void OpdsBookBrowserActivity::onEnter() {
} }
void OpdsBookBrowserActivity::onExit() { void OpdsBookBrowserActivity::onExit() {
Activity::onExit(); ActivityWithSubactivity::onExit();
// Turn off WiFi when exiting // Turn off WiFi when exiting
WiFi.mode(WIFI_OFF); WiFi.mode(WIFI_OFF);
@@ -66,13 +66,28 @@ void OpdsBookBrowserActivity::onExit() {
} }
void OpdsBookBrowserActivity::loop() { void OpdsBookBrowserActivity::loop() {
// Handle WiFi selection subactivity
if (state == BrowserState::WIFI_SELECTION) {
ActivityWithSubactivity::loop();
return;
}
// Handle error state - Confirm retries, Back goes back or home // Handle error state - Confirm retries, Back goes back or home
if (state == BrowserState::ERROR) { if (state == BrowserState::ERROR) {
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
state = BrowserState::LOADING; // Check if WiFi is still connected
statusMessage = "Loading..."; if (WiFi.status() == WL_CONNECTED && WiFi.localIP() != IPAddress(0, 0, 0, 0)) {
updateRequired = true; // WiFi connected - just retry fetching the feed
fetchFeed(currentPath); Serial.printf("[%lu] [OPDS] Retry: WiFi connected, retrying fetch\n", millis());
state = BrowserState::LOADING;
statusMessage = "Loading...";
updateRequired = true;
fetchFeed(currentPath);
} else {
// WiFi not connected - launch WiFi selection
Serial.printf("[%lu] [OPDS] Retry: WiFi not connected, launching selection\n", millis());
launchWifiSelection();
}
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
navigateBack(); navigateBack();
} }
@@ -350,8 +365,8 @@ void OpdsBookBrowserActivity::downloadBook(const OpdsEntry& book) {
} }
void OpdsBookBrowserActivity::checkAndConnectWifi() { void OpdsBookBrowserActivity::checkAndConnectWifi() {
// Already connected? // Already connected? Verify connection is valid by checking IP
if (WiFi.status() == WL_CONNECTED) { if (WiFi.status() == WL_CONNECTED && WiFi.localIP() != IPAddress(0, 0, 0, 0)) {
state = BrowserState::LOADING; state = BrowserState::LOADING;
statusMessage = "Loading..."; statusMessage = "Loading...";
updateRequired = true; updateRequired = true;
@@ -359,38 +374,33 @@ void OpdsBookBrowserActivity::checkAndConnectWifi() {
return; return;
} }
// Try to connect using saved credentials // Not connected - launch WiFi selection screen directly
statusMessage = "Connecting to WiFi..."; launchWifiSelection();
}
void OpdsBookBrowserActivity::launchWifiSelection() {
state = BrowserState::WIFI_SELECTION;
updateRequired = true; updateRequired = true;
WIFI_STORE.loadFromFile(); enterNewActivity(new WifiSelectionActivity(renderer, mappedInput,
const auto& credentials = WIFI_STORE.getCredentials(); [this](const bool connected) { onWifiSelectionComplete(connected); }));
if (credentials.empty()) { }
state = BrowserState::ERROR;
errorMessage = "No WiFi credentials saved";
updateRequired = true;
return;
}
// Use the first saved credential void OpdsBookBrowserActivity::onWifiSelectionComplete(const bool connected) {
const auto& cred = credentials[0]; exitActivity();
WiFi.mode(WIFI_STA);
WiFi.begin(cred.ssid.c_str(), cred.password.c_str());
// Wait for connection with timeout if (connected) {
constexpr int WIFI_TIMEOUT_MS = 10000; Serial.printf("[%lu] [OPDS] WiFi connected via selection, fetching feed\n", millis());
const unsigned long startTime = millis();
while (WiFi.status() != WL_CONNECTED && millis() - startTime < WIFI_TIMEOUT_MS) {
vTaskDelay(100 / portTICK_PERIOD_MS);
}
if (WiFi.status() == WL_CONNECTED) {
Serial.printf("[%lu] [OPDS] WiFi connected: %s\n", millis(), WiFi.localIP().toString().c_str());
state = BrowserState::LOADING; state = BrowserState::LOADING;
statusMessage = "Loading..."; statusMessage = "Loading...";
updateRequired = true; updateRequired = true;
fetchFeed(currentPath); fetchFeed(currentPath);
} else { } else {
Serial.printf("[%lu] [OPDS] WiFi selection cancelled/failed\n", millis());
// Force disconnect to ensure clean state for next retry
// This prevents stale connection status from interfering
WiFi.disconnect();
WiFi.mode(WIFI_OFF);
state = BrowserState::ERROR; state = BrowserState::ERROR;
errorMessage = "WiFi connection failed"; errorMessage = "WiFi connection failed";
updateRequired = true; updateRequired = true;

View File

@@ -8,25 +8,27 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "../Activity.h" #include "../ActivityWithSubactivity.h"
/** /**
* Activity for browsing and downloading books from an OPDS server. * Activity for browsing and downloading books from an OPDS server.
* Supports navigation through catalog hierarchy and downloading EPUBs. * Supports navigation through catalog hierarchy and downloading EPUBs.
* When WiFi connection fails, launches WiFi selection to let user connect.
*/ */
class OpdsBookBrowserActivity final : public Activity { class OpdsBookBrowserActivity final : public ActivityWithSubactivity {
public: public:
enum class BrowserState { enum class BrowserState {
CHECK_WIFI, // Checking WiFi connection CHECK_WIFI, // Checking WiFi connection
LOADING, // Fetching OPDS feed WIFI_SELECTION, // WiFi selection subactivity is active
BROWSING, // Displaying entries (navigation or books) LOADING, // Fetching OPDS feed
DOWNLOADING, // Downloading selected EPUB BROWSING, // Displaying entries (navigation or books)
ERROR // Error state with message DOWNLOADING, // Downloading selected EPUB
ERROR // Error state with message
}; };
explicit OpdsBookBrowserActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, explicit OpdsBookBrowserActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
const std::function<void()>& onGoHome) const std::function<void()>& onGoHome)
: Activity("OpdsBookBrowser", renderer, mappedInput), onGoHome(onGoHome) {} : ActivityWithSubactivity("OpdsBookBrowser", renderer, mappedInput), onGoHome(onGoHome) {}
void onEnter() override; void onEnter() override;
void onExit() override; void onExit() override;
@@ -54,6 +56,8 @@ class OpdsBookBrowserActivity final : public Activity {
void render() const; void render() const;
void checkAndConnectWifi(); void checkAndConnectWifi();
void launchWifiSelection();
void onWifiSelectionComplete(bool connected);
void fetchFeed(const std::string& path); void fetchFeed(const std::string& path);
void navigateToEntry(const OpdsEntry& entry); void navigateToEntry(const OpdsEntry& entry);
void navigateBack(); void navigateBack();