Wi-Fi station mode
Joining a network you do not own, and doing it in a way that survives a router being slow, a password being wrong, and the network disappearing halfway through.
What it does
Station mode means the board joins a network somebody else runs, the same way a laptop does. It asks the router for an address, the router hands one out, and from then on the board can reach anything on that network and anything on the internet beyond it.
The part people underestimate is that joining takes time — usually two to six seconds, occasionally much longer, and sometimes never. Code that assumes the connection is up by the next line will work on your desk and fail in someone's house.
Treat "connected" as something that becomes true later
Start the connection in setup(), then check the state once per pass through
loop(). Everything that needs the network — a web server, an MQTT client, an
NTP sync — waits for that check rather than assuming.
The alternative is a device that does nothing at all while the router is slow: no display, no button, no readings, and a watchdog reset if it goes on long enough.
Two facts that catch everybody
- The ESP32, S2, S3, C3 and C6 have no 5 GHz radio. Only the C5 does. A network that works on your phone and not on the board is usually two bands sharing one name.
- The reason code is worth printing. A bad password is reason 15 or 204, 201 is a network that was never found, and those two send you in completely different directions.
The code
Connect once in setup(), then check the state instead of assuming it. The loop below never blocks for longer than a second.
#include <WiFi.h>
const char* SSID = "your-network";
const char* PASS = "your-password";
volatile uint8_t lastReason = 0; // ESP-IDF disconnect reason code
void setup() {
Serial.begin(115200);
WiFi.onEvent([](WiFiEvent_t, WiFiEventInfo_t info) {
lastReason = info.wifi_sta_disconnected.reason;
}, ARDUINO_EVENT_WIFI_STA_DISCONNECTED);
WiFi.mode(WIFI_STA);
WiFi.setSleep(false); // otherwise replies wait for the next DTIM beacon
WiFi.begin(SSID, PASS);
unsigned long start = millis();
while (WiFi.status() != WL_CONNECTED && millis() - start < 15000) {
delay(250);
Serial.print(".");
}
if (WiFi.status() != WL_CONNECTED) {
Serial.printf("\nfailed, reason %d\n", lastReason);
return; // carry on offline rather than hanging
}
Serial.println(WiFi.localIP());
}
void loop() {
if (WiFi.status() != WL_CONNECTED) {
WiFi.reconnect(); // cheap, and it does nothing if already up
delay(1000);
}
}WiFi.setSleep(false) is the line most people are missing when a board answers slowly. The default, minimum modem sleep, wakes the radio only for DTIM beacons, so a reply can wait up to one DTIM period. Turning sleep off removes that wait and keeps the receiver on.
Same idea, and you can paste it straight into the REPL to watch it happen. Credentials live in a file on the board, so changing the password does not mean reflashing.
import network, time
SSID = "your-network"
PASS = "your-password"
def connect(timeout=15):
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if wlan.isconnected():
return wlan
wlan.connect(SSID, PASS)
start = time.ticks_ms()
while not wlan.isconnected():
if time.ticks_diff(time.ticks_ms(), start) > timeout * 1000:
print("failed, status", wlan.status())
return None # carry on offline rather than hanging
time.sleep_ms(250)
print(wlan.ifconfig()[0])
return wlan
wlan = connect()wlan.status() returns a number here too. 1010 is connected and 201 means the network was not found. A wrong password depends on the reason the radio reports. 202 comes back for an authentication or connection failure, but a handshake that times out (reason 15 or 204) reads 1001, still connecting, while the board keeps retrying, which by default is forever.
When it does not work
Almost always a 5 GHz network. The ESP32, S2, S3, C3 and C6 have no 5 GHz radio; only the C5 does. The phone that set up the router may be hiding that there are two bands with the same name.
Check power before code. Transmit bursts pull over 300 mA and a thin USB cable browns the board out. The reboot looks exactly like a crash.
On the ESP32, S2 and S3 the second ADC block is shared with the radio, and reads on its pins fail while Wi-Fi is using it. Move the sensor to an ADC1 pin.
Print the disconnect reason code instead of guessing. A bad password shows as reason 15 or 204, reason 201 means the network was never found, and those two send you in completely different directions.
Joining somebody else's network is half of it. Running your own is what a board does when there is no network to join.
Access point mode →Edit this page — content/esp32/wifi-station-mode.mdx
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.