Merge pull request #136 from jpirnay/feat-try-more-networks
feat: Wi-Fi fallback: auto-retry on saved networks when primary is unavailable
This commit is contained in:
@@ -70,6 +70,9 @@ void WifiSelectionActivity::onEnter() {
|
||||
savePromptSelection = 0;
|
||||
forgetPromptSelection = 0;
|
||||
autoConnecting = false;
|
||||
autoCycleCandidates.clear();
|
||||
autoCycleCandidateIndex = 0;
|
||||
autoCycleAfterScan = false;
|
||||
|
||||
const std::string persistedMac = formatMacDashed(mac);
|
||||
if (WIFI_STORE.getLastKnownMacAddress() != persistedMac) {
|
||||
@@ -122,6 +125,7 @@ void WifiSelectionActivity::onExit() {
|
||||
|
||||
void WifiSelectionActivity::startWifiScan() {
|
||||
autoConnecting = false;
|
||||
// autoCycleAfterScan intentionally preserved when set by the auto-cycle flow
|
||||
state = WifiSelectionState::SCANNING;
|
||||
networks.clear();
|
||||
requestUpdate();
|
||||
@@ -135,6 +139,81 @@ void WifiSelectionActivity::startWifiScan() {
|
||||
WiFi.scanNetworks(true); // true = async scan
|
||||
}
|
||||
|
||||
void WifiSelectionActivity::buildAutoCycleCandidates() {
|
||||
autoCycleCandidates.clear();
|
||||
autoCycleCandidateIndex = 0;
|
||||
|
||||
const std::string& skipSsid = WIFI_STORE.getLastConnectedSsid(); // already tried
|
||||
|
||||
struct Candidate {
|
||||
std::string ssid;
|
||||
int32_t rssi;
|
||||
};
|
||||
std::vector<Candidate> candidates;
|
||||
|
||||
for (const auto& net : networks) {
|
||||
if (net.ssid == skipSsid) continue;
|
||||
if (!WIFI_STORE.hasSavedCredential(net.ssid)) continue;
|
||||
candidates.push_back({net.ssid, net.rssi});
|
||||
}
|
||||
|
||||
std::sort(candidates.begin(), candidates.end(),
|
||||
[](const Candidate& a, const Candidate& b) { return a.rssi > b.rssi; });
|
||||
|
||||
std::transform(candidates.begin(), candidates.end(), std::back_inserter(autoCycleCandidates),
|
||||
[](const Candidate& c) { return c.ssid; });
|
||||
|
||||
LOG_DBG("WIFI", "Auto-cycle candidates: %zu", autoCycleCandidates.size());
|
||||
}
|
||||
|
||||
void WifiSelectionActivity::tryNextAutoCycleCandidate() {
|
||||
if (autoCycleCandidateIndex >= autoCycleCandidates.size()) {
|
||||
// All candidates exhausted — fall through to manual selection
|
||||
LOG_DBG("WIFI", "Auto-cycle exhausted, falling through to network list");
|
||||
state = WifiSelectionState::NETWORK_LIST;
|
||||
selectedNetworkIndex = 0;
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string& ssid = autoCycleCandidates[autoCycleCandidateIndex++];
|
||||
const auto* cred = WIFI_STORE.findCredential(ssid);
|
||||
if (!cred) {
|
||||
tryNextAutoCycleCandidate(); // Credential disappeared, skip
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DBG("WIFI", "Auto-cycle trying %s (%zu/%zu)", ssid.c_str(), autoCycleCandidateIndex, autoCycleCandidates.size());
|
||||
|
||||
selectedSSID = cred->ssid;
|
||||
enteredPassword = cred->password;
|
||||
selectedRequiresPassword = !cred->password.empty();
|
||||
usedSavedPassword = true;
|
||||
autoConnecting = false;
|
||||
|
||||
state = WifiSelectionState::AUTO_CYCLING;
|
||||
connectionStartTime = millis();
|
||||
connectedIP.clear();
|
||||
connectionError.clear();
|
||||
requestUpdate();
|
||||
|
||||
WiFi.persistent(false);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.disconnect(true, true);
|
||||
delay(100);
|
||||
|
||||
uint8_t baseMac[6];
|
||||
readDeviceBaseMac(baseMac);
|
||||
String hostname = "CrossPoint-Reader-" + formatMacCompact(baseMac);
|
||||
WiFi.setHostname(hostname.c_str());
|
||||
|
||||
if (selectedRequiresPassword && !enteredPassword.empty()) {
|
||||
WiFi.begin(selectedSSID.c_str(), enteredPassword.c_str());
|
||||
} else {
|
||||
WiFi.begin(selectedSSID.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void WifiSelectionActivity::processWifiScanResults() {
|
||||
const int16_t scanResult = WiFi.scanComplete();
|
||||
|
||||
@@ -144,6 +223,7 @@ void WifiSelectionActivity::processWifiScanResults() {
|
||||
}
|
||||
|
||||
if (scanResult == WIFI_SCAN_FAILED) {
|
||||
autoCycleAfterScan = false;
|
||||
state = WifiSelectionState::NETWORK_LIST;
|
||||
requestUpdate();
|
||||
return;
|
||||
@@ -191,6 +271,14 @@ void WifiSelectionActivity::processWifiScanResults() {
|
||||
});
|
||||
|
||||
WiFi.scanDelete();
|
||||
|
||||
if (autoCycleAfterScan) {
|
||||
autoCycleAfterScan = false;
|
||||
buildAutoCycleCandidates();
|
||||
tryNextAutoCycleCandidate();
|
||||
return;
|
||||
}
|
||||
|
||||
state = WifiSelectionState::NETWORK_LIST;
|
||||
selectedNetworkIndex = 0;
|
||||
requestUpdate();
|
||||
@@ -296,7 +384,8 @@ bool WifiSelectionActivity::checkCaptivePortal() {
|
||||
}
|
||||
|
||||
void WifiSelectionActivity::checkConnectionStatus() {
|
||||
if (state != WifiSelectionState::CONNECTING && state != WifiSelectionState::AUTO_CONNECTING) {
|
||||
if (state != WifiSelectionState::CONNECTING && state != WifiSelectionState::AUTO_CONNECTING &&
|
||||
state != WifiSelectionState::AUTO_CYCLING) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -340,7 +429,20 @@ void WifiSelectionActivity::checkConnectionStatus() {
|
||||
return;
|
||||
}
|
||||
|
||||
const unsigned long timeout =
|
||||
state == WifiSelectionState::AUTO_CYCLING ? AUTO_CYCLE_TIMEOUT_MS : CONNECTION_TIMEOUT_MS;
|
||||
|
||||
if (status == WL_CONNECT_FAILED || status == WL_NO_SSID_AVAIL) {
|
||||
if (state == WifiSelectionState::AUTO_CONNECTING) {
|
||||
// Primary SSID failed — scan and try remaining saved credentials
|
||||
autoCycleAfterScan = true;
|
||||
startWifiScan();
|
||||
return;
|
||||
}
|
||||
if (state == WifiSelectionState::AUTO_CYCLING) {
|
||||
tryNextAutoCycleCandidate();
|
||||
return;
|
||||
}
|
||||
connectionError = tr(STR_ERROR_GENERAL_FAILURE);
|
||||
if (status == WL_NO_SSID_AVAIL) {
|
||||
connectionError = tr(STR_ERROR_NETWORK_NOT_FOUND);
|
||||
@@ -351,8 +453,17 @@ void WifiSelectionActivity::checkConnectionStatus() {
|
||||
}
|
||||
|
||||
// Check for timeout
|
||||
if (millis() - connectionStartTime > CONNECTION_TIMEOUT_MS) {
|
||||
if (millis() - connectionStartTime > timeout) {
|
||||
WiFi.disconnect();
|
||||
if (state == WifiSelectionState::AUTO_CONNECTING) {
|
||||
autoCycleAfterScan = true;
|
||||
startWifiScan();
|
||||
return;
|
||||
}
|
||||
if (state == WifiSelectionState::AUTO_CYCLING) {
|
||||
tryNextAutoCycleCandidate();
|
||||
return;
|
||||
}
|
||||
connectionError = tr(STR_ERROR_CONNECTION_TIMEOUT);
|
||||
state = WifiSelectionState::CONNECTION_FAILED;
|
||||
requestUpdate();
|
||||
@@ -368,7 +479,8 @@ void WifiSelectionActivity::loop() {
|
||||
}
|
||||
|
||||
// Check connection progress
|
||||
if (state == WifiSelectionState::CONNECTING || state == WifiSelectionState::AUTO_CONNECTING) {
|
||||
if (state == WifiSelectionState::CONNECTING || state == WifiSelectionState::AUTO_CONNECTING ||
|
||||
state == WifiSelectionState::AUTO_CYCLING) {
|
||||
checkConnectionStatus();
|
||||
return;
|
||||
}
|
||||
@@ -573,6 +685,7 @@ void WifiSelectionActivity::render(RenderLock&&) {
|
||||
|
||||
switch (state) {
|
||||
case WifiSelectionState::AUTO_CONNECTING:
|
||||
case WifiSelectionState::AUTO_CYCLING:
|
||||
renderConnecting();
|
||||
break;
|
||||
case WifiSelectionState::SCANNING:
|
||||
|
||||
@@ -21,6 +21,7 @@ struct WifiNetworkInfo {
|
||||
// WiFi selection states
|
||||
enum class WifiSelectionState {
|
||||
AUTO_CONNECTING, // Trying to connect to the last known network
|
||||
AUTO_CYCLING, // Cycling through remaining saved credentials after AUTO_CONNECTING failed
|
||||
SCANNING, // Scanning for networks
|
||||
NETWORK_LIST, // Displaying available networks
|
||||
PASSWORD_ENTRY, // Entering password for selected network
|
||||
@@ -73,12 +74,18 @@ class WifiSelectionActivity final : public Activity {
|
||||
// Whether we are attempting to auto-connect
|
||||
bool autoConnecting = false;
|
||||
|
||||
// Saved-credential candidates for auto-cycling (SSIDs visible in scan, sorted by RSSI desc)
|
||||
std::vector<std::string> autoCycleCandidates;
|
||||
size_t autoCycleCandidateIndex = 0;
|
||||
bool autoCycleAfterScan = false; // Scan was triggered to build cycle candidates
|
||||
|
||||
// Save/forget prompt selection (0 = Yes, 1 = No)
|
||||
int savePromptSelection = 0;
|
||||
int forgetPromptSelection = 0;
|
||||
|
||||
// Connection timeout
|
||||
// Connection timeouts
|
||||
static constexpr unsigned long CONNECTION_TIMEOUT_MS = 15000;
|
||||
static constexpr unsigned long AUTO_CYCLE_TIMEOUT_MS = 5000;
|
||||
unsigned long connectionStartTime = 0;
|
||||
|
||||
void renderNetworkList() const;
|
||||
@@ -92,6 +99,8 @@ class WifiSelectionActivity final : public Activity {
|
||||
|
||||
void startWifiScan();
|
||||
void processWifiScanResults();
|
||||
void buildAutoCycleCandidates();
|
||||
void tryNextAutoCycleCandidate();
|
||||
void selectNetwork(int index);
|
||||
void attemptConnection();
|
||||
void checkConnectionStatus();
|
||||
|
||||
Reference in New Issue
Block a user