feat: auto-connect saved Wi-Fi networks (#2189)
Co-authored-by: Alexander Hoffer <git@alexanderhoffer.com>
This commit is contained in:
co-authored by
Alexander Hoffer
parent
e0253a8664
commit
5202bbf911
+8
-3
@@ -32,9 +32,14 @@ networks or in hotspot mode when you control who is connected.
|
||||
## Join Network Mode
|
||||
|
||||
1. Select **Join Network**.
|
||||
2. Pick a 2.4 GHz Wi-Fi network from the scan results.
|
||||
3. Enter the password if prompted.
|
||||
4. Save credentials if you want the reader to reconnect automatically next time.
|
||||
2. If you have saved Wi-Fi credentials, CrossPoint first tries the last
|
||||
connected network, then other visible saved networks in signal-strength
|
||||
order. Press **Back** to cancel or **Confirm** to stop auto-connect and show
|
||||
the network list.
|
||||
3. If the network list is shown, pick a 2.4 GHz Wi-Fi network from the scan
|
||||
results.
|
||||
4. Enter the password if prompted.
|
||||
5. Save credentials if you want the reader to reconnect automatically next time.
|
||||
|
||||
After connection, the reader shows:
|
||||
|
||||
|
||||
@@ -29,7 +29,10 @@ STR_WIFI_NETWORKS: "Wi-Fi Networks"
|
||||
STR_NO_NETWORKS: "No networks found"
|
||||
STR_NETWORKS_FOUND: "%zu networks found"
|
||||
STR_SCANNING: "Scanning..."
|
||||
STR_FINDING_SAVED_WIFI: "Finding saved Wi-Fi..."
|
||||
STR_CONNECTING: "Connecting..."
|
||||
STR_CONNECTING_SAVED_WIFI: "Connecting to saved Wi-Fi..."
|
||||
STR_SHOW_NETWORKS: "Show"
|
||||
STR_CONNECTED: "Connected!"
|
||||
STR_CONNECTION_FAILED: "Connection Failed"
|
||||
STR_FORGET_NETWORK: "Forget Network?"
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <Logging.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "WifiCredentialStore.h"
|
||||
@@ -35,6 +37,9 @@ void WifiSelectionActivity::onEnter() {
|
||||
savePromptSelection = 0;
|
||||
forgetPromptSelection = 0;
|
||||
autoConnecting = false;
|
||||
manualNetworkListRequested = false;
|
||||
autoAttemptedSsids.clear();
|
||||
autoAttemptedSsids.reserve(WIFI_STORE.getCredentials().size());
|
||||
|
||||
// Cache MAC address for display
|
||||
uint8_t mac[6];
|
||||
@@ -47,23 +52,20 @@ void WifiSelectionActivity::onEnter() {
|
||||
// Trigger first update to show scanning message
|
||||
requestUpdate();
|
||||
|
||||
// Attempt to auto-connect to the last network
|
||||
if (allowAutoConnect) {
|
||||
// Attempt to auto-connect to known networks. Try the last successful
|
||||
// network first for speed, then scan and try any visible saved networks by
|
||||
// signal strength. The user can interrupt this and show the scan result.
|
||||
if (allowAutoConnect && !WIFI_STORE.getCredentials().empty()) {
|
||||
const std::string lastSsid = WIFI_STORE.getLastConnectedSsid();
|
||||
if (!lastSsid.empty()) {
|
||||
const auto* cred = WIFI_STORE.findCredential(lastSsid);
|
||||
if (cred) {
|
||||
LOG_DBG("WIFI", "Attempting to auto-connect to %s", lastSsid.c_str());
|
||||
selectedSSID = cred->ssid;
|
||||
enteredPassword = cred->password;
|
||||
selectedRequiresPassword = !cred->password.empty();
|
||||
usedSavedPassword = true;
|
||||
autoConnecting = true;
|
||||
attemptConnection();
|
||||
requestUpdate();
|
||||
if (cred && tryAutoConnectCredential(*cred)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
startWifiScan(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback to scanning
|
||||
@@ -87,8 +89,9 @@ void WifiSelectionActivity::onExit() {
|
||||
LOG_DBG("WIFI", "Free heap at onExit end: %d bytes", ESP.getFreeHeap());
|
||||
}
|
||||
|
||||
void WifiSelectionActivity::startWifiScan() {
|
||||
autoConnecting = false;
|
||||
void WifiSelectionActivity::startWifiScan(const bool autoScan) {
|
||||
autoConnecting = autoScan;
|
||||
manualNetworkListRequested = false;
|
||||
state = WifiSelectionState::SCANNING;
|
||||
networks.clear();
|
||||
requestUpdate();
|
||||
@@ -111,6 +114,8 @@ void WifiSelectionActivity::processWifiScanResults() {
|
||||
}
|
||||
|
||||
if (scanResult == WIFI_SCAN_FAILED) {
|
||||
autoConnecting = false;
|
||||
manualNetworkListRequested = false;
|
||||
state = WifiSelectionState::NETWORK_LIST;
|
||||
requestUpdate();
|
||||
return;
|
||||
@@ -154,6 +159,13 @@ void WifiSelectionActivity::processWifiScanResults() {
|
||||
});
|
||||
|
||||
WiFi.scanDelete();
|
||||
|
||||
if (autoConnecting && !manualNetworkListRequested && tryNextSavedNetworkFromScan()) {
|
||||
return;
|
||||
}
|
||||
|
||||
autoConnecting = false;
|
||||
manualNetworkListRequested = false;
|
||||
state = WifiSelectionState::NETWORK_LIST;
|
||||
selectedNetworkIndex = 0;
|
||||
requestUpdate();
|
||||
@@ -204,6 +216,76 @@ void WifiSelectionActivity::selectNetwork(const int index) {
|
||||
}
|
||||
}
|
||||
|
||||
bool WifiSelectionActivity::hasAttemptedAutoSsid(const std::string& ssid) const {
|
||||
return std::find(autoAttemptedSsids.begin(), autoAttemptedSsids.end(), ssid) != autoAttemptedSsids.end();
|
||||
}
|
||||
|
||||
bool WifiSelectionActivity::tryAutoConnectCredential(const WifiCredential& cred) {
|
||||
if (hasAttemptedAutoSsid(cred.ssid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_DBG("WIFI", "Attempting saved network: %s", cred.ssid.c_str());
|
||||
autoAttemptedSsids.push_back(cred.ssid);
|
||||
selectedSSID = cred.ssid;
|
||||
enteredPassword = cred.password;
|
||||
selectedRequiresPassword = !cred.password.empty();
|
||||
usedSavedPassword = true;
|
||||
autoConnecting = true;
|
||||
manualNetworkListRequested = false;
|
||||
attemptConnection();
|
||||
requestUpdate();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WifiSelectionActivity::tryNextSavedNetworkFromScan() {
|
||||
for (const auto& network : networks) {
|
||||
if (!network.hasSavedPassword || hasAttemptedAutoSsid(network.ssid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto* cred = WIFI_STORE.findCredential(network.ssid);
|
||||
if (cred && tryAutoConnectCredential(*cred)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WifiSelectionActivity::handleAutoConnectFailure() {
|
||||
LOG_DBG("WIFI", "Saved network failed: %s", selectedSSID.c_str());
|
||||
WiFi.disconnect();
|
||||
|
||||
if (!networks.empty()) {
|
||||
if (tryNextSavedNetworkFromScan()) {
|
||||
return;
|
||||
}
|
||||
autoConnecting = false;
|
||||
state = WifiSelectionState::NETWORK_LIST;
|
||||
selectedNetworkIndex = 0;
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
startWifiScan(true);
|
||||
}
|
||||
|
||||
void WifiSelectionActivity::showNetworkListFromAutoConnect() {
|
||||
LOG_DBG("WIFI", "User requested manual network list");
|
||||
WiFi.disconnect();
|
||||
autoConnecting = false;
|
||||
manualNetworkListRequested = true;
|
||||
|
||||
if (networks.empty()) {
|
||||
startWifiScan(false);
|
||||
return;
|
||||
}
|
||||
|
||||
state = WifiSelectionState::NETWORK_LIST;
|
||||
selectedNetworkIndex = 0;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void WifiSelectionActivity::attemptConnection() {
|
||||
state = autoConnecting ? WifiSelectionState::AUTO_CONNECTING : WifiSelectionState::CONNECTING;
|
||||
connectionStartTime = millis();
|
||||
@@ -282,15 +364,24 @@ void WifiSelectionActivity::checkConnectionStatus() {
|
||||
if (status == WL_NO_SSID_AVAIL) {
|
||||
connectionError = tr(STR_ERROR_NETWORK_NOT_FOUND);
|
||||
}
|
||||
if (autoConnecting) {
|
||||
handleAutoConnectFailure();
|
||||
return;
|
||||
}
|
||||
state = WifiSelectionState::CONNECTION_FAILED;
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for timeout
|
||||
if (millis() - connectionStartTime > CONNECTION_TIMEOUT_MS) {
|
||||
const unsigned long timeoutMs = autoConnecting ? AUTO_CONNECTION_TIMEOUT_MS : CONNECTION_TIMEOUT_MS;
|
||||
if (millis() - connectionStartTime > timeoutMs) {
|
||||
WiFi.disconnect();
|
||||
connectionError = tr(STR_ERROR_CONNECTION_TIMEOUT);
|
||||
if (autoConnecting) {
|
||||
handleAutoConnectFailure();
|
||||
return;
|
||||
}
|
||||
state = WifiSelectionState::CONNECTION_FAILED;
|
||||
requestUpdate();
|
||||
return;
|
||||
@@ -300,12 +391,33 @@ void WifiSelectionActivity::checkConnectionStatus() {
|
||||
void WifiSelectionActivity::loop() {
|
||||
// Check scan progress
|
||||
if (state == WifiSelectionState::SCANNING) {
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
WiFi.scanDelete();
|
||||
onComplete(false);
|
||||
return;
|
||||
}
|
||||
if (autoConnecting && mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||
autoConnecting = false;
|
||||
manualNetworkListRequested = true;
|
||||
requestUpdate();
|
||||
}
|
||||
processWifiScanResults();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check connection progress
|
||||
if (state == WifiSelectionState::CONNECTING || state == WifiSelectionState::AUTO_CONNECTING) {
|
||||
if (state == WifiSelectionState::AUTO_CONNECTING) {
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
WiFi.disconnect();
|
||||
onComplete(false);
|
||||
return;
|
||||
}
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||
showNetworkListFromAutoConnect();
|
||||
return;
|
||||
}
|
||||
}
|
||||
checkConnectionStatus();
|
||||
return;
|
||||
}
|
||||
@@ -559,9 +671,15 @@ void WifiSelectionActivity::renderConnecting(const Rect* screen, const ThemeMetr
|
||||
const auto top = screen->y + (screen->height - height) / 2;
|
||||
|
||||
if (state == WifiSelectionState::SCANNING) {
|
||||
UITheme::drawCenteredText(renderer, *screen, UI_10_FONT_ID, top, tr(STR_SCANNING));
|
||||
UITheme::drawCenteredText(renderer, *screen, UI_10_FONT_ID, top,
|
||||
autoConnecting ? tr(STR_FINDING_SAVED_WIFI) : tr(STR_SCANNING));
|
||||
if (autoConnecting) {
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_CANCEL), tr(STR_SHOW_NETWORKS), "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
}
|
||||
} else {
|
||||
UITheme::drawCenteredText(renderer, *screen, UI_12_FONT_ID, top - 40, tr(STR_CONNECTING), true,
|
||||
UITheme::drawCenteredText(renderer, *screen, UI_12_FONT_ID, top - 40,
|
||||
autoConnecting ? tr(STR_CONNECTING_SAVED_WIFI) : tr(STR_CONNECTING), true,
|
||||
EpdFontFamily::BOLD);
|
||||
|
||||
std::string ssidInfo = std::string(tr(STR_TO_PREFIX)) + selectedSSID;
|
||||
@@ -569,6 +687,10 @@ void WifiSelectionActivity::renderConnecting(const Rect* screen, const ThemeMetr
|
||||
ssidInfo.replace(22, ssidInfo.length() - 22, "...");
|
||||
}
|
||||
UITheme::drawCenteredText(renderer, *screen, UI_10_FONT_ID, top, ssidInfo.c_str());
|
||||
if (autoConnecting) {
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_CANCEL), tr(STR_SHOW_NETWORKS), "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
struct Rect;
|
||||
struct ThemeMetrics;
|
||||
struct WifiCredential;
|
||||
|
||||
// Structure to hold WiFi network information
|
||||
struct WifiNetworkInfo {
|
||||
@@ -71,15 +72,22 @@ class WifiSelectionActivity final : public Activity {
|
||||
// Whether to attempt auto-connect on entry
|
||||
const bool allowAutoConnect;
|
||||
|
||||
// Whether we are attempting to auto-connect
|
||||
// Whether we are attempting to auto-connect or auto-scan saved networks.
|
||||
bool autoConnecting = false;
|
||||
|
||||
// True when the user stopped auto-connect and asked to see the scan result.
|
||||
bool manualNetworkListRequested = false;
|
||||
|
||||
// Saved SSIDs already attempted during the current auto-connect session.
|
||||
std::vector<std::string> autoAttemptedSsids;
|
||||
|
||||
// Save/forget prompt selection (0 = Yes, 1 = No)
|
||||
int savePromptSelection = 0;
|
||||
int forgetPromptSelection = 0;
|
||||
|
||||
// Connection timeout
|
||||
static constexpr unsigned long CONNECTION_TIMEOUT_MS = 15000;
|
||||
static constexpr unsigned long AUTO_CONNECTION_TIMEOUT_MS = 7000;
|
||||
unsigned long connectionStartTime = 0;
|
||||
|
||||
void renderNetworkList(const Rect* screen, const ThemeMetrics* metrics) const;
|
||||
@@ -90,11 +98,16 @@ class WifiSelectionActivity final : public Activity {
|
||||
void renderConnectionFailed(const Rect* screen, const ThemeMetrics* metrics) const;
|
||||
void renderForgetPrompt(const Rect* screen, const ThemeMetrics* metrics) const;
|
||||
|
||||
void startWifiScan();
|
||||
void startWifiScan(bool autoScan = false);
|
||||
void processWifiScanResults();
|
||||
void selectNetwork(int index);
|
||||
void attemptConnection();
|
||||
void checkConnectionStatus();
|
||||
bool tryAutoConnectCredential(const WifiCredential& cred);
|
||||
bool tryNextSavedNetworkFromScan();
|
||||
void handleAutoConnectFailure();
|
||||
void showNetworkListFromAutoConnect();
|
||||
bool hasAttemptedAutoSsid(const std::string& ssid) const;
|
||||
std::string getSignalStrengthIndicator(int32_t rssi) const;
|
||||
|
||||
void onComplete(bool connected);
|
||||
|
||||
Reference in New Issue
Block a user