ESP32 Wi-Fi First Steps: STA/AP Modes, Connection Management and HTTP
Milestone: at this point you command GPIO, ADC, PWM and the buses for local peripherals. Bring Wi-Fi up and the ESP32 graduates from “microcontroller” to “IoT device” — data reaches the cloud, phones reach the device.
1. The three operating modes
| Mode | Role | Use case |
|---|---|---|
| STA (Station) | Client that joins a router | Devices fetching/reporting data |
| AP (Access Point) | Emits its own hotspot | Config portal without a router (phone connects to the device) |
| AP+STA | Both at once | The classic provisioning architecture: AP collects config → STA joins the router |
WiFi.mode(WIFI_STA); // client mode (most common)
WiFi.mode(WIFI_AP); // hotspot mode
WiFi.mode(WIFI_AP_STA); // dual mode
The classic ESP32 is 2.4GHz-only — not finding your 5GHz network is normal, not a fault.
2. Connecting: blocking vs event-driven
1. Blocking version (fine for prototypes)
#include <WiFi.h>
void setup() {
Serial.begin(115200);
WiFi.begin("YOUR_SSID", "YOUR_PASSWORD");
while (WiFi.status() != WL_CONNECTED) {
delay(300);
Serial.print(".");
}
Serial.printf("\nConnected! IP: %s\n", WiFi.localIP().toString().c_str());
}
Problem: if it can’t connect, it waits forever (remember the watchdog from timers?).
2. Event-driven version (production posture)
WiFiEventHandler onGotIP, onDisconnect;
void setupWiFi() {
WiFi.mode(WIFI_STA);
WiFi.setAutoReconnect(true); // automatic reconnection
WiFi.persistent(false); // don't wear the flash with credentials
onGotIP = WiFi.onStationModeGotIP([](const WiFiEventStationModeGotIP &e) {
Serial.printf("Connected, IP: %s\n", e.ip.toString().c_str());
});
onDisconnect = WiFi.onStationModeDisconnected([](const WiFiEventStationModeDisconnected &e) {
Serial.println("Disconnected, auto-reconnecting...");
});
WiFi.begin("YOUR_SSID", "YOUR_PASSWORD");
}
Key point: never call
WiFi.begin()repeatedly in loop — every call restarts the radio and makes things worse. begin once + auto-reconnect + event callbacks is the standard architecture.
3. HTTP: GET and POST
#include <WiFi.h>
#include <HTTPClient.h>
void fetchWeather() {
HTTPClient http;
http.begin("http://api.example.com/weather?city=shenzhen");
int code = http.GET();
if (code == 200) {
String payload = http.getString();
Serial.println(payload);
}
http.end(); // release the connection (mandatory!)
}
void postData() {
HTTPClient http;
http.begin("http://api.example.com/log");
http.addHeader("Content-Type", "application/json");
int code = http.POST("{\"temp\":25.5}");
Serial.printf("POST status: %d\n", code);
http.end();
}
HTTPS: just switch the URL to
https://(root certificates are built in; some self-signed services need config). Production code should be HTTPS-only.
4. ArduinoJson: parsing responses
#include <ArduinoJson.h>
void parseWeather(String json) {
JsonDocument doc; // v7 auto-capacity
DeserializationError err = deserializeJson(doc, json);
if (err) { Serial.println("JSON parse failed"); return; }
const char* city = doc["city"];
float temp = doc["temperature"];
int humidity = doc["humidity"];
Serial.printf("%s: %.1f°C, humidity %d%%\n", city, temp, humidity);
}
v6 and v7 differ (
StaticJsonDocument/DynamicJsonDocumentvsJsonDocument) — “not declared” errors mean you hit the version watershed, same story as LEDC.
5. Project: the full temperature reporting loop
Wiring together everything from this column (an L2 acceptance project):
// Architecture: timer(collect) → I2C(read AHT20) → Wi-Fi(HTTP report) → non-blocking loop
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
uint32_t tReport = 0;
void loop() {
if (WiFi.status() == WL_CONNECTED && millis() - tReport > 60000) {
tReport = millis();
float temp = readAht20Temp(); // from the buses guide
JsonDocument doc;
doc["temp"] = temp;
String body;
serializeJson(doc, body);
HTTPClient http;
http.begin("http://api.example.com/report");
http.addHeader("Content-Type", "application/json");
http.POST(body);
http.end();
}
}
6. Pitfall cheat sheet (bookmark this)
| Symptom | Cause and fix |
|---|---|
| Can’t see the 5GHz network | 2.4GHz-only — normal |
| 15-second connect timeout | 5GHz SSID / non-ASCII SSID / wrong password; verify with a phone hotspot |
| Repeated begin makes it worse | begin once; leave reconnecting to auto-reconnect |
| HTTP returns -1 | Requested while offline; or DNS failure — check WiFi.status() |
Forgot http.end() | Connection leak — later requests fail |
| Random Brownout reboots | Radio transients ~450mA — power traps in the Chip Selection Guide |
| JSON “out of memory” | v6 manual capacity too small; upgrade to v7 or enlarge |
| 30s stall on boot | Stale NVS config + blocking wait; go event-driven |
7. Further reading
- ⏭️ Next: WebServer & mDNS — control the ESP32 from a phone browser
- 🧭 Foundation: Timers — non-blocking periodic reporting
- 📚 Going deeper: MQTT (L3), ESP-NOW (L3), deep-sleep low-power reporting (L3)
License: CC BY-SA 4.0. Repost & remix with attribution: ESPDIY Maker Lab - espdiy.com