feat: Hidden wifi ssid support (#2360)

This commit is contained in:
Mr.Catfood
2026-07-13 02:33:48 -04:00
committed by GitHub
parent 73ab9ab20d
commit bfa1d706b1
28 changed files with 253 additions and 21 deletions
+103 -20
View File
@@ -28,6 +28,7 @@ void WifiSelectionActivity::onEnter() {
// Reset state
selectedNetworkIndex = 0;
networks.clear();
realNetworkCount = 0;
state = WifiSelectionState::SCANNING;
selectedSSID.clear();
connectedIP.clear();
@@ -114,9 +115,13 @@ void WifiSelectionActivity::processWifiScanResults() {
}
if (scanResult == WIFI_SCAN_FAILED) {
networks.clear();
realNetworkCount = 0;
appendHiddenNetworkEntry();
autoConnecting = false;
manualNetworkListRequested = false;
state = WifiSelectionState::NETWORK_LIST;
selectedNetworkIndex = 0;
requestUpdate();
return;
}
@@ -158,6 +163,9 @@ void WifiSelectionActivity::processWifiScanResults() {
return a.rssi > b.rssi;
});
realNetworkCount = networks.size();
appendHiddenNetworkEntry();
WiFi.scanDelete();
if (autoConnecting && !manualNetworkListRequested && tryNextSavedNetworkFromScan()) {
@@ -171,12 +179,30 @@ void WifiSelectionActivity::processWifiScanResults() {
requestUpdate();
}
void WifiSelectionActivity::appendHiddenNetworkEntry() {
// Synthetic list entry that lets the user type an SSID that is not broadcast.
// ESP32 can join hidden APs as long as the SSID is supplied to WiFi.begin().
WifiNetworkInfo placeholder;
placeholder.rssi = 0;
placeholder.isEncrypted = true; // Treated as encrypted; an empty password still connects open APs
placeholder.hasSavedPassword = false;
placeholder.isHiddenPlaceholder = true;
networks.push_back(std::move(placeholder));
}
void WifiSelectionActivity::selectNetwork(const int index) {
if (index < 0 || index >= static_cast<int>(networks.size())) {
return;
}
const auto& network = networks[index];
// Synthetic "Add hidden network..." entry: prompt the user to type the SSID first
if (network.isHiddenPlaceholder) {
promptHiddenSsid();
return;
}
selectedSSID = network.ssid;
selectedRequiresPassword = network.isEncrypted;
usedSavedPassword = false;
@@ -195,27 +221,57 @@ void WifiSelectionActivity::selectNetwork(const int index) {
}
if (selectedRequiresPassword) {
// Show password entry
state = WifiSelectionState::PASSWORD_ENTRY;
// Don't allow screen updates while changing activity
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_ENTER_WIFI_PASSWORD),
"", // No initial text
64, // Max password length
InputType::Password),
[this](const ActivityResult& result) {
if (result.isCancelled) {
state = WifiSelectionState::NETWORK_LIST;
} else {
enteredPassword = std::get<KeyboardResult>(result.data).text;
// state will be updated in next loop iteration
}
});
promptPasswordEntry();
} else {
// Connect directly for open networks
attemptConnection();
}
}
void WifiSelectionActivity::promptPasswordEntry() {
// Show password entry
state = WifiSelectionState::PASSWORD_ENTRY;
// Don't allow screen updates while changing activity
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_ENTER_WIFI_PASSWORD),
"", // No initial text
64, // Max password length
InputType::Password),
[this](const ActivityResult& result) {
if (result.isCancelled) {
state = WifiSelectionState::NETWORK_LIST;
} else {
enteredPassword = std::get<KeyboardResult>(result.data).text;
// state will be updated in next loop iteration
}
});
}
void WifiSelectionActivity::promptHiddenSsid() {
selectedSSID.clear();
selectedRequiresPassword = true; // Hidden networks are usually encrypted; empty password still joins open APs
usedSavedPassword = false;
enteredPassword.clear();
autoConnecting = false;
// Suppress rendering during the activity transition (see render()).
state = WifiSelectionState::HIDDEN_SSID_ENTRY;
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_ENTER_WIFI_SSID),
"", // No initial text
32, // Max SSID length (IEEE 802.11: 32 bytes)
InputType::Text),
[this](const ActivityResult& result) {
if (result.isCancelled) {
state = WifiSelectionState::NETWORK_LIST;
return;
}
selectedSSID = std::get<KeyboardResult>(result.data).text;
if (selectedSSID.empty()) {
state = WifiSelectionState::NETWORK_LIST;
}
// Otherwise stay in HIDDEN_SSID_ENTRY; loop() continues the flow.
});
}
bool WifiSelectionActivity::hasAttemptedAutoSsid(const std::string& ssid) const {
return std::find(autoAttemptedSsids.begin(), autoAttemptedSsids.end(), ssid) != autoAttemptedSsids.end();
}
@@ -422,6 +478,22 @@ void WifiSelectionActivity::loop() {
return;
}
// Reached once the hidden-network SSID has been entered (and was non-empty).
if (state == WifiSelectionState::HIDDEN_SSID_ENTRY) {
const auto* savedCred = WIFI_STORE.findCredential(selectedSSID);
if (savedCred && !savedCred->password.empty()) {
// We already know this hidden network - connect with the saved password
enteredPassword = savedCred->password;
usedSavedPassword = true;
LOG_DBG("WiFi", "Using saved password for hidden network %s", selectedSSID.c_str());
attemptConnection();
} else {
// Prompt for the password (empty password connects to open hidden APs)
promptPasswordEntry();
}
return;
}
if (state == WifiSelectionState::PASSWORD_ENTRY) {
// Reach here once password entry finished in subactivity
attemptConnection();
@@ -582,9 +654,9 @@ std::string WifiSelectionActivity::getSignalStrengthIndicator(const int32_t rssi
}
void WifiSelectionActivity::render(RenderLock&&) {
// Don't render if we're in PASSWORD_ENTRY state - we're just transitioning
// Don't render if we're in a keyboard-entry state - we're just transitioning
// from the keyboard subactivity back to the main activity
if (state == WifiSelectionState::PASSWORD_ENTRY) {
if (state == WifiSelectionState::PASSWORD_ENTRY || state == WifiSelectionState::HIDDEN_SSID_ENTRY) {
return;
}
@@ -596,7 +668,7 @@ void WifiSelectionActivity::render(RenderLock&&) {
// Draw header
char countStr[32];
snprintf(countStr, sizeof(countStr), tr(STR_NETWORKS_FOUND), networks.size());
snprintf(countStr, sizeof(countStr), tr(STR_NETWORKS_FOUND), realNetworkCount);
GUI.drawHeader(renderer, Rect{screen.x, screen.y + metrics.topPadding, screen.width, metrics.headerHeight},
tr(STR_WIFI_NETWORKS), countStr);
GUI.drawSubHeader(
@@ -614,6 +686,9 @@ void WifiSelectionActivity::render(RenderLock&&) {
case WifiSelectionState::NETWORK_LIST:
renderNetworkList(&screen, &metrics);
break;
case WifiSelectionState::HIDDEN_SSID_ENTRY:
// Transitioning to/from the SSID keyboard subactivity - nothing to draw
break;
case WifiSelectionState::CONNECTING:
renderConnecting(&screen, &metrics);
break;
@@ -647,9 +722,17 @@ void WifiSelectionActivity::renderNetworkList(const Rect* screen, const ThemeMet
int contentHeight = screen->height - contentTop - metrics->verticalSpacing * 2;
GUI.drawList(
renderer, Rect{screen->x, contentTop, screen->width, contentHeight}, static_cast<int>(networks.size()),
selectedNetworkIndex, [this](int index) { return networks[index].ssid; }, nullptr, nullptr,
selectedNetworkIndex,
[this](int index) {
auto network = networks[index];
const auto& network = networks[index];
return network.isHiddenPlaceholder ? std::string(tr(STR_ADD_HIDDEN_NETWORK)) : network.ssid;
},
nullptr, nullptr,
[this](int index) {
const auto& network = networks[index];
if (network.isHiddenPlaceholder) {
return std::string();
}
return std::string(network.hasSavedPassword ? "+ " : "") + (network.isEncrypted ? "* " : "") +
getSignalStrengthIndicator(network.rssi);
});
@@ -18,7 +18,8 @@ struct WifiNetworkInfo {
std::string ssid;
int32_t rssi;
bool isEncrypted;
bool hasSavedPassword; // Whether we have saved credentials for this network
bool hasSavedPassword; // Whether we have saved credentials for this network
bool isHiddenPlaceholder = false; // Synthetic "Add hidden network..." list entry
};
// WiFi selection states
@@ -26,6 +27,7 @@ enum class WifiSelectionState {
AUTO_CONNECTING, // Trying to connect to the last known network
SCANNING, // Scanning for networks
NETWORK_LIST, // Displaying available networks
HIDDEN_SSID_ENTRY, // Entering SSID for a hidden network
PASSWORD_ENTRY, // Entering password for selected network
CONNECTING, // Attempting to connect
CONNECTED, // Successfully connected
@@ -51,6 +53,8 @@ class WifiSelectionActivity final : public Activity {
WifiSelectionState state = WifiSelectionState::SCANNING;
size_t selectedNetworkIndex = 0;
std::vector<WifiNetworkInfo> networks;
// Number of real (scanned) networks, excluding the synthetic hidden-network entry
size_t realNetworkCount = 0;
// Selected network for connection
std::string selectedSSID;
@@ -100,7 +104,10 @@ class WifiSelectionActivity final : public Activity {
void startWifiScan(bool autoScan = false);
void processWifiScanResults();
void appendHiddenNetworkEntry();
void selectNetwork(int index);
void promptHiddenSsid();
void promptPasswordEntry();
void attemptConnection();
void checkConnectionStatus();
bool tryAutoConnectCredential(const WifiCredential& cred);