Der vollständige Guide: WLAN-Signal-Monitor mit ESP8266 und OLED

Ein Anfänger-Projekt Schritt für Schritt erklärt


Einleitung

Dieses Projekt ist der ideale Einstieg in die Welt des ESP8266 und der IoT-Entwicklung. Wir bauen einen WLAN-Signal-Monitor, der dauerhaft die Empfangsstärke zweier Netzwerke (z.B. FRITZI und SMU) auf einem OLED-Display anzeigt. Das Besondere: Das Gerät arbeitet völlig autonom, erstellt einen eigenen WLAN-Access Point und bietet ein Webinterface zur Konfiguration – ganz ohne dass du dafür einen Router oder Internetzugang benötigst.

Für Anfänger ist dieses Projekt perfekt geeignet, weil es:

  • Alle Grundlagen der ESP8266-Programmierung abdeckt
  • Hardware und Software sinnvoll kombiniert
  • Sofort sichtbare Ergebnisse liefert
  • Erweiterbar ist für eigene Ideen

1. Benötigte Hardware

Grundausstattung:

KomponenteBeschreibungUngefährer Preis
ESP8266 NodeMCU v3Das Gehirn des Projekts5-8 €
OLED Display 0,96″SSD1306, 128×64 Pixel, I2C3-5 €
4x Jumper-KabelFemale-Female1-2 €
Micro-USB KabelZum Programmieren und Strom2-3 €

Gesamtkosten: ca. 10-15 €

Bezugsquellen:

  • ESP8266: Amazon, eBay, AZ-Delivery, Reichelt
  • OLED Display: Gleiche Quellen, oft im Set mit ESP8266

Wichtig beim Kauf:

  • ESP8266: Achte auf „NodeMCU v3“ oder „ESP-12E“ – das sind die gängigsten Versionen mit bester Dokumentation
  • OLED: Stelle sicher, dass es ein „SSD1306“ mit I2C-Anschluss ist (4 Pins: VCC, GND, SCL, SDA)

2. Verkabelung – Schritt für Schritt

Die Verbindung zwischen ESP8266 und OLED ist denkbar einfach, da wir die I2C-Schnittstelle nutzen:

OLED PinESP8266 PinKabel
VCC (3.3V)3.3VRot
GNDGNDSchwarz
SCL (Clock)D1 (GPIO5)Gelb/Grün
SDA (Data)D2 (GPIO4)Blau

Detaillierte Anleitung:

  1. Vorbereitung: Lege beide Boards vor dich. Der ESP8266 hat die Pins nach oben, das OLED hat meist 4 Pins in einer Reihe.
  2. Spannungsversorgung (VCC und GND):
    • Verbinde VCC (am OLED) mit 3.3V (am ESP8266) – das rote Kabel
    • Verbinde GND (am OLED) mit GND (am ESP8266) – das schwarze Kabel
      Warum?: Das OLED braucht 3.3V Betriebsspannung, genau wie der ESP8266 sie liefert
  3. Datenleitungen (I2C):
    • Verbinde SCL (am OLED) mit D1 (am ESP8266) – das ist GPIO5
    • Verbinde SDA (am OLED) mit D2 (am ESP8266) – das ist GPIO4
      Warum?: I2C ist ein Standardprotokoll, bei dem SCL der Takt und SDA die Datenleitung ist
  4. Kontrolle: Überprüfe alle Verbindungen. Sie sollten fest sitzen und die richtigen Pins treffen.

Häufige Fehler:

  • VCC mit 5V statt 3.3V verbunden → OLED kann durchbrennen
  • SCL und SDA vertauscht → Display bleibt dunkel
  • GND vergessen → Display funktioniert nicht

3. Software vorbereiten

Arduino IDE installieren

  1. Lade die Arduino IDE herunter: arduino.cc/en/software
  2. Installiere sie mit den Standard-Einstellungen

ESP8266 Board-Unterstützung hinzufügen

  1. Öffne die Arduino IDE
  2. Gehe zu Datei → Voreinstellungen (Strg+Komma)
  3. Füge bei „Zusätzliche Boardverwalter-URLs“ ein:texthttp://arduino.esp8266.com/stable/package_esp8266com_index.json
  4. Klicke auf OK
  5. Gehe zu Werkzeuge → Board → Boardverwalter
  6. Suche nach „esp8266“
  7. Installiere „esp8266 by ESP8266 Community“ (Version 3.0.2 oder neuer)

Benötigte Bibliotheken installieren

  1. Gehe zu Werkzeuge → Bibliotheken verwalten (Strg+Umschalt+I)
  2. Suche und installiere:
    • „U8g2“ von oliver (für das OLED Display)
    • „ESP8266WiFi“ ist bereits im Board-Paket enthalten
    • „ESP8266WebServer“ ebenfalls im Board-Paket

Wichtig: Nach der Installation aller Bibliotheken die IDE einmal neu starten.

Board-Einstellungen

Schließe deinen ESP8266 per USB an und stelle folgende Einstellungen ein:

  • Board: „NodeMCU 1.0 (ESP-12E Module)“
  • Upload Speed: „115200“
  • CPU Frequency: „80 MHz“
  • Flash Size: „4MB (FS:2MB OTA:~1019KB)“
  • Port: Der COM-Port, unter dem dein ESP8266 erscheint (unter Windows z.B. COM3, COM4; unter Linux /dev/ttyUSB0)

4. Der vollständige Code mit ausführlichen Erklärungen

cpp

// ============================================================
// WLAN SIGNAL MONITOR FÜR ESP8266 MIT OLED DISPLAY
// ============================================================
// Dieser Code erstellt einen dauerhaften Signal-Monitor für zwei
// WLAN-Netzwerke. Die Ergebnisse werden auf einem OLED angezeigt.
// Zusätzlich gibt es ein Webinterface zur Konfiguration.
// ============================================================

// -------------------- BIBLIOTHEKEN --------------------
// Hier binden wir alle benötigten Bibliotheken ein.
// U8g2lib.h: Für die Ansteuerung des OLED Displays
// Wire.h: Für die I2C-Kommunikation mit dem Display
// ESP8266WiFi.h: Für WLAN-Funktionen (scannen, AP-Modus)
// ESP8266WebServer.h: Für den eingebauten Webserver
// EEPROM.h: Zum Speichern von Einstellungen dauerhaft

#include <U8g2lib.h>
#include <Wire.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <EEPROM.h>

// -------------------- OLED KONFIGURATION --------------------
// Hier initialisieren wir das Display.
// U8G2_SSD1306_128X64_NONAME_F_SW_I2C bedeutet:
// - SSD1306: Der Display-Chip
// - 128x64: Auflösung
// - F: Full buffer (ganzer Bildspeicher)
// - SW_I2C: Software I2C (wir definieren die Pins selbst)
// Die Zahlen 14 und 12 sind die GPIO-Pins für SCL und SDA:
// 14 = GPIO14 (D5) aber wir wollen D1 (GPIO5) und D2 (GPIO4)!
// ACHTUNG: Hier müssen wir anpassen!

// Korrekte Initialisierung für D1=GPIO5 (SCL) und D2=GPIO4 (SDA):
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ 5, /* data=*/ 4, /* reset=*/ U8X8_PIN_NONE);
// U8G2_R0: Keine Rotation (0 Grad)
// clock=5: GPIO5 = D1 (SCL)
// data=4: GPIO4 = D2 (SDA)
// reset=U8X8_PIN_NONE: Kein Reset-Pin verwendet

// -------------------- WEB SERVER --------------------
// Erstelle einen Webserver, der auf Port 80 lauscht
ESP8266WebServer server(80);

// -------------------- EEPROM SPEICHER --------------------
// Der ESP8266 hat einen kleinen Speicher (EEPROM), in dem wir
// Einstellungen dauerhaft speichern können, auch nach Stromausfall.
// Wir reservieren 128 Bytes dafür.
#define EEPROM_SIZE 128

// Hier definieren wir, wo welche Daten im EEPROM liegen:
#define ADDR_SSID1 0 // Netzwerk 1 Name: Start bei Byte 0 (max 31 Zeichen + Null)
#define ADDR_SSID2 32 // Netzwerk 2 Name: Start bei Byte 32
#define ADDR_SCAN_INT 64 // Scan-Intervall: Start bei Byte 64 (4 Bytes für int)
#define ADDR_THRESHOLD 68 // Alarm-Schwelle: Start bei Byte 68 (4 Bytes für int)

// -------------------- STANDARDWERTE --------------------
// Falls keine Einstellungen gespeichert sind oder der EEPROM leer ist,
// verwenden wir diese Werte als Standard.
String network1 = "NW1"; // Erstes zu überwachendes Netzwerk
String network2 = "NW2"; // Zweites zu überwachendes Netzwerk
int scanInterval = 10; // Scan alle 10 Sekunden
int alarmThreshold = -80; // Alarm bei -80 dBm oder schlechter

// -------------------- STATUSVARIABLEN --------------------
// Diese Variablen speichern den aktuellen Zustand des Systems.
int rssi1 = -100; // Signalstärke Netzwerk 1 (Startwert sehr schlecht)
int rssi2 = -100; // Signalstärke Netzwerk 2
bool found1 = false; // Wurde Netzwerk 1 gefunden?
bool found2 = false; // Wurde Netzwerk 2 gefunden?
unsigned long lastScan = 0; // Zeitpunkt des letzten Scans (in Millisekunden)
bool alarmActive = false; // Ist gerade ein Alarm aktiv?
String lastAlarmTime = ""; // Wann war der letzte Alarm?

// -------------------- SIGNALSTÄRKE-SYMBOLE --------------------
// Hier definieren wir, wie die Signalstärke auf dem Display dargestellt wird.
// Wir haben 11 Stufen von 0 bis 10 Balken.
const char* signalBars[11] = {
"░░░░░░░░░░", // 0/10 - Kein Empfang
"▓░░░░░░░░░", // 1/10
"▓▓░░░░░░░░", // 2/10
"▓▓▓░░░░░░░", // 3/10
"▓▓▓▓░░░░░░", // 4/10
"▓▓▓▓▓░░░░░", // 5/10
"▓▓▓▓▓▓░░░░", // 6/10
"▓▓▓▓▓▓▓░░░", // 7/10
"▓▓▓▓▓▓▓▓░░", // 8/10
"▓▓▓▓▓▓▓▓▓░", // 9/10
"▓▓▓▓▓▓▓▓▓▓" // 10/10 - Exzellenter Empfang
};

// -------------------- HILFSFUNKTIONEN --------------------
// Diese Funktionen helfen uns, die Signalstärke zu interpretieren.

/**
* Wandelt RSSI-Wert in eine Textbeschreibung um
*
* RSSI (Received Signal Strength Indicator) ist ein Maß für die
* Empfangsstärke in dBm (Dezibel Milliwatt).
* Je näher an 0, desto besser. -30 dBm ist exzellent, -100 dBm ist nichts.
*
* Typische Werte:
* -30 dBm: Direkt neben dem Router (perfekt)
* -50 dBm: Ausgezeichnet
* -60 dBm: Sehr gut
* -70 dBm: Gut
* -80 dBm: Mittel
* -90 dBm: Schwach
* -100 dBm: Kein Empfang
*/
String getSignalQuality(int rssi) {
if (rssi >= -50) return "AUSGEZEICHNET";
if (rssi >= -60) return "SEHR GUT";
if (rssi >= -70) return "GUT";
if (rssi >= -80) return "MITTEL";
if (rssi >= -90) return "SCHWACH";
return "KEIN EMPFANG";
}

/**
* Wandelt RSSI in Prozent um (0-100%)
*
* Wir nehmen an, dass -30 dBm = 100% und -100 dBm = 0% ist.
* Alles dazwischen wird linear interpoliert.
*/
int getSignalPercent(int rssi) {
if (rssi >= -30) return 100;
if (rssi <= -100) return 0;
return map(rssi, -100, -30, 0, 100);
}

/**
* Wandelt RSSI in Balken-Stufe um (0-10)
*
* Wir teilen die 0-100% einfach in 10 Stufen auf.
*/
int getSignalBars(int rssi) {
int percent = getSignalPercent(rssi);
return map(percent, 0, 100, 0, 10);
}

/**
* Lädt die gespeicherten Einstellungen aus dem EEPROM
*
* Der EEPROM ist ein nicht-flüchtiger Speicher. Das bedeutet,
* die Daten bleiben auch nach Stromausfall erhalten.
* Wir müssen EEPROM.begin() aufrufen, bevor wir lesen/schreiben.
*/
void loadSettings() {
EEPROM.begin(EEPROM_SIZE);

// Netzwerk 1 lesen (Zeichen für Zeichen bis zum Null-Byte)
network1 = "";
for (int i = 0; i < 32; i++) {
char c = EEPROM.read(ADDR_SSID1 + i);
if (c == 0) break; // Null-Byte = Ende des Strings
network1 += c;
}
if (network1.length() == 0) network1 = "FRITZI"; // Fallback

// Netzwerk 2 lesen
network2 = "";
for (int i = 0; i < 32; i++) {
char c = EEPROM.read(ADDR_SSID2 + i);
if (c == 0) break;
network2 += c;
}
if (network2.length() == 0) network2 = "SMU"; // Fallback

// Scan Intervall lesen (ganze Zahl)
EEPROM.get(ADDR_SCAN_INT, scanInterval);
if (scanInterval < 5 || scanInterval > 300) scanInterval = 10; // Plausibilitätsprüfung

// Alarm Schwelle lesen
EEPROM.get(ADDR_THRESHOLD, alarmThreshold);
if (alarmThreshold > -30 || alarmThreshold < -100) alarmThreshold = -80; // Prüfung

EEPROM.end(); // EEPROM freigeben

// Ausgabe zur Kontrolle (für den seriellen Monitor)
Serial.println("Einstellungen geladen:");
Serial.println(" Netzwerk 1: " + network1);
Serial.println(" Netzwerk 2: " + network2);
Serial.println(" Scan Intervall: " + String(scanInterval) + "s");
Serial.println(" Alarm Schwelle: " + String(alarmThreshold) + "dBm");
}

/**
* Speichert die aktuellen Einstellungen im EEPROM
*/
void saveSettings() {
EEPROM.begin(EEPROM_SIZE);

// Netzwerk 1 speichern (jedes Zeichen einzeln, dann Null-Byte)
for (int i = 0; i < 32; i++) {
if (i < network1.length()) {
EEPROM.write(ADDR_SSID1 + i, network1[i]);
} else {
EEPROM.write(ADDR_SSID1 + i, 0); // Mit Null-Bytes auffüllen
}
}

// Netzwerk 2 speichern
for (int i = 0; i < 32; i++) {
if (i < network2.length()) {
EEPROM.write(ADDR_SSID2 + i, network2[i]);
} else {
EEPROM.write(ADDR_SSID2 + i, 0);
}
}

// Zahlen speichern
EEPROM.put(ADDR_SCAN_INT, scanInterval);
EEPROM.put(ADDR_THRESHOLD, alarmThreshold);

EEPROM.commit(); // Änderungen festschreiben
EEPROM.end();

Serial.println("Einstellungen gespeichert");
}

/**
* Führt einen WLAN-Scan durch und aktualisiert die RSSI-Werte
*
* Das ist das Herzstück des Programms. Wir scannen alle verfügbaren
* WLAN-Netzwerke und suchen nach unseren beiden Ziel-Netzwerken.
*/
void scanWifi() {
// In den Station-Modus wechseln und alte Verbindungen trennen
WiFi.mode(WIFI_STA);
WiFi.disconnect();
delay(10);

// Netzwerke scannen (blockierend)
int n = WiFi.scanNetworks();

// Reset der Statusvariablen
rssi1 = -100;
rssi2 = -100;
found1 = false;
found2 = false;
alarmActive = false;

// Gefundene Netzwerke durchsuchen
for (int i = 0; i < n; i++) {
String ssid = WiFi.SSID(i);
int rssi = WiFi.RSSI(i);

// Prüfen, ob es Netzwerk 1 ist
if (ssid == network1) {
rssi1 = rssi;
found1 = true;
if (rssi <= alarmThreshold) {
alarmActive = true; // Alarm auslösen
}
}

// Prüfen, ob es Netzwerk 2 ist
if (ssid == network2) {
rssi2 = rssi;
found2 = true;
if (rssi <= alarmThreshold) {
alarmActive = true;
}
}
}

// Scan-Ergebnisse freigeben
WiFi.scanDelete();

// Wenn Alarm, Zeit speichern
if (alarmActive) {
lastAlarmTime = getTimeString();
}
}

/**
* Gibt einen einfachen Zeit-String zurück (seit Systemstart)
*
* Da wir keine Echtzeituhr haben, zählen wir einfach die Millisekunden
* seit dem Start und rechnen sie in Stunden:Minuten:Sekunden um.
*/
String getTimeString() {
unsigned long seconds = millis() / 1000;
unsigned long minutes = seconds / 60;
unsigned long hours = minutes / 60;

char buffer[20];
snprintf(buffer, sizeof(buffer), "%02lu:%02lu:%02lu",
hours % 24, minutes % 60, seconds % 60);
return String(buffer);
}

/**
* Aktualisiert die Anzeige auf dem OLED
*
* Diese Funktion ist für das Layout des Displays verantwortlich.
* Sie zeichnet Text, Linien und Symbole an die richtigen Positionen.
*/
void updateDisplay() {
u8g2.clearBuffer(); // Display löschen

// ----- HEADER (obere Zeile) -----
u8g2.setFont(u8g2_font_6x10_tf); // Kleine Schrift
u8g2.setCursor(0, 10); // Position (x=0, y=10)
u8g2.print("WLAN SIGNAL MONITOR");

// Alarm-Anzeige rechts im Header
if (alarmActive) {
u8g2.setCursor(100, 10);
u8g2.print("ALARM!");
}

// Trennlinie unter dem Header
u8g2.drawLine(0, 12, 128, 12);

// ----- NETZWERK 1 (oberer Bereich) -----
u8g2.setFont(u8g2_font_7x13_tf); // Größere Schrift für Netzwerkname
u8g2.setCursor(0, 28);

// Netzwerkname kürzen, falls zu lang (max 8 Zeichen)
String displayName1 = network1;
if (displayName1.length() > 8) {
displayName1 = displayName1.substring(0, 8) + ".";
}
u8g2.print(displayName1);
u8g2.print(":");

// Signal-Balken
int bars1 = found1 ? getSignalBars(rssi1) : 0;
u8g2.setFont(u8g2_font_unifont_t_symbols); // Spezielle Schrift für Symbole
u8g2.setCursor(0, 42);
u8g2.print(signalBars[bars1]);

// Qualitäts-Text
u8g2.setFont(u8g2_font_6x10_tf);
u8g2.setCursor(0, 54);
if (found1) {
String quality = getSignalQuality(rssi1);
u8g2.print(quality);

// Alarm-Markierung (unterer Balken)
if (rssi1 <= alarmThreshold) {
u8g2.drawBox(0, 55, 128, 2); // Schwarzer Balken als Warnung
}
} else {
u8g2.print("NICHT GEFUNDEN");
}

// Trennlinie zwischen den Netzwerken
u8g2.drawLine(0, 56, 128, 56);

// ----- NETZWERK 2 (unterer Bereich) -----
u8g2.setFont(u8g2_font_7x13_tf);
u8g2.setCursor(0, 64);

// Netzwerkname kürzen
String displayName2 = network2;
if (displayName2.length() > 8) {
displayName2 = displayName2.substring(0, 8) + ".";
}
u8g2.print(displayName2);
u8g2.print(":");

// Für Netzwerk 2 haben wir weniger Platz, daher nur Kurzinfo
u8g2.setFont(u8g2_font_6x10_tf);
u8g2.setCursor(60, 64);
if (found2) {
u8g2.print(getSignalQuality(rssi2));

// Alarm-Markierung
if (rssi2 <= alarmThreshold) {
u8g2.drawBox(60, 65, 68, 2);
}
} else {
u8g2.print("NICHT DA");
}

// Bildschirm übertragen
u8g2.sendBuffer();
}

// -------------------- WEBINTERFACE --------------------
// Diese Funktionen erzeugen die HTML-Seiten für die Konfiguration.

/**
* Generiert die komplette HTML-Seite für das Webinterface
*/
String getWebPage() {
String html = "<!DOCTYPE html><html><head>";
html += "<meta name='viewport' content='width=device-width, initial-scale=1'>";
html += "<meta charset='UTF-8'>";
html += "<title>WLAN Monitor Einstellungen</title>";
html += "<style>";
html += "body { font-family: Arial, sans-serif; margin: 20px; background: #f0f0f0; }";
html += ".container { max-width: 600px; margin: auto; background: white; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }";
html += "h1 { color: #333; }";
html += "h2 { color: #555; border-bottom: 2px solid #4CAF50; padding-bottom: 5px; }";
html += "label { display: block; margin: 10px 0 5px; font-weight: bold; }";
html += "input, select { width: 100%; padding: 8px; margin-bottom: 15px; border: 1px solid #ddd; border-radius: 4px; }";
html += "button { background: #4CAF50; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; }";
html += "button:hover { background: #45a049; }";
html += ".status { background: #e8f5e9; padding: 10px; border-radius: 5px; margin: 10px 0; }";
html += ".alarm { background: #ffebee; padding: 10px; border-radius: 5px; margin: 10px 0; }";
html += "</style>";
html += "</head><body>";
html += "<div class='container'>";
html += "<h1>📶 WLAN Monitor Einstellungen</h1>";

// Aktueller Status-Block
html += "<div class='status'>";
html += "<h2>Aktueller Status</h2>";
html += "<p><strong>" + network1 + ":</strong> ";
html += found1 ? String(rssi1) + " dBm (" + getSignalQuality(rssi1) + ")" : "Nicht gefunden";
html += "</p>";
html += "<p><strong>" + network2 + ":</strong> ";
html += found2 ? String(rssi2) + " dBm (" + getSignalQuality(rssi2) + ")" : "Nicht gefunden";
html += "</p>";
html += "<p><strong>Letzter Scan:</strong> Vor " + String((millis() - lastScan) / 1000) + " Sekunden</p>";

// Alarm-Anzeige, falls aktiv
if (alarmActive) {
html += "<div class='alarm'>";
html += "<p><strong>⚠️ ALARM AKTIV!</strong></p>";
html += "<p>Signal unter " + String(alarmThreshold) + " dBm</p>";
html += "<p>Letzter Alarm: " + lastAlarmTime + "</p>";
html += "</div>";
}
html += "</div>";

// Einstellungs-Formular
html += "<h2>Einstellungen</h2>";
html += "<form action='/save' method='POST'>";

html += "<label for='ssid1'>Netzwerk 1 Name:</label>";
html += "<input type='text' id='ssid1' name='ssid1' value='" + network1 + "' maxlength='31'>";

html += "<label for='ssid2'>Netzwerk 2 Name:</label>";
html += "<input type='text' id='ssid2' name='ssid2' value='" + network2 + "' maxlength='31'>";

html += "<label for='interval'>Scan Intervall (Sekunden):</label>";
html += "<input type='number' id='interval' name='interval' value='" + String(scanInterval) + "' min='5' max='300'>";

html += "<label for='threshold'>Alarm Schwelle (dBm):</label>";
html += "<select id='threshold' name='threshold'>";
String thresholds[] = {"-70", "-75", "-80", "-85", "-90"};
for (String t : thresholds) {
html += "<option value='" + t + "'";
if (t.toInt() == alarmThreshold) html += " selected";
html += ">" + t + " dBm</option>";
}
html += "</select>";

html += "<button type='submit'>Einstellungen Speichern</button>";
html += "</form>";

// Steuerungs-Buttons
html += "<h2>Steuerung</h2>";
html += "<p><a href='/scan'><button>Sofort Scannen</button></a></p>";
html += "<p><a href='/reboot'><button>Neustart</button></a></p>";

// System-Informationen
html += "<h2>System Info</h2>";
html += "<p>IP Adresse: " + WiFi.softAPIP().toString() + "</p>";
html += "<p>ESP8266 Chip ID: " + String(ESP.getChipId()) + "</p>";
html += "<p>Free Heap: " + String(ESP.getFreeHeap()) + " Bytes</p>";

html += "</div>";
html += "</body></html>";

return html;
}

// -------------------- WEB SERVER HANDLER --------------------
// Diese Funktionen werden aufgerufen, wenn bestimmte URLs aufgerufen werden.

/**
* Hauptseite (/) anzeigen
*/
void handleRoot() {
server.send(200, "text/html", getWebPage());
}

/**
* Einstellungen speichern (/save)
* Wird aufgerufen, wenn das Formular abgeschickt wird
*/
void handleSave() {
// Werte aus dem Formular auslesen
if (server.hasArg("ssid1")) network1 = server.arg("ssid1");
if (server.hasArg("ssid2")) network2 = server.arg("ssid2");
if (server.hasArg("interval")) scanInterval = server.arg("interval").toInt();
if (server.hasArg("threshold")) alarmThreshold = server.arg("threshold").toInt();

// Im EEPROM speichern
saveSettings();

// Bestätigungsseite mit automatischer Weiterleitung
String html = "<!DOCTYPE html><html><head>";
html += "<meta http-equiv='refresh' content='3;url=/'>";
html += "<title>Einstellungen gespeichert</title>";
html += "<style>body { font-family: Arial; text-align: center; margin-top: 50px; }</style>";
html += "</head><body>";
html += "<h1>✅ Einstellungen gespeichert!</h1>";
html += "<p>Weiterleitung in 3 Sekunden...</p>";
html += "</body></html>";

server.send(200, "text/html", html);
}

/**
* Sofort-Scan ausführen (/scan)
*/
void handleScan() {
scanWifi(); // Neuen Scan durchführen
server.sendHeader("Location", "/"); // Zurück zur Hauptseite
server.send(302, "text/plain", "Scan gestartet");
}

/**
* Neustart ausführen (/reboot)
*/
void handleReboot() {
server.send(200, "text/html", "<h1>Neustart...</h1>");
delay(1000); // Kurz warten, damit die Seite gesendet wird
ESP.restart(); // ESP8266 neu starten
}

// -------------------- SETUP-FUNKTION --------------------
// Wird einmal beim Start ausgeführt

void setup() {
// Serielle Schnittstelle für Debug-Ausgaben starten
Serial.begin(115200);
Serial.println("\n=== WLAN Monitor mit Webinterface ===");

// Display initialisieren
u8g2.begin();

// Startbildschirm anzeigen
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_10x20_tf);
u8g2.setCursor(10, 30);
u8g2.print("WLAN");
u8g2.setCursor(10, 55);
u8g2.print("MONITOR");
u8g2.sendBuffer();
delay(2000);

// Gespeicherte Einstellungen laden
loadSettings();

// WiFi als Access Point starten
WiFi.mode(WIFI_AP); // Access Point Modus

// Einen eindeutigen Namen für den Access Point generieren
String apName = "WLAN-Monitor-" + String(ESP.getChipId());
WiFi.softAP(apName.c_str()); // AP starten (ohne Passwort)

Serial.println("Access Point gestartet:");
Serial.println(" SSID: " + apName);
Serial.println(" IP: " + WiFi.softAPIP().toString());

// Web Server Routen einrichten
server.on("/", handleRoot); // Hauptseite
server.on("/save", handleSave); // Einstellungen speichern
server.on("/scan", handleScan); // Sofort scannen
server.on("/reboot", handleReboot); // Neustart

// Web Server starten
server.begin();
Serial.println("Web Server gestartet");

// Ersten Scan durchführen
scanWifi();
updateDisplay();

Serial.println("System bereit!");
}

// -------------------- HAUPT-SCHLEIFE --------------------
// Wird immer wieder ausgeführt

void loop() {
// Web Server Anfragen bearbeiten
server.handleClient();

unsigned long now = millis();

// Regelmäßig scannen (alle scanInterval Sekunden)
if (now - lastScan >= scanInterval * 1000) {
scanWifi(); // Neuen Scan durchführen
updateDisplay(); // Display aktualisieren
lastScan = now; // Zeitpunkt merken

// Debug-Ausgabe über serielle Schnittstelle
Serial.print("Scan: ");
Serial.print(network1 + ": ");
if (found1) Serial.print(String(rssi1) + "dBm");
else Serial.print("nicht da");
Serial.print(" | " + network2 + ": ");
if (found2) Serial.println(String(rssi2) + "dBm");
else Serial.println("nicht da");

if (alarmActive) {
Serial.println("⚠️ ALARM: Signal unter " + String(alarmThreshold) + "dBm!");
}
}

// Kurze Pause, um CPU-Last zu reduzieren
delay(100);
}

5. Schritt-für-Schritt: Code hochladen

  1. Code kopieren: Markiere den gesamten Code oben und kopiere ihn (Strg+C)
  2. In Arduino IDE einfügen: Öffne die Arduino IDE, erstelle eine neue Datei (Strg+N) und füge den Code ein (Strg+V)
  3. Board auswählen: Gehe zu Werkzeuge → Board → ESP8266 Boards → NodeMCU 1.0 (ESP-12E Module)
  4. Port auswählen: Unter Werkzeuge → Port wählst du den COM-Port, unter dem dein ESP8266 erscheint
  5. Code überprüfen: Klicke auf den Haken (Überprüfen) – die IDE compiliert den Code und zeigt Fehler an, falls vorhanden
  6. Hochladen: Klicke auf den Pfeil (Hochladen). Die IDE kompiliert erneut und lädt den Code auf den ESP8266
  7. Warten: Während des Uploads blinkt die LED auf dem Board. Wenn „Done uploading“ erscheint, ist der Vorgang abgeschlossen
  8. Neustart: Der ESP8266 startet automatisch neu und führt den Code aus

Typische Fehler beim Hochladen:

  • „Upload fehlgeschlagen“: Halte den Flash-Button gedrückt, drücke kurz Reset, lasse Flash los – dann erneut versuchen
  • „Port nicht gefunden“: Prüfe, ob der richtige USB-Treiber installiert ist (CP2102 oder CH340)
  • „Board nicht kompatibel“: Prüfe, ob das richtige Board ausgewählt ist

6. Inbetriebnahme und Test

Erste Schritte nach dem Hochladen:

  1. Display prüfen: Das OLED sollte „WLAN MONITOR“ anzeigen, dann nach kurzer Zeit die Signalstärke
  2. Mit dem Access Point verbinden:
    • Öffne die WLAN-Einstellungen deines Smartphones/PCs
    • Suche nach einem Netzwerk namens „WLAN-Monitor-XXXXXX“ (die Zahlen sind individuell)
    • Verbinde dich (kein Passwort nötig)
  3. Webinterface öffnen:
    • Öffne einen Browser
    • Gib ein: http://192.168.4.1
    • Du siehst nun die Webseite mit Status und Einstellungen
  4. Funktionstest:
    • Die Signalstärke sollte angezeigt werden
    • Ändere die Netzwerk-Namen (z.B. dein eigenes WLAN)
    • Klicke auf „Sofort Scannen“ und beobachte die Änderungen
    • Stelle einen Alarm ein und prüfe, ob er bei schwachem Signal auslöst

7. Detaillierte Erklärung des Codes

7.1 Grundlegende Konzepte

Was ist ein ESP8266?
Der ESP8266 ist ein Mikrocontroller mit integriertem WLAN. Er kann Sensoren auslesen, Displays ansteuern und sich mit dem Internet verbinden – alles für wenige Euro.

Was ist I2C?
I2C (Inter-Integrated Circuit) ist ein Kommunikationsprotokoll, mit dem mehrere Geräte über nur zwei Leitungen (SCL und SDA) miteinander kommunizieren können. Unser OLED Display nutzt I2C.

Was ist RSSI?
RSSI (Received Signal Strength Indicator) ist ein Maß für die Empfangsstärke eines WLAN-Signals. Der Wert wird in dBm (Dezibel Milliwatt) angegeben und ist negativ. Je näher an 0, desto besser: -30 dBm ist exzellent, -90 dBm ist sehr schwach.

7.2 Die wichtigsten Code-Teile im Detail

Bibliotheken einbinden:

cpp

#include <U8g2lib.h>      // Für das OLED Display
#include <Wire.h>         // Für I2C-Kommunikation
#include <ESP8266WiFi.h>  // Für WLAN-Funktionen
#include <ESP8266WebServer.h> // Für den Webserver
#include <EEPROM.h>       // Für dauerhafte Speicherung

Jede Bibliothek erweitert die Fähigkeiten unseres Programms. Stell dir vor, du kaufst Werkzeuge für deinen Werkzeugkasten – jede Bibliothek ist ein spezialisiertes Werkzeug.

Display initialisieren:

cpp

U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ 5, /* data=*/ 4, /* reset=*/ U8X8_PIN_NONE);

Hier sagen wir dem Programm: „Wir haben ein OLED mit 128×64 Pixeln, das per Software-I2C an GPIO5 (SCL) und GPIO4 (SDA) angeschlossen ist.“ Die Zahlen 5 und 4 sind entscheidend – sie müssen genau den Pins entsprechen, die du verdrahtet hast.

Signalstärke interpretieren:

cpp

String getSignalQuality(int rssi) {
  if (rssi >= -50) return "AUSGEZEICHNET";
  if (rssi >= -60) return "SEHR GUT";
  if (rssi >= -70) return "GUT";
  if (rssi >= -80) return "MITTEL";
  if (rssi >= -90) return "SCHWACH";
  return "KEIN EMPFANG";
}

Diese Funktion übersetzt den technischen RSSI-Wert in eine für Menschen verständliche Beschreibung. Sie ist ein gutes Beispiel für „Abstraktion“ – wir verstecken die technischen Details hinter einfachen Worten.

WLAN scannen:

cpp

int n = WiFi.scanNetworks();
for (int i = 0; i < n; i++) {
  String ssid = WiFi.SSID(i);
  int rssi = WiFi.RSSI(i);
  // ... prüfen, ob es unser Netzwerk ist
}

Hier bitten wir den ESP8266, alle in Reichweite befindlichen WLANs zu suchen. Dann gehen wir die Liste durch und merken uns die Signalstärke unserer beiden Ziel-Netzwerke.

Display ansteuern:

cpp

u8g2.clearBuffer();           // Display löschen
u8g2.setFont(u8g2_font_...);  // Schriftart wählen
u8g2.setCursor(x, y);         // Position setzen
u8g2.print("Text");           // Text schreiben
u8g2.drawLine(x1, y1, x2, y2); // Linie zeichnen
u8g2.sendBuffer();            // Alles anzeigen

Das ist wie malen auf einem digitalen Blatt Papier: Zuerst löschen wir die Leinwand, dann wählen wir Pinsel und Farbe (Schriftart), setzen den Pinsel an eine Position und malen (print/zeichnen). Zum Schluss schicken wir das Bild an den Bildschirm.

7.3 Die Hauptschleife verstehen

Die loop()-Funktion wird immer wieder ausgeführt, solange das Gerät läuft:

  1. Webserver bedienen: server.handleClient() prüft, ob jemand im Browser auf unser Gerät zugreift, und reagiert darauf.
  2. Zeit prüfen: if (now - lastScan >= scanInterval * 1000) rechnet aus, ob es Zeit für einen neuen Scan ist. millis() zählt die Millisekunden seit dem Start, lastScan merkt sich den Zeitpunkt des letzten Scans.
  3. Scannen und anzeigen: Wenn genug Zeit vergangen ist, wird neu gescannt, das Display aktualisiert und der Zeitpunkt gemerkt.
  4. Kurze Pause: delay(100) wartet 100 Millisekunden – das verhindert, dass der ESP8266 zu viel Rechenleistung verbraucht.

8. Fehlerbehebung – Häufige Probleme und Lösungen

Display bleibt dunkel:

Mögliche UrsacheLösung
Falsche VerkabelungPrüfe: VCC → 3.3V, GND → GND, SCL → D1, SDA → D2
I2C-Adresse falschManche OLEDs haben 0x3C, andere 0x3D. Probiere im Code u8g2.begin(0x3D)
Display defektTeste mit einem anderen I2C-Gerät (z.B. Sensor)

Keine WLAN-Netzwerke gefunden:

Mögliche UrsacheLösung
Zu weit vom Router entferntGehe näher an den Router
Falscher NetzwerknamePrüfe Groß-/Kleinschreibung im Webinterface
WLAN deaktiviertStelle sicher, dass das Netzwerk sichtbar ist

Webinterface nicht erreichbar:

Mögliche UrsacheLösung
Falsches WLANVerbinde mit „WLAN-Monitor-XXXXXX“, nicht deinem Heimnetz
Falsche IPÖffne http://192.168.4.1 – das ist immer die IP des Access Points
Gerät nicht gestartetDrücke den Reset-Knopf am ESP8266

Ungewöhnliche RSSI-Werte:

Mögliche UrsacheLösung
InterferenzenAndere elektronische Geräte können stören
Metall in der NäheMetall abschirmt WLAN-Signale
Router-KonfigurationManche Router senden mit reduzierter Leistung

9. Erweiterungsmöglichkeiten

Das Grundprojekt lässt sich vielfältig erweitern:

9.1 Hardware-Erweiterungen

  • Buzzer hinzufügen für akustischen Alarm bei schwachem Signal
  • RGB-LED für visuelle Signalqualität (grün/gelb/rot)
  • Batteriebetrieb mit Spannungsmessung und Deep-Sleep-Modus
  • Temperatur-/Luftfeuchtigkeitssensor zusätzlich integrieren

9.2 Software-Erweiterungen

  • Signal-Historie speichern und als Grafik anzeigen
  • E-Mail-Benachrichtigung bei Alarm (dann bräuchte man aber Internet)
  • Mehrere Netzwerke überwachen (bis zu 5)
  • Datenlogger mit SD-Karte oder Upload zu ThingSpeak

9.3 Gehäuse-Ideen

  • 3D-gedrucktes Gehäuse mit Platz für Batterien
  • Modellbaukasten (z.B. mit LEGO)
  • Aufputz-Gehäuse für die Hutschiene im Sicherungskasten

10. Hintergrundwissen für Fortgeschrittene

Warum funktioniert das ohne Internet?

Der ESP8266 erstellt einen eigenen Access Point (WLAN-Hotspot). Das ist wie ein kleines eigenes Netzwerk, das nur aus dir und dem ESP besteht. Du verbindest dich direkt mit ihm, ohne dass ein Router nötig ist.

Wie genau funktioniert der WLAN-Scan?

Wenn WiFi.scanNetworks() aufgerufen wird, sendet der ESP8266 eine Anfrage an alle WLAN-Kanäle. Router in der Nähe antworten mit ihrer SSID und ihrer Signalstärke. Das ist vergleichbar mit einem Scanner, der Radiowellen abhört.

Was bedeutet die Alarm-Schwelle?

-70 dBm ist ein guter Wert, -80 dBm ist akzeptabel. Wenn du den Alarm auf -80 setzt, warnt dich das Gerät, sobald eines der Netzwerke schwächer als -80 dBm wird – das kann ein Hinweis auf Störungen oder große Entfernung sein.

Warum EEPROM und nicht normale Variablen?

Normale Variablen sind flüchtig – bei Stromausfall sind sie weg. Der EEPROM (Electrically Erasable Programmable Read-Only Memory) behält seine Daten auch ohne Strom. So bleiben deine Einstellungen erhalten, selbst wenn du das Gerät vom Strom nimmst.


Fazit

Du hast gerade ein vollwertiges IoT-Gerät gebaut – von der Verkabelung über die Programmierung bis zur Inbetriebnahme. Der WLAN-Signal-Monitor zeigt dir nicht nur die Empfangsqualität deiner Netzwerke, sondern hat auch ein eigenes Webinterface zur Konfiguration.

Dieses Projekt ist der perfekte Einstieg in die Welt des ESP8266, weil es:

  • Alle Grundlagen abdeckt (Hardware, I2C, WLAN, WebServer)
  • Sofort nutzbare Ergebnisse liefert
  • Erweiterbar ist für eigene Ideen
  • Praktischen Nutzen im Alltag hat

Der Code ist bewusst ausführlich kommentiert, damit du jede Zeile verstehen und später anpassen kannst. Experimentiere ruhig mit den Einstellungen, ändere die Display-Darstellung oder füge eigene Funktionen hinzu – das ist der beste Weg, um zu lernen.

Viel Spaß mit deinem ersten eigenen IoT-Projekt!


Quellen

Offizielle Dokumentationen

Fachbücher

  • „ESP8266: Programmierung und Anwendung“ von Michael Wieland, Franzis Verlag 2019
  • *“IoT-Projekte mit ESP8266″* von Erik Bartmann, O’Reilly 2020
  • „Handbuch Elektronik“ von Rainer Köthe, Carl Hanser Verlag 2018

Online-Ressourcen


Kommentar abschicken