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
|
## Join Network Mode
|
||||||
|
|
||||||
1. Select **Join Network**.
|
1. Select **Join Network**.
|
||||||
2. Pick a 2.4 GHz Wi-Fi network from the scan results.
|
2. If you have saved Wi-Fi credentials, CrossPoint first tries the last
|
||||||
3. Enter the password if prompted.
|
connected network, then other visible saved networks in signal-strength
|
||||||
4. Save credentials if you want the reader to reconnect automatically next time.
|
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:
|
After connection, the reader shows:
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,10 @@ STR_WIFI_NETWORKS: "Wi-Fi Networks"
|
|||||||
STR_NO_NETWORKS: "No networks found"
|
STR_NO_NETWORKS: "No networks found"
|
||||||
STR_NETWORKS_FOUND: "%zu networks found"
|
STR_NETWORKS_FOUND: "%zu networks found"
|
||||||
STR_SCANNING: "Scanning..."
|
STR_SCANNING: "Scanning..."
|
||||||
|
STR_FINDING_SAVED_WIFI: "Finding saved Wi-Fi..."
|
||||||
STR_CONNECTING: "Connecting..."
|
STR_CONNECTING: "Connecting..."
|
||||||
|
STR_CONNECTING_SAVED_WIFI: "Connecting to saved Wi-Fi..."
|
||||||
|
STR_SHOW_NETWORKS: "Show"
|
||||||
STR_CONNECTED: "Connected!"
|
STR_CONNECTED: "Connected!"
|
||||||
STR_CONNECTION_FAILED: "Connection Failed"
|
STR_CONNECTION_FAILED: "Connection Failed"
|
||||||
STR_FORGET_NETWORK: "Forget Network?"
|
STR_FORGET_NETWORK: "Forget Network?"
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
#include <Logging.h>
|
#include <Logging.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
#include "WifiCredentialStore.h"
|
#include "WifiCredentialStore.h"
|
||||||
@@ -35,6 +37,9 @@ void WifiSelectionActivity::onEnter() {
|
|||||||
savePromptSelection = 0;
|
savePromptSelection = 0;
|
||||||
forgetPromptSelection = 0;
|
forgetPromptSelection = 0;
|
||||||
autoConnecting = false;
|
autoConnecting = false;
|
||||||
|
manualNetworkListRequested = false;
|
||||||
|
autoAttemptedSsids.clear();
|
||||||
|
autoAttemptedSsids.reserve(WIFI_STORE.getCredentials().size());
|
||||||
|
|
||||||
// Cache MAC address for display
|
// Cache MAC address for display
|
||||||
uint8_t mac[6];
|
uint8_t mac[6];
|
||||||
@@ -47,23 +52,20 @@ void WifiSelectionActivity::onEnter() {
|
|||||||
// Trigger first update to show scanning message
|
// Trigger first update to show scanning message
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
|
|
||||||
// Attempt to auto-connect to the last network
|
// Attempt to auto-connect to known networks. Try the last successful
|
||||||
if (allowAutoConnect) {
|
// 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();
|
const std::string lastSsid = WIFI_STORE.getLastConnectedSsid();
|
||||||
if (!lastSsid.empty()) {
|
if (!lastSsid.empty()) {
|
||||||
const auto* cred = WIFI_STORE.findCredential(lastSsid);
|
const auto* cred = WIFI_STORE.findCredential(lastSsid);
|
||||||
if (cred) {
|
if (cred && tryAutoConnectCredential(*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();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startWifiScan(true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to scanning
|
// Fallback to scanning
|
||||||
@@ -87,8 +89,9 @@ void WifiSelectionActivity::onExit() {
|
|||||||
LOG_DBG("WIFI", "Free heap at onExit end: %d bytes", ESP.getFreeHeap());
|
LOG_DBG("WIFI", "Free heap at onExit end: %d bytes", ESP.getFreeHeap());
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiSelectionActivity::startWifiScan() {
|
void WifiSelectionActivity::startWifiScan(const bool autoScan) {
|
||||||
autoConnecting = false;
|
autoConnecting = autoScan;
|
||||||
|
manualNetworkListRequested = false;
|
||||||
state = WifiSelectionState::SCANNING;
|
state = WifiSelectionState::SCANNING;
|
||||||
networks.clear();
|
networks.clear();
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
@@ -111,6 +114,8 @@ void WifiSelectionActivity::processWifiScanResults() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (scanResult == WIFI_SCAN_FAILED) {
|
if (scanResult == WIFI_SCAN_FAILED) {
|
||||||
|
autoConnecting = false;
|
||||||
|
manualNetworkListRequested = false;
|
||||||
state = WifiSelectionState::NETWORK_LIST;
|
state = WifiSelectionState::NETWORK_LIST;
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
return;
|
return;
|
||||||
@@ -154,6 +159,13 @@ void WifiSelectionActivity::processWifiScanResults() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
WiFi.scanDelete();
|
WiFi.scanDelete();
|
||||||
|
|
||||||
|
if (autoConnecting && !manualNetworkListRequested && tryNextSavedNetworkFromScan()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
autoConnecting = false;
|
||||||
|
manualNetworkListRequested = false;
|
||||||
state = WifiSelectionState::NETWORK_LIST;
|
state = WifiSelectionState::NETWORK_LIST;
|
||||||
selectedNetworkIndex = 0;
|
selectedNetworkIndex = 0;
|
||||||
requestUpdate();
|
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() {
|
void WifiSelectionActivity::attemptConnection() {
|
||||||
state = autoConnecting ? WifiSelectionState::AUTO_CONNECTING : WifiSelectionState::CONNECTING;
|
state = autoConnecting ? WifiSelectionState::AUTO_CONNECTING : WifiSelectionState::CONNECTING;
|
||||||
connectionStartTime = millis();
|
connectionStartTime = millis();
|
||||||
@@ -282,15 +364,24 @@ void WifiSelectionActivity::checkConnectionStatus() {
|
|||||||
if (status == WL_NO_SSID_AVAIL) {
|
if (status == WL_NO_SSID_AVAIL) {
|
||||||
connectionError = tr(STR_ERROR_NETWORK_NOT_FOUND);
|
connectionError = tr(STR_ERROR_NETWORK_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
if (autoConnecting) {
|
||||||
|
handleAutoConnectFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
state = WifiSelectionState::CONNECTION_FAILED;
|
state = WifiSelectionState::CONNECTION_FAILED;
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for timeout
|
// 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();
|
WiFi.disconnect();
|
||||||
connectionError = tr(STR_ERROR_CONNECTION_TIMEOUT);
|
connectionError = tr(STR_ERROR_CONNECTION_TIMEOUT);
|
||||||
|
if (autoConnecting) {
|
||||||
|
handleAutoConnectFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
state = WifiSelectionState::CONNECTION_FAILED;
|
state = WifiSelectionState::CONNECTION_FAILED;
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
return;
|
return;
|
||||||
@@ -300,12 +391,33 @@ void WifiSelectionActivity::checkConnectionStatus() {
|
|||||||
void WifiSelectionActivity::loop() {
|
void WifiSelectionActivity::loop() {
|
||||||
// Check scan progress
|
// Check scan progress
|
||||||
if (state == WifiSelectionState::SCANNING) {
|
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();
|
processWifiScanResults();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check connection progress
|
// Check connection progress
|
||||||
if (state == WifiSelectionState::CONNECTING || state == WifiSelectionState::AUTO_CONNECTING) {
|
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();
|
checkConnectionStatus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -559,9 +671,15 @@ void WifiSelectionActivity::renderConnecting(const Rect* screen, const ThemeMetr
|
|||||||
const auto top = screen->y + (screen->height - height) / 2;
|
const auto top = screen->y + (screen->height - height) / 2;
|
||||||
|
|
||||||
if (state == WifiSelectionState::SCANNING) {
|
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 {
|
} 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);
|
EpdFontFamily::BOLD);
|
||||||
|
|
||||||
std::string ssidInfo = std::string(tr(STR_TO_PREFIX)) + selectedSSID;
|
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, "...");
|
ssidInfo.replace(22, ssidInfo.length() - 22, "...");
|
||||||
}
|
}
|
||||||
UITheme::drawCenteredText(renderer, *screen, UI_10_FONT_ID, top, ssidInfo.c_str());
|
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 Rect;
|
||||||
struct ThemeMetrics;
|
struct ThemeMetrics;
|
||||||
|
struct WifiCredential;
|
||||||
|
|
||||||
// Structure to hold WiFi network information
|
// Structure to hold WiFi network information
|
||||||
struct WifiNetworkInfo {
|
struct WifiNetworkInfo {
|
||||||
@@ -71,15 +72,22 @@ class WifiSelectionActivity final : public Activity {
|
|||||||
// Whether to attempt auto-connect on entry
|
// Whether to attempt auto-connect on entry
|
||||||
const bool allowAutoConnect;
|
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;
|
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)
|
// Save/forget prompt selection (0 = Yes, 1 = No)
|
||||||
int savePromptSelection = 0;
|
int savePromptSelection = 0;
|
||||||
int forgetPromptSelection = 0;
|
int forgetPromptSelection = 0;
|
||||||
|
|
||||||
// Connection timeout
|
// Connection timeout
|
||||||
static constexpr unsigned long CONNECTION_TIMEOUT_MS = 15000;
|
static constexpr unsigned long CONNECTION_TIMEOUT_MS = 15000;
|
||||||
|
static constexpr unsigned long AUTO_CONNECTION_TIMEOUT_MS = 7000;
|
||||||
unsigned long connectionStartTime = 0;
|
unsigned long connectionStartTime = 0;
|
||||||
|
|
||||||
void renderNetworkList(const Rect* screen, const ThemeMetrics* metrics) const;
|
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 renderConnectionFailed(const Rect* screen, const ThemeMetrics* metrics) const;
|
||||||
void renderForgetPrompt(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 processWifiScanResults();
|
||||||
void selectNetwork(int index);
|
void selectNetwork(int index);
|
||||||
void attemptConnection();
|
void attemptConnection();
|
||||||
void checkConnectionStatus();
|
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;
|
std::string getSignalStrengthIndicator(int32_t rssi) const;
|
||||||
|
|
||||||
void onComplete(bool connected);
|
void onComplete(bool connected);
|
||||||
|
|||||||
Reference in New Issue
Block a user