Wi-Fi provisioning
Hard-coding the password works exactly once, on your own network. Getting somebody else's credentials into a device with no keyboard is a five-state problem, and the state everybody forgets is what happens when they move house.
Five states, and the one everybody forgets
Do not make them type the network name
Scan, and offer a list. A typed SSID is a typo, and a typo is indistinguishable from a wrong password from the user's side. The scan also tells you the signal strength, which is worth showing — "this network is very weak here" prevents a support call later.
Which library
WiFiManager is the well-worn one and it does everything on this page. Espressif
also ship a provisioning system that works over BLE from a phone app, which is
nicer for a product and more work for a project. Either is better than writing
it yourself; the value of this page is knowing what the library is doing and
which of the five states it forgot.
The code
Try what is stored, and fall back to a setup portal when it fails. The button check at the top is the way back out - without it, a device on a changed network can only be fixed with a cable.
#include <WiFi.h>
#include <Preferences.h>
Preferences prefs;
const int RESET_BUTTON = 0; // BOOT button, held at power-on
bool tryStored() {
prefs.begin("wifi", true);
String ssid = prefs.getString("ssid", "");
String pass = prefs.getString("pass", "");
prefs.end();
if (ssid.isEmpty()) return false;
WiFi.begin(ssid.c_str(), pass.c_str());
unsigned long t = millis();
while (WiFi.status() != WL_CONNECTED && millis() - t < 20000) delay(250);
return WiFi.status() == WL_CONNECTED;
}
void setup() {
Serial.begin(115200);
pinMode(RESET_BUTTON, INPUT_PULLUP);
if (digitalRead(RESET_BUTTON) == LOW) { // held at boot = forget
prefs.begin("wifi", false);
prefs.clear();
prefs.end();
}
if (!tryStored()) {
Serial.println("starting setup portal");
// bring up the access point and the form - see Access point mode
}
}
void loop() {}Preferences stores the credentials in NVS, which survives a firmware update. That is what you want, and it is also why a factory reset has to erase it explicitly.
Same structure with a JSON file instead of NVS. Storing a scan result rather than typed text removes the most common failure, which is a mistyped network name.
import network, json, time
def load():
try:
with open('wifi.json') as f:
return json.load(f)
except OSError:
return None
def connect(cfg, timeout=20):
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(cfg['ssid'], cfg['pass'])
t = time.ticks_ms()
while not wlan.isconnected():
if time.ticks_diff(time.ticks_ms(), t) > timeout * 1000:
return False
time.sleep_ms(250)
return True
cfg = load()
if not cfg or not connect(cfg):
print('start the setup access point here')Never store the password in a file you also serve over HTTP. It is easy to end up with the setup page and the credentials on the same filesystem.
When it does not work
Show them why. Reason 15 is a bad password, 201 is a network that does not exist. Reporting failed is what generates the support email.
There is no way back to the portal. Always ship an escape - a button held at power-on that clears the stored credentials.
No captive portal. Run a DNS server on the access point that answers every name with the board's own address, or the phone leaves before anyone types anything.
They live in NVS, which is a separate partition. Erase it deliberately - do not assume flashing clears it.
The credentials are in. On a C6 there is one more thing the radio can do that changes battery life more than anything else here.
Wi-Fi 6 features →Edit this page — content/esp32/wifi-provisioning.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.