diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index 232c7c57..e032fab6 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -22,7 +22,7 @@ void readAndValidate(FsFile& file, uint8_t& member, const uint8_t maxValue) { namespace { constexpr uint8_t SETTINGS_FILE_VERSION = 1; // Increment this when adding new persisted settings fields -constexpr uint8_t SETTINGS_COUNT = 23; +constexpr uint8_t SETTINGS_COUNT = 24; constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin"; } // namespace @@ -60,6 +60,7 @@ bool CrossPointSettings::saveToFile() const { serialization::writeString(outputFile, std::string(opdsUsername)); serialization::writeString(outputFile, std::string(opdsPassword)); serialization::writePod(outputFile, sleepScreenCoverFilter); + serialization::writeString(outputFile, std::string(lastConnectedSSID)); // New fields added at end for backward compatibility outputFile.close(); diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index c450d348..7a4f9904 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -137,6 +137,7 @@ class CrossPointSettings { uint8_t hideBatteryPercentage = HIDE_NEVER; // Long-press chapter skip on side buttons uint8_t longPressChapterSkip = 1; + char lastConnectedSSID[33] = ""; ~CrossPointSettings() = default; diff --git a/src/WifiCredentialStore.cpp b/src/WifiCredentialStore.cpp index be865b86..c126cd92 100644 --- a/src/WifiCredentialStore.cpp +++ b/src/WifiCredentialStore.cpp @@ -4,6 +4,8 @@ #include #include +#include + // Initialize the static instance WifiCredentialStore WifiCredentialStore::instance; @@ -102,8 +104,8 @@ bool WifiCredentialStore::loadFromFile() { bool WifiCredentialStore::addCredential(const std::string& ssid, const std::string& password) { // Check if this SSID already exists and update it - const auto cred = find_if(credentials.begin(), credentials.end(), - [&ssid](const WifiCredential& cred) { return cred.ssid == ssid; }); + auto cred = std::find_if(credentials.begin(), credentials.end(), + [&ssid](const WifiCredential& cred) { return cred.ssid == ssid; }); if (cred != credentials.end()) { cred->password = password; Serial.printf("[%lu] [WCS] Updated credentials for: %s\n", millis(), ssid.c_str()); @@ -123,8 +125,8 @@ bool WifiCredentialStore::addCredential(const std::string& ssid, const std::stri } bool WifiCredentialStore::removeCredential(const std::string& ssid) { - const auto cred = find_if(credentials.begin(), credentials.end(), - [&ssid](const WifiCredential& cred) { return cred.ssid == ssid; }); + auto cred = std::find_if(credentials.begin(), credentials.end(), + [&ssid](const WifiCredential& cred) { return cred.ssid == ssid; }); if (cred != credentials.end()) { credentials.erase(cred); Serial.printf("[%lu] [WCS] Removed credentials for: %s\n", millis(), ssid.c_str()); @@ -134,8 +136,8 @@ bool WifiCredentialStore::removeCredential(const std::string& ssid) { } const WifiCredential* WifiCredentialStore::findCredential(const std::string& ssid) const { - const auto cred = find_if(credentials.begin(), credentials.end(), - [&ssid](const WifiCredential& cred) { return cred.ssid == ssid; }); + auto cred = std::find_if(credentials.begin(), credentials.end(), + [&ssid](const WifiCredential& cred) { return cred.ssid == ssid; }); if (cred != credentials.end()) { return &*cred; diff --git a/src/activities/network/AutoConnectingActivity.cpp b/src/activities/network/AutoConnectingActivity.cpp new file mode 100644 index 00000000..423f7f35 --- /dev/null +++ b/src/activities/network/AutoConnectingActivity.cpp @@ -0,0 +1,75 @@ +#include "AutoConnectingActivity.h" + +#include +#include + +#include + +#include "CrossPointSettings.h" +#include "MappedInputManager.h" +#include "WifiCredentialStore.h" +#include "fontIds.h" + +AutoConnectingActivity::AutoConnectingActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, + const std::function& on_success, + const std::function& on_failure) + : Activity("AutoConnecting", renderer, mappedInput), on_success(on_success), on_failure(on_failure) {} + +void AutoConnectingActivity::onEnter() { + Activity::onEnter(); + render(); + attemptConnection(); +} + +void AutoConnectingActivity::onExit() { Activity::onExit(); } + +void AutoConnectingActivity::loop() { checkConnectionStatus(); } + +void AutoConnectingActivity::attemptConnection() { + connectionStartTime = millis(); + std::string ssid = SETTINGS.lastConnectedSSID; + const auto* cred = WIFI_STORE.findCredential(ssid); + if (!cred) { + on_failure(); + return; + } + + WiFi.mode(WIFI_STA); + WiFi.begin(ssid.c_str(), cred->password.c_str()); +} + +void AutoConnectingActivity::checkConnectionStatus() { + const wl_status_t status = WiFi.status(); + + if (status == WL_CONNECTED) { + on_success(); + return; + } + + if (status == WL_CONNECT_FAILED || status == WL_NO_SSID_AVAIL) { + on_failure(); + return; + } + + if (millis() - connectionStartTime > CONNECTION_TIMEOUT_MS) { + WiFi.disconnect(); + on_failure(); + return; + } +} + +void AutoConnectingActivity::render() const { + renderer.clearScreen(); + const auto pageHeight = renderer.getScreenHeight(); + const auto height = renderer.getLineHeight(UI_10_FONT_ID); + const auto top = (pageHeight - height) / 2; + + renderer.drawCenteredText(UI_12_FONT_ID, top - 40, "Connecting...", true, EpdFontFamily::BOLD); + + std::string ssidInfo = "to " + std::string(SETTINGS.lastConnectedSSID); + if (ssidInfo.length() > 25) { + ssidInfo.replace(22, ssidInfo.length() - 22, "..."); + } + renderer.drawCenteredText(UI_10_FONT_ID, top, ssidInfo.c_str()); + renderer.displayBuffer(); +} diff --git a/src/activities/network/AutoConnectingActivity.h b/src/activities/network/AutoConnectingActivity.h new file mode 100644 index 00000000..d745f25f --- /dev/null +++ b/src/activities/network/AutoConnectingActivity.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include "activities/Activity.h" + +class AutoConnectingActivity final : public Activity { + public: + AutoConnectingActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, + const std::function& on_success, const std::function& on_failure); + + void onEnter() override; + void onExit() override; + void loop() override; + + private: + void render() const; + void attemptConnection(); + void checkConnectionStatus(); + + const std::function on_success; + const std::function on_failure; + + unsigned long connectionStartTime = 0; + static constexpr unsigned long CONNECTION_TIMEOUT_MS = 15000; +}; diff --git a/src/activities/network/WifiSelectionActivity.cpp b/src/activities/network/WifiSelectionActivity.cpp index becd41a3..ee787fd6 100644 --- a/src/activities/network/WifiSelectionActivity.cpp +++ b/src/activities/network/WifiSelectionActivity.cpp @@ -3,8 +3,10 @@ #include #include +#include #include +#include "CrossPointSettings.h" #include "MappedInputManager.h" #include "WifiCredentialStore.h" #include "activities/util/KeyboardEntryActivity.h" @@ -251,6 +253,11 @@ void WifiSelectionActivity::checkConnectionStatus() { snprintf(ipStr, sizeof(ipStr), "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); connectedIP = ipStr; + // Save the last connected SSID + strncpy(SETTINGS.lastConnectedSSID, selectedSSID.c_str(), sizeof(SETTINGS.lastConnectedSSID) - 1); + SETTINGS.lastConnectedSSID[sizeof(SETTINGS.lastConnectedSSID) - 1] = '\0'; + SETTINGS.saveToFile(); + // If we entered a new password, ask if user wants to save it // Otherwise, immediately complete so parent can start web server if (!usedSavedPassword && !enteredPassword.empty()) { @@ -311,14 +318,12 @@ void WifiSelectionActivity::loop() { // Handle save prompt state if (state == WifiSelectionState::SAVE_PROMPT) { - if (mappedInput.wasPressed(MappedInputManager::Button::Up) || - mappedInput.wasPressed(MappedInputManager::Button::Left)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Left)) { if (savePromptSelection > 0) { savePromptSelection--; updateRequired = true; } - } else if (mappedInput.wasPressed(MappedInputManager::Button::Down) || - mappedInput.wasPressed(MappedInputManager::Button::Right)) { + } else if (mappedInput.wasPressed(MappedInputManager::Button::Right)) { if (savePromptSelection < 1) { savePromptSelection++; updateRequired = true; @@ -341,14 +346,12 @@ void WifiSelectionActivity::loop() { // Handle forget prompt state (connection failed with saved credentials) if (state == WifiSelectionState::FORGET_PROMPT) { - if (mappedInput.wasPressed(MappedInputManager::Button::Up) || - mappedInput.wasPressed(MappedInputManager::Button::Left)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Left)) { if (forgetPromptSelection > 0) { forgetPromptSelection--; updateRequired = true; } - } else if (mappedInput.wasPressed(MappedInputManager::Button::Down) || - mappedInput.wasPressed(MappedInputManager::Button::Right)) { + } else if (mappedInput.wasPressed(MappedInputManager::Button::Right)) { if (forgetPromptSelection < 1) { forgetPromptSelection++; updateRequired = true; @@ -360,8 +363,8 @@ void WifiSelectionActivity::loop() { WIFI_STORE.removeCredential(selectedSSID); xSemaphoreGive(renderingMutex); // Update the network list to reflect the change - const auto network = find_if(networks.begin(), networks.end(), - [this](const WifiNetworkInfo& net) { return net.ssid == selectedSSID; }); + const auto network = std::find_if(networks.begin(), networks.end(), + [this](const WifiNetworkInfo& net) { return net.ssid == selectedSSID; }); if (network != networks.end()) { network->hasSavedPassword = false; } @@ -420,18 +423,23 @@ void WifiSelectionActivity::loop() { } // Handle UP/DOWN navigation - if (mappedInput.wasPressed(MappedInputManager::Button::Up) || - mappedInput.wasPressed(MappedInputManager::Button::Left)) { + if (mappedInput.wasPressed(MappedInputManager::Button::Up)) { if (selectedNetworkIndex > 0) { selectedNetworkIndex--; updateRequired = true; } - } else if (mappedInput.wasPressed(MappedInputManager::Button::Down) || - mappedInput.wasPressed(MappedInputManager::Button::Right)) { + } else if (mappedInput.wasPressed(MappedInputManager::Button::Down)) { if (!networks.empty() && selectedNetworkIndex < static_cast(networks.size()) - 1) { selectedNetworkIndex++; updateRequired = true; } + } else if (mappedInput.wasPressed(MappedInputManager::Button::Left)) { + if (!networks.empty() && networks[selectedNetworkIndex].hasSavedPassword) { + selectedSSID = networks[selectedNetworkIndex].ssid; + state = WifiSelectionState::FORGET_PROMPT; + forgetPromptSelection = 0; // Default to "Cancel" + updateRequired = true; + } } } } @@ -585,7 +593,7 @@ void WifiSelectionActivity::renderNetworkList() const { // Draw help text renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 75, "* = Encrypted | + = Saved"); - const auto labels = mappedInput.mapLabels("« Back", "Connect", "", ""); + const auto labels = mappedInput.mapLabels("« Back", "Connect", "Forget", ""); renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } @@ -689,7 +697,7 @@ void WifiSelectionActivity::renderForgetPrompt() const { const auto height = renderer.getLineHeight(UI_10_FONT_ID); const auto top = (pageHeight - height * 3) / 2; - renderer.drawCenteredText(UI_12_FONT_ID, top - 40, "Connection Failed", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, top - 40, "Forget Network", true, EpdFontFamily::BOLD); std::string ssidInfo = "Network: " + selectedSSID; if (ssidInfo.length() > 28) { diff --git a/src/activities/settings/CategorySettingsActivity.cpp b/src/activities/settings/CategorySettingsActivity.cpp index 7fd5ef5f..ad7a1d16 100644 --- a/src/activities/settings/CategorySettingsActivity.cpp +++ b/src/activities/settings/CategorySettingsActivity.cpp @@ -11,6 +11,7 @@ #include "KOReaderSettingsActivity.h" #include "MappedInputManager.h" #include "OtaUpdateActivity.h" +#include "activities/network/WifiSelectionActivity.h" #include "fontIds.h" void CategorySettingsActivity::taskTrampoline(void* param) { @@ -95,7 +96,15 @@ void CategorySettingsActivity::toggleCurrentSetting() { SETTINGS.*(setting.valuePtr) = currentValue + setting.valueRange.step; } } else if (setting.type == SettingType::ACTION) { - if (strcmp(setting.name, "KOReader Sync") == 0) { + if (strcmp(setting.name, "Network") == 0) { + xSemaphoreTake(renderingMutex, portMAX_DELAY); + exitActivity(); + enterNewActivity(new WifiSelectionActivity(renderer, mappedInput, [this](bool) { + exitActivity(); + updateRequired = true; + })); + xSemaphoreGive(renderingMutex); + } else if (strcmp(setting.name, "KOReader Sync") == 0) { xSemaphoreTake(renderingMutex, portMAX_DELAY); exitActivity(); enterNewActivity(new KOReaderSettingsActivity(renderer, mappedInput, [this] { diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 7316db05..1edec2b8 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -48,11 +48,14 @@ const SettingInfo controlsSettings[controlsSettingsCount] = { SettingInfo::Toggle("Long-press Chapter Skip", &CrossPointSettings::longPressChapterSkip), SettingInfo::Enum("Short Power Button Click", &CrossPointSettings::shortPwrBtn, {"Ignore", "Sleep", "Page Turn"})}; -constexpr int systemSettingsCount = 5; +constexpr int systemSettingsCount = 6; const SettingInfo systemSettings[systemSettingsCount] = { SettingInfo::Enum("Time to Sleep", &CrossPointSettings::sleepTimeout, {"1 min", "5 min", "10 min", "15 min", "30 min"}), - SettingInfo::Action("KOReader Sync"), SettingInfo::Action("OPDS Browser"), SettingInfo::Action("Clear Cache"), + SettingInfo::Action("Network"), + SettingInfo::Action("KOReader Sync"), + SettingInfo::Action("OPDS Browser"), + SettingInfo::Action("Clear Cache"), SettingInfo::Action("Check for updates")}; } // namespace diff --git a/src/main.cpp b/src/main.cpp index 89c4e13c..f3e7cd0b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -20,7 +21,9 @@ #include "activities/browser/OpdsBookBrowserActivity.h" #include "activities/home/HomeActivity.h" #include "activities/home/MyLibraryActivity.h" +#include "activities/network/AutoConnectingActivity.h" #include "activities/network/CrossPointWebServerActivity.h" +#include "activities/network/WifiSelectionActivity.h" #include "activities/reader/ReaderActivity.h" #include "activities/settings/SettingsActivity.h" #include "activities/util/FullScreenMessageActivity.h" @@ -211,9 +214,38 @@ void onGoToReader(const std::string& initialEpubPath, MyLibraryActivity::Tab fro } void onContinueReading() { onGoToReader(APP_STATE.openEpubPath, MyLibraryActivity::Tab::Recent); } +void onGoHome(); // forward declaration + +void withWifi(std::function on_success) { + if (WiFi.isConnected()) { + on_success(); + return; + } + + auto on_failure = [on_success]() { + exitActivity(); + enterNewActivity(new WifiSelectionActivity(renderer, mappedInputManager, [on_success](bool connected) { + if (connected) { + on_success(); + } else { + onGoHome(); + } + })); + }; + + if (strlen(SETTINGS.lastConnectedSSID) > 0) { + exitActivity(); + enterNewActivity(new AutoConnectingActivity(renderer, mappedInputManager, on_success, on_failure)); + } else { + on_failure(); + } +} + void onGoToFileTransfer() { - exitActivity(); - enterNewActivity(new CrossPointWebServerActivity(renderer, mappedInputManager, onGoHome)); + withWifi([]() { + exitActivity(); + enterNewActivity(new CrossPointWebServerActivity(renderer, mappedInputManager, onGoHome)); + }); } void onGoToSettings() { @@ -232,8 +264,10 @@ void onGoToMyLibraryWithTab(const std::string& path, MyLibraryActivity::Tab tab) } void onGoToBrowser() { - exitActivity(); - enterNewActivity(new OpdsBookBrowserActivity(renderer, mappedInputManager, onGoHome)); + withWifi([]() { + exitActivity(); + enterNewActivity(new OpdsBookBrowserActivity(renderer, mappedInputManager, onGoHome)); + }); } void onGoHome() {