Waarom hard gecodeerde WiFi-gegevens een slecht idee zijn, en hoe je dat oplost — met WiFiManager, of volledig zelf geschreven.
De ESP8266 / ESP32 is eigenlijk een programmeerbare schakelaar van GPIO-pinnen, met WiFi en Bluetooth / BLE. WiFi wordt vrijwel altijd gebruikt bij de ESP: de meeste programmeerbare bordjes zonder WiFi zijn veel zuiniger qua energie dan de ESP. We kunnen dus stellen dat we de ESP steeds gebruiken voor WiFi.
Hoe programmeren we best de verbinding dan?
De login-gegevens hard gecodeerd in de sketch? Dat is een slecht idee. Een afzonderlijk bestand "credentials.h" met SSID en paswoord bij de sketch zetten? Een professionele toepassing ontwikkelen op die manier kan evenmin. Een klant zal een sketch niet opnieuw compileren, die wil dat zijn IOT gadget gewoon werkt, na een eenmalige setup.
Eigenlijk moet de ESP bij opstart proberen te verbinden met WiFi, met de login-gegevens vanuit het EEPROM. Indien dat niet lukt, een toegangspunt en een webserver opstarten om de login-gegevens op te slaan, om vervolgens de ESP te herstarten.
WiFiManager.h doet, met slechts 2 lijntjes in je sketch. Je vindt op deze site voldoende sketches (TM1637, SSD1306, SH1106, ...) om te zien hoe dat in de praktijk werkt.
Als je niet graag afhankelijk bent van een bibliotheek, kan je manueel de code toevoegen in je sketch. Hierbij een eenvoudig voorbeeld voor de ESP32, waarbij verbonden wordt met WiFi, en als het niet lukt, een WiFi-toegangspunt en webserver opgestart wordt om de gegevens op te slaan — inclusief een nette configuratiepagina met een lijst van gevonden netwerken en hun signaalsterkte.
/*------------------------------------------------------------
ESP32 WiFi Setup Portal
------------------------------------------------------------
Description
----------
If the ESP32 cannot connect to a previously stored WiFi network,
it creates a temporary access point allowing the user to select
and configure WiFi credentials via a web interface - 192.168.4.1
Features
--------
• Scans nearby WiFi networks
• Displays signal strength
• Indicates open / secured networks
• Stores credentials in ESP32 NVS (Preferences)
• Automatically reconnects after reboot
Author : johanok@gmail.com - https://espgo.be
Board : ESP32
IDE : Arduino IDE
------------------------------------------------------------
*/
#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>
#include <vector>
WebServer server(80);
Preferences flash; // Store data in non-volatile storage (NVS)
#define this_Ssid "ESP32-Conn" // Name of the temporary setup hotspot
String ssid, pasw;
struct WifiNet { // Structure used to store scanned networks
String ssid;
int rssi;
bool open; // true = no password required
};
std::vector<WifiNet> availableSSIDs; // List of discovered networks (sorted by signal strength)
void setup() {
Serial.begin(115200);
connect_to_WiFi();
Serial.println("Connected to " + WiFi.SSID());
}
void loop() {} // empty in this design
void connect_to_WiFi() {
WiFi.mode(WIFI_MODE_STA);
flash.begin("login_data", true); // Read previously stored credentials from flash
ssid = flash.getString("ssid", "");
pasw = flash.getString("pasw", "");
flash.end();
if (ssid.length() == 0) {
Serial.println("No saved network found.");
startPortal();
return;
}
Serial.print("Connecting");
#if CONFIG_IDF_TARGET_ESP32S3
WiFi.setSleep(false); // Ensure stable WiFi on ESP32-S3
#endif
WiFi.begin(ssid.c_str(), pasw.c_str());
for (uint8_t i = 0; i < 50; ++i) { // Try connecting for ~8 seconds
if (WiFi.isConnected()) {
WiFi.setAutoReconnect(true);
WiFi.persistent(true);
return;
}
delay(160);
Serial.print(".");
}
Serial.println("\nConnection failed.");
startPortal(); // Connection failed → start WiFi configuration portal
}
void startPortal() {
Serial.println("Connect to hotspot: " + String(this_Ssid));
int numNetworks = WiFi.scanNetworks();
availableSSIDs.clear();
for (int i = 0; i < numNetworks; i++) {
WifiNet net;
net.ssid = WiFi.SSID(i);
net.rssi = WiFi.RSSI(i);
net.open = (WiFi.encryptionType(i) == WIFI_AUTH_OPEN);
availableSSIDs.push_back(net);
}
std::sort(availableSSIDs.begin(), availableSSIDs.end(),
[](const WifiNet &a, const WifiNet &b) {
return a.rssi > b.rssi;
});
WiFi.mode(WIFI_MODE_AP);
WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info) { // Start the AP as soon as the driver indicates that AP mode is active
if (event == WIFI_EVENT_AP_START) {
Serial.println("AP mode ready, starting hotspot...");
WiFi.softAP(this_Ssid, "", 1);
}
});
server.on("/", serveWiFiSetupPage);
server.on("/setting", processWiFiSettingsSubmission);
server.begin();
for (;;) server.handleClient(); // Stay here until credentials are entered and ESP restarts
}
int rssiPercent(int rssi) { // Convert RSSI (dBm) into signal percentage (0-100)
if (rssi >= -50) return 100;
if (rssi <= -100) return 0;
return 2 * (rssi + 100);
}
void serveWiFiSetupPage() {
String html = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>WiFi Instellen</title>
<style>
:root{
--bg:#f0f4f8;
--card:#ffffff;
--border:#c8d4e0;
--accent:#0066cc;
--accent2:#004fa3;
--text:#1a2332;
--muted:#5a6a7a;
--success:#16a34a;
--radius:14px;
}
*{box-sizing:border-box;margin:0;padding:0}
body{
background:var(--bg);
color:var(--text);
font-family:'Segoe UI',system-ui,sans-serif;
min-height:100vh;
display:flex;
align-items:center;
justify-content:center;
padding:16px
}
.card{
background:var(--card);
border:1px solid var(--border);
border-radius:22px;
width:100%;
max-width:460px;
padding:32px;
box-shadow:0 4px 24px rgba(0,60,120,0.10)
}
.logo{
display:flex;
align-items:center;
justify-content:center;
gap:20px;
margin-bottom:8px}
.logo svg {
flex-shrink:0;
height: 46px;
vertical-align: middle;
}
.logo-text {
font-size:20px;
font-weight:900;
letter-spacing:.10em;
color:var(--accent);
line-height: 1; /* prevents extra vertical space */
}
h1{font-size:26px;font-weight:800;margin-bottom:6px}
.sub{
font-size:16px;
color:var(--muted);
margin-bottom:26px
}
.section-label{
font-size:13px;
font-weight:700;
letter-spacing:.07em;
text-transform:uppercase;
color:var(--muted);
margin-bottom:12px
}
.net-list{
display:flex;
flex-direction:column;
gap:8px;
margin-bottom:26px;
max-height:300px;
overflow-y:auto;
padding-right:2px
}
.net-btn{
display:flex;
align-items:center;
gap:14px;
width:100%;
background:#f7fafd;
border:2px solid var(--border);
border-radius:var(--radius);
padding:14px 18px;
cursor:pointer;
transition:all .15s;
text-align:left;
color:var(--text)
}
.net-btn:hover{
border-color:var(--accent);
background:#e8f0fb
}
.net-btn.selected{
border-color:var(--accent);
background:#ddeeff
}
.net-name{
font-size:17px;
font-weight:600;
flex:1;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap
}
.net-lock{font-size:18px;opacity:.8}
.signal{
display:flex;
align-items:flex-end;
gap:3px;
height:20px;
flex-shrink:0
}
.signal span{
display:inline-block;
width:5px;
background:#c8d4e0;
border-radius:2px
}
.signal span.on{background:var(--accent)}
.s1{height:4px}
.s2{height:7px}
.s3{height:11px}
.s4{height:15px}
.s5{height:20px}
.divider{
height:1px;
background:var(--border);
margin-bottom:24px
}
label{
display:block;
font-size:14px;
font-weight:700;
letter-spacing:.05em;
text-transform:uppercase;
color:var(--muted);
margin-bottom:8px
}
input[type=text],input[type=password]{
width:100%;
background:#f7fafd;
border:2px solid var(--border);
border-radius:var(--radius);
padding:14px 16px;
color:var(--text);
font-size:17px;
outline:none;
margin-bottom:16px
}
.btn{
width:100%;
background:linear-gradient(135deg,var(--accent2),var(--accent));
border:none;
border-radius:var(--radius);
padding:17px;
color:#fff;
font-size:18px;
font-weight:700;
cursor:pointer
}
</style>
</head>
<body>
<div class="card">
<div class="logo">
<svg width="46" height="46" preserveAspectRatio="xMidYMid" viewBox="0 -32 256 256">
<path fill-rule="evenodd" fill="#004d26" d="M198.751 31.302C181.263 12.072 156.041 0 128 0S74.737 12.072 57.249 31.302zM57.25 159.91c17.488
19.23 42.71 31.302 70.751 31.302s53.263-12.072 70.751-31.302zM42.464 39.431C19.012 39.431 0 58.443 0 81.896v27.42c0 23.452
19.012 42.464 42.464 42.464h171.072c23.452 0 42.464-19.012 42.464-42.464v-27.42c0-23.453-19.012-42.465-42.464-42.465zm209.168
41.98c0-20.773-16.839-37.612-37.611-37.612h-42.465c-20.772 0-37.611 16.839-37.611 37.611v28.512c0 20.773-16.839 37.612-37.611
37.612H214.02c20.772 0 37.611-16.84 37.611-37.612zM104.1 83.957v40.16h12.132v-40.16zm-1.214-12.678a7.219 7.219 0 1 0 14.438
0 7.219 7.219 0 0 0-14.438 0M21.96 65.517l15.651 58.6h10.07l11.77-40.523 11.768 40.524h10.434l15.409-58.601h-11.77l-8.977
38.582-11.162-38.582H54.355l-11.163 37.611L34.7 65.517zm131.276
0v58.6h12.86v-22.93h29.12v-11.89h-29.12v-11.89h31.06v-11.89zm52.656 18.441v40.16h12.133v-40.16zm-1.092-12.8a7.219
7.219 0 1 0 14.438 0 7.219 7.219 0 0 0-14.438 0"/>
</svg>
<span class="logo-text">WiFi Setup</span>
<svg width="46" height="46" data-name="Layer 1" viewBox="0 0 24 24">
<defs>
<style>
.cls-1 {
fill: none;
stroke: #004d26;
stroke-miterlimit: 10;
stroke-width: 1.91px;
}
.cls-arc {
fill: none;
stroke: #0066cc;
stroke-miterlimit: 10;
stroke-width: 1.91px;
}
</style>
</defs>
<rect width="21" height="7.64" x="1.5" y="14.86" class="cls-1" rx="1.91"/>
<path d="M6.27 18.68ZM10.09 18.68ZM19.64 18.68H12M4.36 3.41v11.45" class="cls-1"/>
<circle cx="4.36" cy="2.45" r=".95" class="cls-1"/>
<circle cx="9.14" cy="11.05" r=".95" style="fill:#004d26"/>
<!-- inner arc - blue -->
<path d="M8.18 7.23A4.76 4.76 0 0 1 13 12" class="cls-arc"/>
<!-- outer arc - blue -->
<path d="M8.18 3.41A8.59 8.59 0 0 1 16.77 12" class="cls-arc"/>
</svg>
</div>
<h1>Configure Connection</h1>
<p class="sub">Select a network or enter the credentials manually.</p>
<div class="section-label">Available networks</div>
<div class="net-list">
)rawliteral";
// -------- Add dynamic networks --------
for (const WifiNet &net : availableSSIDs) {
int pct = rssiPercent(net.rssi);
String s1 = pct >= 10 ? "on" : "";
String s2 = pct >= 30 ? "on" : "";
String s3 = pct >= 50 ? "on" : "";
String s4 = pct >= 70 ? "on" : "";
String s5 = pct >= 90 ? "on" : "";
String lockIcon = net.open ? "🔓" : "🔒";
html += "<button type='button' class='net-btn' data-ssid='";
html += net.ssid;
html += "' onclick=\"selectNet('";
html += net.ssid;
html += "',";
html += net.open ? "true" : "false";
html += ")\">";
html += "<span class='signal'>";
html += "<span class='s1 " + s1 + "'></span>";
html += "<span class='s2 " + s2 + "'></span>";
html += "<span class='s3 " + s3 + "'></span>";
html += "<span class='s4 " + s4 + "'></span>";
html += "<span class='s5 " + s5 + "'></span>";
html += "</span>";
html += "<span class='net-name'>" + net.ssid + "</span>";
html += "<span class='net-lock'>" + lockIcon + "</span>";
html += "</button>";
}
// -------- HTML continued --------
html += R"rawliteral(
</div>
<div class="divider"></div>
<form method="get" action="setting">
<label for="ssid-inp">Network Name (SSID)</label>
<input type="text" id="ssid-inp" name="ssid" placeholder="Network Name" required>
<label for="pass-inp">Password</label>
<input type="password" id="pass-inp" name="pass" placeholder="Password" required>
<button class="btn" type="submit">Save & Connect</button>
</form>
</div>
<script>
function selectNet(name,isOpen){
var si=document.getElementById('ssid-inp');
var pi=document.getElementById('pass-inp');
si.value=name;
document.querySelectorAll('.net-btn').forEach(function(b){
b.classList.toggle('selected', b.dataset.ssid === name);
});
if(isOpen){
pi.value='';
pi.disabled=true;
pi.placeholder='Open network - no password needed';
pi.removeAttribute('required');
}
else{
pi.disabled=false;
pi.placeholder='Password';
pi.setAttribute('required','required');
}
si.focus();
}
</script>
</body>
</html>
)rawliteral";
server.send(200, "text/html; charset=utf-8", html);
}
void processWiFiSettingsSubmission() { // Read submitted form data
ssid = server.arg("ssid");
pasw = server.arg("pass");
Serial.println();
Serial.println("Received WiFi credentials:");
Serial.println(ssid);
Serial.println(pasw);
flash.begin("login_data", false); // Store credentials in flash memory
flash.putString("ssid", ssid);
flash.putString("pasw", pasw);
flash.end();
String reply = F("<!DOCTYPE html><html><head>"
"<meta charset='UTF-8'>"
"<meta name='viewport' content='width=device-width,initial-scale=1'>"
"<title>Saved</title>"
"</head><body style='font-family:Arial;text-align:center;padding:40px'>"
"<h2>Settings saved</h2>"
"<p>The ESP32 will now restart and attempt to connect to:</p><b>");
reply += ssid;
reply += F("</b></body></html>");
server.send(200, "text/html; charset=utf-8", reply);
delay(1000);
ESP.restart();
}
Benieuwd naar concrete projecten die WiFiManager gebruiken? Bekijk het volledige overzicht met o.a. TM1637, SSD1306, SH1106, ST7735, ST7789, e-paper, de Lilygo T-Display(S3) en de Waveshare ESP32-S3.