Lesson 2 of 5 · 18 min
Joining Wi-Fi as a station
Joining a Wi-Fi network looks like one line, WiFi.begin(ssid, pass), and in a demo it works. In the real world access points reboot, routers are moved, signal fades and DHCP is slow. A device that connects once and never checks again will silently go deaf the first time any of that happens. This lesson builds the connection the way a product would: event driven, with a polite reconnect, a signal meter and no secrets in the source code.
Station mode
The ESP32 can act in three roles: station (STA), which joins an existing network like your phone does; access point (AP), which creates its own network; or both together. Almost every sensor node uses station mode. Two limits to remember: the radio is 2.4 GHz only, so a 5 GHz-only network is invisible to it, and it speaks WPA2 and WPA3 personal but not captive-portal logins.
A connection goes through stages, and each can fail for a different reason:
- Scan and associate with the access point (wrong password fails here).
- Authenticate the security handshake.
- DHCP to receive an IP address from the router.
Only after the third stage can you send any data. This is why checking WiFi.status() once after a fixed delay(5000) is a poor approach: the time needed varies from under one second to ten or more.
Events instead of polling
The Wi-Fi driver notifies you of changes through events. You register one callback and the driver calls it when the state changes:
| Event | Meaning |
|---|---|
ARDUINO_EVENT_WIFI_STA_CONNECTED | Associated with the access point, no IP yet |
ARDUINO_EVENT_WIFI_STA_GOT_IP | DHCP finished, the network is usable |
ARDUINO_EVENT_WIFI_STA_DISCONNECTED | Link lost or attempt failed, with a reason code |
The callback runs in a system task, not in your loop(). So keep it short: set a flag, record a time, print a line. Do the real work in loop().
Reconnect with backoff
The core can reconnect by itself (WiFi.setAutoReconnect(true)), but you then have no say in how often it tries. If the router is down for an hour, a device retrying every second wastes power and spams the network. A better rule is exponential backoff: wait 1 s, then 2 s, 4 s, 8 s and so on up to a ceiling of 60 s, and reset to 1 s after a success.
The worst case wait before an attempt is capped at 60 s, so after a long outage you are back online within about a minute of the router returning, while the number of attempts per hour falls from 3600 to about 60.
#include <WiFi.h>
#include <Preferences.h>
Preferences prefs;
String ssid, pass;
volatile bool connected = false; // written by the event task, read by loop()
volatile bool retryPending = false;
volatile uint32_t nextTryMs = 0;
uint32_t backoffMs = 1000; // start at 1 s, doubles on every failure
void onWiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
switch (event) {
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
connected = true;
backoffMs = 1000; // success: reset the backoff
Serial.printf("IP %s, RSSI %d dBm\n",
WiFi.localIP().toString().c_str(), WiFi.RSSI());
break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
connected = false;
retryPending = true;
nextTryMs = millis() + backoffMs; // schedule the next attempt
backoffMs = min<uint32_t>(backoffMs * 2, 60000); // double, capped at 60 s
Serial.printf("Disconnected, reason %d\n", info.wifi_sta_disconnected.reason);
break;
default:
break;
}
}
void setup() {
Serial.begin(115200);
prefs.begin("wifi", true); // read-only view of flash storage
ssid = prefs.getString("ssid", "");
pass = prefs.getString("pass", "");
prefs.end();
if (ssid.isEmpty()) {
Serial.println("No credentials stored. Run the provisioning sketch first.");
while (true) delay(1000);
}
WiFi.persistent(false); // do not rewrite flash on every begin
WiFi.setAutoReconnect(false); // we control the retry policy
WiFi.mode(WIFI_STA);
WiFi.onEvent(onWiFiEvent);
WiFi.begin(ssid.c_str(), pass.c_str());
}
void loop() {
if (retryPending && millis() >= nextTryMs) { // backoff time has passed
retryPending = false;
WiFi.begin(ssid.c_str(), pass.c_str());
}
// the rest of your program runs here, never blocked by Wi-Fi
}
Each failed attempt also raises a disconnect event, so the chain of retries keeps itself going until GOT_IP arrives.
Signal strength: RSSI
WiFi.RSSI() returns the received signal strength in dBm, a logarithmic unit relative to 1 mW:
P = 1 mW x 10^(dBm / 10)
So -50 dBm is 10^-5 mW = 10 nW, and -90 dBm is 10^-9 mW = 1 pW. Every 10 dB is a factor of ten, and every 3 dB is roughly a factor of two.
| RSSI | Quality | What to expect |
|---|---|---|
| -50 dBm or higher | Excellent | Fast and stable |
| -60 dBm | Good | Fine for anything |
| -70 dBm | Fair | Fine for sensor data |
| -80 dBm | Weak | Drops and retries |
| -90 dBm or lower | Unusable | Often disconnects |
Going from -60 to -70 dBm costs a factor of ten in power, yet a sensor node still works. Log the RSSI along with your readings: when something misbehaves, it is the first number worth checking.
Never hardcode credentials
Writing const char* ssid = "HomeNet"; and const char* pass = "..." into a sketch feels harmless until you commit it, paste it into a forum post or flash the same binary onto a device you give away. Passwords in source live forever in version history.
Use one of these instead:
- Preferences (NVS), as above. The credentials sit in a flash partition, separate from the code. Fill them once with a small provisioning sketch that reads them from the Serial Monitor.
- A
secrets.hfile listed in.gitignore, with asecrets.example.hcommitted so others know what to create. - A setup portal, where the device starts its own access point and the user types the credentials into a web page. This is how consumer products do it.
A provisioning sketch for the first option:
#include <Preferences.h>
Preferences prefs;
String ask(const char* label) {
Serial.printf("%s: ", label);
while (!Serial.available()) delay(10); // wait for the user to type
String s = Serial.readStringUntil('\n');
s.trim(); // strip the trailing newline
Serial.println();
return s;
}
void setup() {
Serial.begin(115200);
String s = ask("SSID");
String p = ask("Password");
prefs.begin("wifi", false); // read-write
prefs.putString("ssid", s);
prefs.putString("pass", p);
prefs.end();
Serial.println("Saved. Now flash the real sketch.");
}
void loop() {}
Check yourself
Your signal changes from -60 dBm to -70 dBm. How much weaker is the received power?
Check yourself
Why is it a bad idea to write the Wi-Fi password directly in the sketch?