08 · Connecting to WiFi¶
Not flashed to hardware
Reasoned through against the documented ESP8266WiFi/WiFi (ESP32)
library APIs — WiFi.mode(WIFI_STA), WiFi.begin(ssid, password),
WiFi.status(), the WL_CONNECTED constant, and WiFi.localIP() are
part of the stable, documented API surface shared by both cores. Not
compiled or flashed to physical hardware in this environment — actual
connection timing depends heavily on your router and signal strength.
Station mode vs. access point mode¶
Both chips can act as a station (STA, joining an existing WiFi
network as a client — like your phone joining home WiFi) or an access
point (AP, creating its own network for other devices to join). This
module covers station mode only, since that's what a sensor node
reporting to your home network needs; AP mode appears later in this course
(Level 3's captive-portal module) for initial device setup without a
pre-known network to join.
Basic connection sketch¶
// wifi-station-connect.ino
#if defined(ESP8266)
#include <ESP8266WiFi.h>
#elif defined(ESP32)
#include <WiFi.h>
#endif
const char* WIFI_SSID = "YourNetworkName";
const char* WIFI_PASSWORD = "YourNetworkPassword";
void setup() {
Serial.begin(115200);
delay(100);
WiFi.mode(WIFI_STA); // station mode: join a network, don't host one
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("Connected! IP address: ");
Serial.println(WiFi.localIP());
}
void loop() {
// Connection established in setup(); nothing more to do here yet.
}
Why WiFi.mode(WIFI_STA) first¶
Calling WiFi.mode(WIFI_STA) explicitly, before WiFi.begin(), avoids a
documented gotcha on both cores: if the board previously ran AP-mode or
AP+STA-mode code (or a factory-default sketch did), the radio can be left
in a combined mode that behaves unpredictably for a simple station
connection. Setting the mode explicitly at the top of setup() is standard
practice and costs nothing.
The blocking while loop is fine here, but only here¶
while (WiFi.status() != WL_CONNECTED) { delay(500); ... } blocks
everything until the connection succeeds (or the code hangs forever if the
credentials are wrong). That's acceptable inside setup(), where
nothing else needs to run yet, but this exact pattern would be a serious
bug if placed inside loop() of a sketch that also needs to blink an LED,
read a button, or respond to sensors while WiFi status changes — Module 10
handles ongoing connection loss without blocking loop().
Handling connection failure gracefully¶
A truly production-shaped version of the connection routine should not loop forever with no way out if the WiFi credentials are simply wrong or the router is off:
// wifi-connect-with-timeout.ino
#if defined(ESP8266)
#include <ESP8266WiFi.h>
#elif defined(ESP32)
#include <WiFi.h>
#endif
const char* WIFI_SSID = "YourNetworkName";
const char* WIFI_PASSWORD = "YourNetworkPassword";
const unsigned long WIFI_TIMEOUT_MS = 15000; // give up after 15 seconds
bool connectToWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
unsigned long startAttempt = millis();
while (WiFi.status() != WL_CONNECTED) {
if (millis() - startAttempt > WIFI_TIMEOUT_MS) {
return false; // timed out
}
delay(300);
Serial.print(".");
}
return true;
}
void setup() {
Serial.begin(115200);
delay(100);
Serial.print("Connecting to WiFi");
if (connectToWiFi()) {
Serial.println();
Serial.print("Connected! IP: ");
Serial.println(WiFi.localIP());
} else {
Serial.println();
Serial.println("WiFi connection FAILED (timed out). Check credentials.");
// A real project might fall back to AP mode here (see Level 3) or
// simply retry periodically from loop() instead of hanging forever.
}
}
void loop() {}
Using millis() - startAttempt rather than counting fixed loop iterations
is the standard non-blocking-timing idiom used throughout Arduino-style
code — it correctly handles the millis() counter's eventual 32-bit
rollover (roughly every 49.7 days of continuous uptime) because unsigned
integer subtraction wraps correctly even across that rollover point,
whereas comparing raw millis() values directly would not.
Checking signal strength¶
Both cores expose WiFi.RSSI() (Received Signal Strength Indicator, in
dBm — closer to 0 is stronger, e.g. -40 is excellent, -80 is weak):
// wifi-signal-strength.ino
void setup() {
Serial.begin(115200);
// ... connect first, using either sketch above ...
}
void loop() {
if (WiFi.status() == WL_CONNECTED) {
Serial.printf("RSSI: %d dBm\n", WiFi.RSSI());
}
delay(2000);
}
How It Actually Works¶
WiFi.begin(ssid, password) kicks off the full 802.11 client (station) association sequence in hardware/firmware, not a single handshake: the radio first performs active scanning (sending probe requests on each channel and listening for probe responses, or passively listening for beacons) to find the target AP's BSSID and capability set, then sends an 802.11 Authentication frame (open-system on WPA2-PSK networks — the "authentication" that actually matters happens next), then an Association Request/Response exchange that negotiates supported rates and capabilities and assigns an Association ID. Only after association does the real security handshake run: the WPA2 4-way handshake, where the AP and station each derive a Pairwise Transient Key from the pre-shared key (password put through PBKDF2 with the SSID as salt) plus nonces exchanged in EAPOL frames, without ever transmitting the password itself over the air — this is why a correct SSID/wrong-password failure looks identical over the air up until message 2 of that handshake fails its MIC check.
Once associated, the chip still needs an IP: WiFi.begin() triggers a DHCP DORA exchange (Discover, Offer, Request, Acknowledge) as broadcast/unicast UDP frames on port 67/68, and the returned lease (IP, gateway, DNS, subnet) is what finally makes WiFi.status() == WL_CONNECTED true. Reconnection after a dropped AP isn't instant because the chip has to re-run scan+auth+associate+DHCP from scratch unless you've cached the channel/BSSID — which is why many sketches see multi-second reconnect gaps that look like "the code just isn't running."
(These examples were written and reasoned through at the register/protocol level but were not flashed to a physical board for this pass — verify timing-sensitive details against your exact chip datasheet before relying on them in production.)
Exercise¶
- Fill in your own network's SSID and password in the basic connection sketch and confirm it prints your board's assigned local IP address.
- Note that IP address — you'll need it again for the capstone at the end of this level, to open a browser to the board's web page.
- Deliberately enter a wrong password in the timeout-aware version and confirm it prints the "FAILED (timed out)" message after roughly 15 seconds rather than hanging forever.
- Add the RSSI-printing loop to a working connection and walk your board (or your router, if it's portable) to a different room; observe how the dBm value changes and note roughly what value corresponds to a noticeably weaker connection in your environment.