44  Wi-Fi Implementation: ESP32 Basics

In 60 Seconds

This chapter covers essential ESP32 Wi-Fi programming: Station Mode connects to existing networks using WiFi.begin() with connection status monitoring and auto-reconnect; Access Point Mode creates device-hosted hotspots using WiFi.softAP() for initial configuration (default IP 192.168.4.1, max 4 clients); Network Scanning discovers nearby APs with SSID, RSSI, channel, and security type; SmartConfig enables wireless credential provisioning without hardcoding passwords; and Raspberry Pi configuration covers both nmcli and wpa_supplicant approaches including WPA2-Enterprise. A Python MQTT client example demonstrates sensor data publishing over Wi-Fi.

Sammy the Sensor was learning to connect to Wi-Fi for the very first time, and Max the Microcontroller was his teacher!

“There are THREE ways to use Wi-Fi,” Max explained. “Station Mode is like being a student joining a classroom – you connect to an existing Wi-Fi network (the router is the teacher). Access Point Mode is like BEING the teacher – you create your own Wi-Fi network and other devices connect to you! And scanning is like peeking into every classroom to see what is available.”

“Why would I want to BE a Wi-Fi network?” asked Sammy. Max replied: “Imagine you are a brand-new smart light bulb with no screen or keyboard. How does your owner tell you the home Wi-Fi password? You create a temporary AP called ‘SmartBulb-Setup,’ the owner connects their phone to it, types the real Wi-Fi password into a web page, and then you switch to Station Mode!”

Bella the Battery added a warning: “NEVER put the Wi-Fi password directly in your code! If someone reads your code on GitHub, they get your password. Use SmartConfig or SoftAP provisioning to send credentials SEPARATELY from the firmware.”

44.1 Learning Objectives

By the end of this chapter, you will be able to:

  • Construct ESP32 Station Mode firmware that connects to existing Wi-Fi networks with timeout handling and auto-reconnect logic
  • Design Access Point configurations that create device-hosted Wi-Fi hotspots for provisioning and local control
  • Evaluate Wi-Fi scan results by interpreting RSSI, channel overlap, and security protocol data to select optimal networks
  • Diagnose connection failures using Wi-Fi status codes and implement exponential-backoff reconnection strategies
  • Contrast provisioning methods (SmartConfig, SoftAP captive portal, BLE) and justify which approach fits a given deployment scenario

What is this chapter? Practical ESP32 Wi-Fi programming including station mode, access point mode, and network scanning.

When to use:

  • When building your first Wi-Fi-connected IoT device
  • When you need to configure Wi-Fi on ESP32/ESP8266
  • Before implementing more complex Wi-Fi applications

Key Topics:

Topic Application
Station Mode Connect device to existing network
AP Mode Create device hotspot
Network Scanning Discover available networks
SmartConfig Provision credentials wirelessly

Prerequisites:

  • Wi-Fi fundamentals knowledge
  • Basic C/C++ programming
  • Understanding of TCP/IP basics

44.2 Prerequisites

Before diving into this chapter, you should be familiar with:

Key Concepts

  • ESP32 Wi-Fi Architecture: Dual-core MCU with integrated 802.11b/g/n radio, TCP/IP stack, TLS library, and MQTT client
  • Station Mode (STA): ESP32 connects to an existing Wi-Fi network as a client device; the standard mode for IoT sensors
  • Access Point Mode (AP): ESP32 creates its own Wi-Fi hotspot; used for provisioning or direct device-to-device communication
  • STA+AP Mode: Simultaneous station and access point; ESP32 connects to router while hosting a configuration portal
  • ESP-IDF Wi-Fi API: Event-based Wi-Fi management API; esp_wifi_init(), esp_wifi_start(), WiFiEventGroup for synchronization
  • DHCP Client: Automatic IP address assignment from router; alternatively use static IP for faster connection and deterministic addressing
  • Wi-Fi Security: WPA2-PSK (TKIP/CCMP) and WPA3-SAE; never use open or WEP networks for production IoT devices
  • NVS (Non-Volatile Storage): Flash-based key-value store for persisting Wi-Fi credentials and device configuration across reboots

44.3 Hands-on: ESP32 Wi-Fi Configuration

Time: ~25 min | Level: Advanced | Code: P08.C36.U01

44.3.1 Basic Wi-Fi Connection

// ESP32 Wi-Fi Station Mode - Basic Connection
#include <Wi-Fi.h>

// Wi-Fi credentials
const char* ssid = "YourNetworkSSID";
const char* password = "YourPassword";

void setup() {
  Serial.begin(115200);
  delay(1000);

  // Connect to Wi-Fi
  Serial.println("Connecting to Wi-Fi...");
  Wi-Fi.mode(WIFI_STA);  // Station mode
  Wi-Fi.begin(ssid, password);

  // Wait for connection
  int attempts = 0;
  while (Wi-Fi.status() != WL_CONNECTED && attempts < 20) {
    delay(500);
    Serial.print(".");
    attempts++;
  }

  if (Wi-Fi.status() == WL_CONNECTED) {
    Serial.println("\nWi-Fi connected!");
    Serial.print("IP address: ");
    Serial.println(Wi-Fi.localIP());
    Serial.print("MAC address: ");
    Serial.println(Wi-Fi.macAddress());
    Serial.print("Signal strength (RSSI): ");
    Serial.print(Wi-Fi.RSSI());
    Serial.println(" dBm");
  } else {
    Serial.println("\nFailed to connect to Wi-Fi");
  }
}

void loop() {
  // Check Wi-Fi connection status
  if (Wi-Fi.status() != WL_CONNECTED) {
    Serial.println("Wi-Fi disconnected! Reconnecting...");
    Wi-Fi.reconnect();
  }
  delay(10000);
}
Security Note

These examples inline SSID/password for clarity. For production IoT, avoid hardcoding credentials: use a provisioning flow (BLE/SoftAP/SmartConfig as appropriate) and store credentials in NVS protected by flash encryption/secure boot where supported. See Wi-Fi Security and Provisioning.

RSSI signal quality interpretation:

Convert RSSI (dBm) to quality percentage for UI visualization:

\[\text{Quality} = \begin{cases} 100\% & \text{RSSI} \geq -50\ \text{dBm} \\ 2 \times (\text{RSSI} + 100) & -100 \leq \text{RSSI} < -50 \\ 0\% & \text{RSSI} < -100\ \text{dBm} \end{cases}\]

Examples:

  • RSSI = -45 dBm: \(Q = 100\%\) (Excellent)
  • RSSI = -60 dBm: \(Q = 2 \times (-60 + 100) = 80\%\) (Good)
  • RSSI = -75 dBm: \(Q = 2 \times (-75 + 100) = 50\%\) (Fair)
  • RSSI = -90 dBm: \(Q = 2 \times (-90 + 100) = 20\%\) (Poor)
  • RSSI = -105 dBm: \(Q = 0\%\) (Critical)
Try It: RSSI Signal Quality Explorer

Distance estimation (2.4 GHz, free space):

Using FSPL formula: \[d = 10^{\frac{\text{FSPL} + 147.55 - 20\log_{10}(f)}{20}}\]

For \(f = 2400\) MHz, TX power +17 dBm, RX antenna gain 2 dBi:

  • RSSI -60 dBm: \(\text{FSPL} = 17 + 2 - (-60) = 79\ \text{dB}\)\(d \approx 15\ \text{m}\)
  • RSSI -75 dBm: \(\text{FSPL} = 94\ \text{dB}\)\(d \approx 50\ \text{m}\)
Interactive Simulator: ESP32 Wi-Fi Connection

Try it yourself! See how ESP32 connects to Wi-Fi networks and retrieves network information.

What This Simulates: An ESP32 connecting to a Wi-Fi network in Station (STA) mode, displaying connection status, IP address, and signal strength.

How to Use:

  1. Click Start Simulation
  2. Watch the Serial Monitor show connection progress
  3. Observe the Wi-Fi connection status LEDs
  4. See the IP address assignment (DHCP)
  5. Monitor signal strength (RSSI) readings
  6. Try modifying credentials to see connection failures

Learning Points

Observe:

  • Wi-Fi.mode(WIFI_STA): Sets ESP32 as Wi-Fi client (station mode)
  • Wi-Fi.begin(): Initiates connection to access point
  • Wi-Fi.status(): Returns connection state (WL_CONNECTED, WL_DISCONNECTED, etc.)
  • Wi-Fi.localIP(): Gets assigned IP address from DHCP server
  • Wi-Fi.macAddress(): Unique hardware identifier for the device
  • Wi-Fi.RSSI(): Received Signal Strength Indicator in dBm (-30 = excellent, -90 = weak)

Connection Process:

1. Scan for SSID
2. Associate with Access Point
3. Authenticate (WPA2/WPA3, depending on network)
4. DHCP request - Receive IP address
5. Connected! (WL_CONNECTED status)

RSSI Signal Strength Guide:

> -30 dBm : Excellent (very close to AP)
-30 to -67 dBm : Very Good
-67 to -70 dBm : Good
-70 to -80 dBm : Fair (usable but not ideal)
-80 to -90 dBm : Weak (connection drops likely)
< -90 dBm : Extremely weak (unusable)

Real-World Applications:

  • Smart Home Devices: Thermostats, lights, plugs connecting to home network
  • Sensor Networks: Environmental monitors sending data to local server
  • Industrial IoT: Factory sensors reporting to edge gateway
  • Building Automation: HVAC, lighting, security systems

Experiment:

  • Add retry logic with exponential backoff
  • Implement connection timeout handling
  • Create Wi-Fi signal strength monitor with alerts
  • Add multiple SSID fallback (try home Wi-Fi, then hotspot)
  • Implement provisioning and store Wi-Fi credentials in NVS (with encryption where supported)

44.3.2 Wi-Fi with Auto-Reconnect

// ESP32 Wi-Fi with Automatic Reconnection
#include <Wi-Fi.h>

const char* ssid = "YourSSID";
const char* password = "YourPassword";

void connectWiFi() {
  if (Wi-Fi.status() != WL_CONNECTED) {
    Serial.print("Connecting to Wi-Fi");
    Wi-Fi.begin(ssid, password);

    int timeout = 0;
    while (Wi-Fi.status() != WL_CONNECTED && timeout < 30) {
      delay(500);
      Serial.print(".");
      timeout++;
    }

    if (Wi-Fi.status() == WL_CONNECTED) {
      Serial.println("\nConnected!");
      Serial.println("IP: " + Wi-Fi.localIP().toString());
    } else {
      Serial.println("\nConnection failed!");
    }
  }
}

void setup() {
  Serial.begin(115200);

  // Configure Wi-Fi for low power
  Wi-Fi.mode(WIFI_STA);
  Wi-Fi.setAutoReconnect(true);
  Wi-Fi.persistent(false);  // Don't save Wi-Fi config to flash

  connectWiFi();
}

void loop() {
  // Your IoT logic here

  // Periodic connection check
  static unsigned long lastCheckMs = 0;
  if (millis() - lastCheckMs >= 30000) {  // Every 30 seconds
    lastCheckMs = millis();
    if (Wi-Fi.status() != WL_CONNECTED) {
      connectWiFi();
    }
  }
}
Try It: Wi-Fi Reconnection Timing Simulator

44.3.3 Wi-Fi Scanning (Find Available Networks)

// ESP32 Wi-Fi Network Scanner
#include <Wi-Fi.h>

void setup() {
  Serial.begin(115200);
  Wi-Fi.mode(WIFI_STA);
  Wi-Fi.disconnect();
  delay(100);

  Serial.println("Scanning Wi-Fi networks...");
}

void loop() {
  int n = Wi-Fi.scanNetworks();

  Serial.println("\n=== Wi-Fi Scan Results ===");
  if (n == 0) {
    Serial.println("No networks found");
  } else {
    Serial.printf("Found %d networks:\n", n);

    for (int i = 0; i < n; i++) {
      Serial.printf("%2d: %-32s ", i + 1, Wi-Fi.SSID(i).c_str());
      Serial.printf("(%3d dBm) ", Wi-Fi.RSSI(i));
      Serial.printf("CH:%2d ", Wi-Fi.channel(i));

      // Security type
      switch (Wi-Fi.encryptionType(i)) {
        case WIFI_AUTH_OPEN:
          Serial.print("[OPEN]");
          break;
        case WIFI_AUTH_WEP:
          Serial.print("[WEP]");
          break;
        case WIFI_AUTH_WPA_PSK:
          Serial.print("[WPA]");
          break;
        case WIFI_AUTH_WPA2_PSK:
          Serial.print("[WPA2]");
          break;
        case WIFI_AUTH_WPA_WPA2_PSK:
          Serial.print("[WPA/WPA2]");
          break;
#ifdef WIFI_AUTH_WPA2_WPA3_PSK
        case WIFI_AUTH_WPA2_WPA3_PSK:
          Serial.print("[WPA2/WPA3]");
          break;
#endif
#ifdef WIFI_AUTH_WPA3_PSK
        case WIFI_AUTH_WPA3_PSK:
          Serial.print("[WPA3]");
          break;
#endif
        case WIFI_AUTH_WPA2_ENTERPRISE:
          Serial.print("[WPA2-ENT]");
          break;
        default:
          Serial.print("[UNKNOWN]");
      }
      Serial.println();
    }
  }

  delay(10000);  // Scan every 10 seconds
}
Try It: Wi-Fi Network Scanner Simulator
Interactive Simulator: Wi-Fi Network Scanner

Try it yourself! See how ESP32 scans and discovers available Wi-Fi networks.

What This Simulates: An ESP32 performing Wi-Fi network scans to discover nearby access points, displaying SSID, signal strength, channel, and security type.

How to Use:

  1. Click Start Simulation
  2. Watch the Serial Monitor show discovered networks
  3. Observe network details: SSID, RSSI (dBm), channel, security
  4. See how scan results update periodically
  5. Notice different security protocols (OPEN, WPA2, etc.)

Learning Points

Observe:

  • Wi-Fi.scanNetworks(): Performs active scan, returns number of networks found
  • Wi-Fi.SSID(i): Network name (Service Set Identifier)
  • Wi-Fi.RSSI(i): Signal strength in dBm (closer to 0 = stronger)
  • Wi-Fi.channel(i): Operating channel (2.4 GHz: 1-11 US, 1-13 EU; 5 GHz varies by region)
  • Wi-Fi.encryptionType(i): Security protocol (OPEN, WEP, WPA, WPA2, WPA3)

Wi-Fi Channels (2.4 GHz):

Channels 1, 6, 11: Non-overlapping (recommended)
Channels 2-5, 7-10, 12-13: Overlapping (interference)

Channel Selection Impact:
- Overlapping channels = slower speeds, more interference
- Proper channel selection crucial for IoT device placement

Security Protocol Ranking:

OPEN: No encryption (never use for IoT!)
WEP: Broken encryption (obsolete)
WPA: Deprecated (TKIP weakness)
WPA2-PSK: Good (AES-CCMP encryption)
WPA2-Enterprise: Better (802.1X authentication)
WPA3: Best (SAE, forward secrecy)

Real-World Applications:

  • Smart Wi-Fi Configuration: Device discovers networks, presents list to user
  • Network Selection: Auto-connect to strongest known network
  • Site Survey: Analyze RF environment for optimal AP placement
  • Roaming: Switch between APs based on signal strength
  • Captive Portal Detection: Identify networks requiring web authentication

Experiment:

  • Implement “auto-connect to strongest known network” logic
  • Create Wi-Fi signal strength heat map
  • Add channel conflict detection (warn if multiple APs on same channel)
  • Filter networks by minimum RSSI threshold (-70 dBm)
  • Implement preferred network list with priorities

44.3.4 ESP32 as Access Point (Soft AP)

// ESP32 as Wi-Fi Access Point
#include <Wi-Fi.h>

const char* ap_ssid = "ESP32-IoT-AP";
const char* ap_password = "12345678";  // Min 8 characters

void setup() {
  Serial.begin(115200);

  // Configure Access Point
  Wi-Fi.softAP(ap_ssid, ap_password);

  IPAddress IP = Wi-Fi.softAPIP();
  Serial.println("Access Point Started");
  Serial.print("AP IP address: ");
  Serial.println(IP);
  Serial.print("AP SSID: ");
  Serial.println(ap_ssid);

  // Optional: Configure AP with more parameters
  // Wi-Fi.softAP(ssid, password, channel, hidden, max_connections)
  // Wi-Fi.softAP(ap_ssid, ap_password, 6, false, 4);
}

void loop() {
  // Check connected clients
  Serial.printf("Connected stations: %d\n", Wi-Fi.softAPgetStationNum());
  delay(5000);
}
Try It: SoftAP Configuration Explorer
Interactive Simulator: ESP32 Access Point Mode

Try it yourself! See how ESP32 creates its own Wi-Fi network that other devices can connect to.

What This Simulates: An ESP32 running as a Wi-Fi Access Point (Soft AP), allowing other devices to connect and displaying the number of connected clients.

How to Use:

  1. Click Start Simulation
  2. Watch the Serial Monitor show AP configuration
  3. See the AP IP address (typically 192.168.4.1)
  4. Observe connected station count updates
  5. Notice the ESP32 becomes a Wi-Fi router!

Learning Points

Observe:

  • Wi-Fi.softAP(): Creates a software-based access point
  • Default IP: 192.168.4.1 (ESP32 acts as DHCP server for clients)
  • Wi-Fi.softAPgetStationNum(): Returns number of connected devices
  • No Internet: Soft AP provides local network only (unless ESP32 also connects to WAN)
  • Dual Mode Possible: ESP32 can be both AP and Station simultaneously

Access Point Parameters:

Wi-Fi.softAP(ssid, password, channel, hidden, max_connections)

ssid: Network name (max 32 characters)
password: WPA2 password (8-64 characters, or empty for open network)
channel: Wi-Fi channel (2.4 GHz: 1-11 US, 1-13 EU; default: 1)
hidden: Hide SSID from scans (default: false)
max_connections: Maximum clients (1-4, default: 4)

Soft AP Configuration:

SoftAP topology showing an ESP32 access point at 192.168.4.1 assigning DHCP addresses to phone, laptop, and tablet clients on the 192.168.4.0/24 network.
Figure 44.1: ESP32 SoftAP mode for local configuration with DHCP client assignment
Diagram illustrating Wifi State Machine
Figure 44.2: Wi-Fi connection lifecycle showing state transitions from power-on through scanning, authentication, DHCP, and connected states with error recovery paths.

Real-World Applications:

  • IoT Device Configuration: ESP32 creates temporary AP for initial setup
    • Smart bulbs, plugs use this for Wi-Fi provisioning
    • User connects to “ESP32-Setup”, configures home Wi-Fi via web page
  • Local Control: Smart home devices create AP for direct control without router
  • Mesh Networks: Multiple ESP32s as repeaters extending coverage
  • Captive Portal: ESP32 AP redirects to config page (hotels, cafes)
  • Data Collection Points: ESP32 AP collects sensor data from nearby devices
  • Educational: Teaching networking without needing external router

Experiment:

  • Create captive portal with web server on 192.168.4.1
  • Implement STA+AP mode: connect to home Wi-Fi AND create AP
  • Add web-based Wi-Fi credential configuration
  • Create mesh network with multiple ESP32s
  • Use a device-specific setup password; optionally add MAC filtering as defense-in-depth (not primary security)

44.3.5 Wi-Fi Provisioning (SmartConfig)

// ESP32 Wi-Fi Provisioning with SmartConfig
#include <Wi-Fi.h>

void setup() {
  Serial.begin(115200);
  Wi-Fi.mode(WIFI_STA);

  // Start SmartConfig
  Serial.println("Starting SmartConfig...");
  Serial.println("Use a SmartConfig-compatible app to provide Wi-Fi credentials");

  Wi-Fi.beginSmartConfig();

  // Wait for SmartConfig completion
  while (!Wi-Fi.smartConfigDone()) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("\nSmartConfig received!");
  Serial.println("Connecting to Wi-Fi...");

  // Wait for Wi-Fi connection
  while (Wi-Fi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("\nWi-Fi Connected!");
  Serial.print("IP Address: ");
  Serial.println(Wi-Fi.localIP());

  // Save credentials (optional)
  Serial.println("SSID: " + Wi-Fi.SSID());
}

void loop() {
  // Your application code
}

44.4 Raspberry Pi Wi-Fi Configuration

For a quick overview, see Wi-Fi Fundamentals and Standards. Below are CLI-first options commonly used in IoT deployments.

44.4.1 Option A: NetworkManager (nmcli)

sudo nmcli dev wifi list
sudo nmcli dev wifi connect "<SSID>" password "<PASSWORD>" ifname wlan0
ip -br a show wlan0
ping -c 3 1.1.1.1

44.4.2 Option B: wpa_supplicant (legacy / minimal images)

Edit /etc/wpa_supplicant/wpa_supplicant.conf:

country=US
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
  ssid="YourNetworkSSID"
  psk="YourPassword"
}

WPA2-Enterprise (example; validate server certificates):

network={
  ssid="Enterprise-Wi-Fi"
  key_mgmt=WPA-EAP
  eap=PEAP
  identity="username"
  password="password"
  ca_cert="/etc/ssl/certs/your_ca.pem"
  phase2="auth=MSCHAPV2"
}

Apply and verify:

sudo wpa_cli -i wlan0 reconfigure
sudo systemctl restart dhcpcd || true
ip -br a show wlan0
iw dev wlan0 link

44.4.3 Python Wi-Fi Client (MQTT over Wi-Fi)

# Python Wi-Fi-enabled MQTT IoT Sensor
import paho.mqtt.client as mqtt
import time
import random

# MQTT Configuration
MQTT_BROKER = "192.168.1.100"  # Local broker or cloud
MQTT_PORT = 1883
MQTT_TOPIC = "home/sensors/temperature"

# Callback signature for paho-mqtt 2.0+
def on_connect(client, userdata, flags, reason_code, properties):
    if reason_code == 0:
        print("Connected to MQTT broker via Wi-Fi")
    else:
        print(f"Connection failed with reason code {reason_code}")

def on_publish(client, userdata, mid):
    print(f"Message {mid} published")

# Create MQTT client (paho-mqtt 2.0+ API)
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="RaspberryPi-Sensor")
client.on_connect = on_connect
client.on_publish = on_publish

# Connect to broker
try:
    client.connect(MQTT_BROKER, MQTT_PORT, 60)
    client.loop_start()

    while True:
        # Simulate sensor reading
        temperature = round(20 + random.uniform(-5, 5), 2)

        # Publish to MQTT
        result = client.publish(MQTT_TOPIC, str(temperature))
        print(f"Published temperature: {temperature}C (QoS: {result.rc})")

        time.sleep(10)  # Publish every 10 seconds

except KeyboardInterrupt:
    print("\nDisconnecting...")
    client.loop_stop()
    client.disconnect()

Common Mistake: Hardcoding Wi-Fi Credentials in Production Firmware

The Mistake: A company ships 10,000 smart light bulbs with Wi-Fi credentials hardcoded in the firmware:

const char* ssid = "CompanyTestNetwork";
const char* password = "DevTeam2024!";

Six months later, the source code leaks on GitHub during a repository cleanup. Now every customer’s home Wi-Fi password is exposed if they connected their devices to the same network name.

Why This Is Catastrophic:

  1. Single Password for All Customers: Every device shipped uses identical credentials burned into firmware
  2. Password Extraction: Firmware can be dumped from any device using SPI flash readers or UART debugging
  3. Reverse Engineering: Compiled binaries can be analyzed to extract string constants
  4. Code Leaks: GitHub, GitLab, or internal repository accidents expose credentials permanently
  5. No Update Path: Changing hardcoded credentials requires firmware update to all 10,000 devices

Real-World Attack Scenario:

  1. Attacker buys one smart bulb ($20) and extracts firmware using ESP32 flash dumper
  2. String search finds const char* ssid = "SmartHome-2024" and password
  3. Attacker now has credentials for thousands of homes that used default network name
  4. Wardriving campaign identifies homes with the compromised SSID
  5. Attacker gains network access to steal data, launch attacks, pivot to other devices

The Correct Approach: Never Hardcode Credentials

44.4.4 Method 1: SmartConfig (ESP Touch)

Device creates temporary AP, user sends credentials via smartphone app:

#include <WiFi.h>

void setup() {
  Serial.begin(115200);

  // No hardcoded credentials
  WiFi.mode(WIFI_STA);
  WiFi.beginSmartConfig();

  Serial.println("Waiting for SmartConfig...");
  Serial.println("1. Download ESP Touch app on smartphone");
  Serial.println("2. Connect phone to your home Wi-Fi");
  Serial.println("3. Open app and enter Wi-Fi password");
  Serial.println("4. Device receives credentials securely");

  while (!WiFi.smartConfigDone()) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("\nSmartConfig complete!");
  Serial.println("Credentials saved to NVS (encrypted storage)");

  // Credentials persist across reboots in NVS
  WiFi.begin();  // Uses stored credentials

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  Serial.print("Connected to: ");
  Serial.println(WiFi.SSID());
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}

Advantages:

  • Zero credentials in source code
  • Works with any Wi-Fi network
  • Stored in NVS (Non-Volatile Storage) - persists across power cycles
  • Each device gets unique network credentials

44.4.5 Method 2: SoftAP Provisioning (Captive Portal)

Device creates temporary hotspot, user configures via web interface. On first boot, ESP32 creates a SmartBulb-Setup AP with a captive portal. Users enter their Wi-Fi credentials through a web form, which are saved to NVS flash for future reconnection.

#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>

WebServer server(80);
Preferences prefs;

const char* ap_ssid = "SmartBulb-Setup";  // This is safe - temporary AP name

void handleRoot() {
  String html = R"(
    <!DOCTYPE html>
    <html>
    <head><title>Wi-Fi Setup</title></head>
    <body>
      <h1>Smart Bulb Configuration</h1>
      <form method='POST' action='/save'>
        <label>Your Wi-Fi Network:</label><br>
        <input type='text' name='ssid' required><br><br>
        <label>Wi-Fi Password:</label><br>
        <input type='password' name='password' required><br><br>
        <input type='submit' value='Connect'>
      </form>
    </body>
    </html>
  )";
  server.send(200, "text/html", html);
}

void handleSave() {
  String ssid = server.arg("ssid");
  String password = server.arg("password");

  // Save to encrypted NVS (NOT to source code)
  prefs.begin("wifi", false);
  prefs.putString("ssid", ssid);
  prefs.putString("password", password);
  prefs.end();

  server.send(200, "text/html", "<h1>Saved! Rebooting...</h1>");
  delay(2000);
  ESP.restart();
}

void setup() {
  Serial.begin(115200);
  prefs.begin("wifi", true);  // Read-only

  String saved_ssid = prefs.getString("ssid", "");

  if (saved_ssid == "") {
    // First boot - start provisioning AP
    WiFi.softAP(ap_ssid);
    Serial.println("Provisioning mode - Connect to 'SmartBulb-Setup'");
    Serial.print("Open http://");
    Serial.println(WiFi.softAPIP());

    server.on("/", handleRoot);
    server.on("/save", HTTP_POST, handleSave);
    server.begin();

    while (true) {
      server.handleClient();
    }
  } else {
    // Normal operation - connect with stored credentials
    WiFi.begin(saved_ssid.c_str(), prefs.getString("password", "").c_str());
    prefs.end();

    int timeout = 0;
    while (WiFi.status() != WL_CONNECTED && timeout < 20) {
      delay(500);
      timeout++;
    }

    if (WiFi.status() == WL_CONNECTED) {
      Serial.println("Connected!");
    } else {
      Serial.println("Failed - restarting provisioning");
      prefs.begin("wifi", false);
      prefs.clear();  // Clear bad credentials
      prefs.end();
      ESP.restart();
    }
  }
}

Provisioning Flow:

  1. First Boot: Device creates SmartBulb-Setup Wi-Fi hotspot (no password for setup)
  2. User Connection: Customer connects phone to SmartBulb-Setup (IP: 192.168.4.1)
  3. Web Configuration: Opens http://192.168.4.1, enters home Wi-Fi credentials
  4. Credential Storage: ESP32 saves to encrypted NVS (never in source code)
  5. Normal Operation: Device reboots, connects to home network using stored credentials

44.4.6 Method 3: BLE Provisioning (ESP-IDF)

For devices without web interface, use Bluetooth Low Energy:

#include <WiFiProv.h>

const char* service_name = "PROV_DEVICE";
const char* pop = "abcd1234";  // Proof of possession (shown on device)

void setup() {
  WiFiProv.beginProvision(WIFI_PROV_SCHEME_BLE, WIFI_PROV_SCHEME_HANDLER_FREE_BTDM,
                           WIFI_PROV_SECURITY_1, pop, service_name);

  Serial.println("BLE provisioning started");
  Serial.println("1. Download ESP BLE Provisioning app");
  Serial.println("2. Scan for 'PROV_DEVICE'");
  Serial.println("3. Enter POP code: abcd1234");
  Serial.println("4. Select your Wi-Fi network and enter password");
}

Security Best Practices:

Practice Why It Matters
Never Hardcode Credentials Source code leaks expose all devices
Use NVS Encryption Flash encryption prevents credential extraction
Rotate Provisioning Passwords Unique POP codes per device (print on label)
Timeout Provisioning Mode Auto-exit after 10 minutes to prevent rogue provisioning
HTTPS for Web Provisioning Self-signed certs better than plain HTTP
Secure Boot Prevents unsigned firmware from reading NVS

Production Deployment Checklist:

Key Insight: Hardcoded credentials are the #1 IoT security vulnerability. The solution is provisioning - every device gets unique credentials from its owner during setup, never from the factory. This eliminates the “one firmware dump compromises all devices” attack vector. The provisioning flow adds 2-3 minutes to customer setup time but provides permanent security. For 10,000-device deployments, this is mandatory, not optional.

Goal: Configure Wi-Fi credentials on an ESP32 without hardcoding them in firmware.

Materials Needed:

  • ESP32 development board
  • Smartphone with ESP Touch app (Android/iOS)
  • Arduino IDE with ESP32 board support

Exercise Steps:

  1. Upload SmartConfig Sketch - Load the SmartConfig example from Arduino IDE (File → Examples → WiFi → WiFiSmartConfig) and upload to ESP32.

  2. Enter SmartConfig Mode - Reset the ESP32. The serial monitor should show “Waiting for SmartConfig” (blue LED may blink).

  3. Install ESP Touch App - Download “ESP Touch” or “EspTouch” from your phone’s app store (official Espressif app).

  4. Send Credentials - Open the app, enter your Wi-Fi SSID and password, tap “Confirm”. The app broadcasts encrypted UDP packets containing your credentials.

  5. Observe Connection - The ESP32 should connect within 30-60 seconds. Serial monitor shows “WiFi Connected!” and the device’s assigned IP address.

  6. Verify Persistence - Reset the ESP32. It should reconnect automatically using stored credentials from NVS flash.

Expected Observations:

  • First boot: Enters SmartConfig mode (credentials not found in NVS)
  • After provisioning: Credentials saved to flash, automatic reconnection on reboot
  • Factory reset: Clears NVS, returns to SmartConfig mode

What to Explore:

  • How does the app encode credentials in UDP packets?
  • What happens if you provision with the wrong password?
  • Can you provision multiple ESP32s simultaneously?

Extension: Implement a “factory reset” button that clears NVS credentials when pressed during boot.

Concept Relates To Why It Matters
Station (STA) Mode Client mode, Connects to existing AP Standard mode for IoT devices joining home/office networks
Access Point (AP) Mode Server mode, Creates own network Enables device configuration and local control without router
SmartConfig Provisioning Wireless configuration, Eliminates hardcoded credentials Securely transfers Wi-Fi credentials from smartphone to device
Network Scanning Site surveys, SSID discovery, Channel analysis Identifies available networks and signal strength
NVS (Non-Volatile Storage) Credential persistence, Flash storage Remembers Wi-Fi credentials across reboots

44.5 See Also

Common Pitfalls

After Wi-Fi connection, the ESP32 receives an IP address via DHCP which takes 100-500 ms. Attempting to make network requests immediately after the “connected” event (before IP assignment) fails silently. Always wait for the WIFI_EVENT_STA_GOT_IP event before starting network communication.

Arduino’s WiFi.begin() pattern blocks the main loop and ignores events. In ESP-IDF and advanced Arduino development, use the event-driven pattern with proper event groups. Blocking patterns prevent handling other tasks during connection and make reconnection logic fragile.

Many ESP32 examples never disconnect. Production devices must handle WIFI_EVENT_STA_DISCONNECTED gracefully — pausing network operations, triggering reconnection with backoff, and buffering sensor data during disconnection. Missing disconnection handling causes the device to hang silently after any Wi-Fi interruption.

Writing Wi-Fi credentials or connection state to NVS on every connect/disconnect cycle causes excessive flash wear. NVS uses wear leveling but has finite write cycles (~100,000 per page). Write credentials once during provisioning; use RAM variables for transient state during normal operation.

44.6 Summary

This chapter covered the fundamentals of ESP32 Wi-Fi implementation:

  • Station Mode Configuration: Connecting ESP32 to existing Wi-Fi networks with connection handling, timeout management, and auto-reconnect logic
  • Access Point Mode: Creating ESP32-hosted Wi-Fi networks for device configuration, local control, and mesh networking
  • Network Scanning: Discovering available networks with SSID, RSSI, channel, and security information for network selection and site surveys
  • SmartConfig Provisioning: Wirelessly configuring Wi-Fi credentials without hardcoding them in firmware
  • Raspberry Pi Configuration: Using nmcli and wpa_supplicant for Linux-based IoT Wi-Fi setup

44.7 Knowledge Check

Place these steps in the correct order for provisioning an ESP32 IoT device using SoftAP with a captive portal.

44.8 What’s Next

If you want to… Read this
Implement HTTP and WebSocket on ESP32 Wi-Fi HTTP & WebSocket
Optimize Wi-Fi power on ESP32 Wi-Fi Power Optimization
Work through comprehensive lab exercises Wi-Fi Comprehensive Lab
Secure your Wi-Fi IoT device Wi-Fi Security and Provisioning
Learn all Wi-Fi IoT implementations Wi-Fi for IoT: Implementations