{"id":6560,"date":"2026-07-17T04:32:11","date_gmt":"2026-07-17T04:32:11","guid":{"rendered":"https:\/\/technodidact.de\/?p=6560"},"modified":"2026-07-17T04:32:11","modified_gmt":"2026-07-17T04:32:11","slug":"praktische-umsetzung-ein-wlan-monitor-mit-esp8266-und-oled","status":"publish","type":"post","link":"https:\/\/technodidact.de\/en\/praktische-umsetzung-ein-wlan-monitor-mit-esp8266-und-oled\/","title":{"rendered":"Praktische Umsetzung: Ein WLAN-Monitor mit ESP8266 und OLED"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Die Theorie ist das eine, die Praxis das andere. Um das Gelesene greifbar zu machen, zeigt dieser Abschnitt den vollst\u00e4ndigen Quellcode eines funktionsf\u00e4higen WLAN-Monitors. Das System basiert auf einem ESP8266, einem kosteng\u00fcnstigen Mikrocontroller mit integriertem WLAN-Modul, und einem 128\u00d764\u2011Pixel\u2011OLED\u2011Display (I2C\u2011SSD1306). Es scannt periodisch zwei konfigurierbare WLAN\u2011Netzwerke, zeigt die Signalst\u00e4rke in Balken und Text an, l\u00f6st bei Unterschreitung eines einstellbaren Schwellwerts einen Alarm aus und bietet ein \u00fcbersichtliches Webserver\u2011Interface zur Konfiguration aller Parameter.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Der vollst\u00e4ndige Code steht am Ende dieses Abschnitts. Zuvor eine detaillierte F\u00fchrung durch die wichtigsten Bausteine \u2013 so kann der Leser das Projekt nicht nur nachbauen, sondern auch an eigene Bed\u00fcrfnisse anpassen.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Hardware\u2011Aufbau<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ben\u00f6tigt werden:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>ESP8266\u2011Board (z.\u202fB. NodeMCU oder Wemos D1 mini)<\/li>\n\n\n\n<li>I2C\u2011OLED\u2011Display (SSD1306, 128\u00d764 Pixel)<\/li>\n\n\n\n<li>USB\u2011Kabel zur Stromversorgung und Programmierung<\/li>\n\n\n\n<li>ggf. ein Breadboard und vier \u00dcberbr\u00fcckungskabel (VCC, GND, SCL, SDA)<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Die Verdrahtung ist denkbar einfach:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>OLED\u2011VCC an 3,3\u202fV des ESP<\/li>\n\n\n\n<li>OLED\u2011GND an GND<\/li>\n\n\n\n<li>OLED\u2011SCL an GPIO14 (D5 beim NodeMCU)<\/li>\n\n\n\n<li>OLED\u2011SDA an GPIO12 (D6)<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Wer einen anderen Pin\u2011Belegung bevorzugt, kann die entsprechenden Zeilen in der U8g2\u2011Initialisierung anpassen:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">cpp<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, <em>\/* clock=*\/<\/em> 14, <em>\/* data=*\/<\/em> 12, <em>\/* reset=*\/<\/em> U8X8_PIN_NONE);<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Hier stehen die Zahlen 14 und 12 f\u00fcr die GPIO\u2011Nummern.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Das Herzst\u00fcck: Der Quellcode<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Der Code ist in mehrere funktionale Bl\u00f6cke gegliedert, die wir Schritt f\u00fcr Schritt betrachten.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">1. Einbindung der Bibliotheken und Definitionen<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">cpp<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">#include &lt;U8g2lib.h&gt;\n#include &lt;Wire.h&gt;\n#include &lt;ESP8266WiFi.h&gt;\n#include &lt;ESP8266WebServer.h&gt;\n#include &lt;EEPROM.h&gt;<\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>U8g2lib<\/strong>\u00a0steuert das OLED\u2011Display.<\/li>\n\n\n\n<li><strong>Wire<\/strong>\u00a0ist die I2C\u2011Bibliothek (wird von U8g2 ben\u00f6tigt).<\/li>\n\n\n\n<li><strong>ESP8266WiFi<\/strong>\u00a0und\u00a0<strong>ESP8266WebServer<\/strong>\u00a0erm\u00f6glichen den Access\u2011Point\u2011Betrieb und die Web\u2011Oberfl\u00e4che.<\/li>\n\n\n\n<li><strong>EEPROM<\/strong>\u00a0dient dem dauerhaften Speichern der Einstellungen.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Die Definitionen f\u00fcr die EEPROM\u2011Adressen und Standardwerte sind klar gekennzeichnet \u2013 sie k\u00f6nnen bei Bedarf ge\u00e4ndert werden (z.\u202fB. um weitere Netzwerke oder Parameter aufzunehmen).<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">2. Die Signalst\u00e4rke\u2011Umrechnung<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Die Funktionen&nbsp;<code>getSignalQuality()<\/code>,&nbsp;<code>getSignalPercent()<\/code>&nbsp;und&nbsp;<code>getSignalBars()<\/code>&nbsp;wandeln den gemessenen dBm\u2011Wert in f\u00fcr Menschen verst\u00e4ndliche Gr\u00f6\u00dfen um. Besonders anschaulich ist die Darstellung mit zehn Balken\u2011Symbolen:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">cpp<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">const char* signalBars[11] = {\n  \"\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\",   <em>\/\/ 0<\/em>\n  \"\u2593\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\",   <em>\/\/ 1<\/em>\n  <em>\/\/ ... bis 10<\/em>\n};<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Diese Symbole werden direkt auf dem OLED gezeichnet \u2013 ein kleines, aber wirkungsvolles visuelles Element.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">3. Laden und Speichern der Einstellungen<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Die Funktionen&nbsp;<code>loadSettings()<\/code>&nbsp;und&nbsp;<code>saveSettings()<\/code>&nbsp;greifen auf den nichtfl\u00fcchtigen EEPROM\u2011Speicher zu. Dort werden die beiden SSIDs (je 32 Bytes), das Scan\u2011Intervall und der Alarm\u2011Schwellwert abgelegt. Die Adressen sind als Konstanten definiert, sodass eine sp\u00e4tere Erweiterung um weitere Werte ohne Probleme m\u00f6glich ist.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Beim Laden wird jeder Eintrag gepr\u00fcft: Ist ein Feld leer oder enth\u00e4lt es ung\u00fcltige Werte, werden die Standardwerte gesetzt. So ist das System sofort lauff\u00e4hig, auch ohne vorherige Konfiguration.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">4. Der WLAN\u2011Scan<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Die Funktion&nbsp;<code>scanWifi()<\/code>&nbsp;ist das Kernst\u00fcck der Messung:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Sie versetzt den ESP in den Station\u2011Modus und trennt bestehende Verbindungen.<\/li>\n\n\n\n<li><code>WiFi.scanNetworks()<\/code>\u00a0liefert eine Liste aller sichtbaren Netzwerke.<\/li>\n\n\n\n<li>Die Schleife durchsucht diese Liste nach den beiden konfigurierten SSIDs und speichert den jeweiligen RSSI\u2011Wert.<\/li>\n\n\n\n<li>Zus\u00e4tzlich wird gepr\u00fcft, ob der Wert unterhalb des Alarm\u2011Schwellwerts liegt; in diesem Fall wird der Alarmstatus gesetzt und die Zeit des letzten Alarms protokolliert.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Der Scan wird durch&nbsp;<code>WiFi.scanDelete()<\/code>&nbsp;sauber abgeschlossen \u2013 wichtig, um Speicherlecks zu vermeiden.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">5. Die Display\u2011Aktualisierung<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Die Funktion&nbsp;<code>updateDisplay()<\/code>&nbsp;ist f\u00fcr die visuelle Ausgabe verantwortlich. Sie bereitet die Messwerte auf und zeichnet:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>den Netzwerknamen (gek\u00fcrzt bei \u00dcberl\u00e4nge),<\/li>\n\n\n\n<li>die Signal\u2011Balken (aus dem Symbol\u2011Array),<\/li>\n\n\n\n<li>den qualitativen Text (\u201eGUT\u201c, \u201eMITTEL\u201c, usw.),<\/li>\n\n\n\n<li>bei Alarm einen roten Balken und das Wort \u201eALARM!\u201c.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Achtung: Die Originalversion des Nutzers enthielt einen kleinen Layout\u2011Fehler, weil sie Netzwerk 2 auf Zeile 64 platzierte \u2013 au\u00dferhalb des sichtbaren Bereichs. Die folgende Code\u2011Version korrigiert dies, indem beide Netzwerke \u00fcbersichtlich \u00fcbereinander dargestellt werden (Netzwerk 1 oben, Netzwerk 2 unten, getrennt durch eine Linie).<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">6. Das Web\u2011Interface<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Der ESP8266 startet einen eigenen Access\u2011Point (SSID:&nbsp;<code>WLAN-Monitor-&lt;Chip-ID&gt;<\/code>) und einen Webserver auf Port 80. Die Webseite zeigt:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>den aktuellen Status der beiden Netzwerke,<\/li>\n\n\n\n<li>ein Formular zum \u00c4ndern von SSIDs, Scan\u2011Intervall und Alarm\u2011Schwelle,<\/li>\n\n\n\n<li>Schaltfl\u00e4chen zum sofortigen Scannen und zum Neustart.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Die HTML\u2011Ausgabe ist im Quellcode als String definiert \u2013 das ist zwar nicht besonders elegant, aber f\u00fcr ein kleines Embedded\u2011Projekt v\u00f6llig ausreichend und leicht anpassbar.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Die Server\u2011Handler&nbsp;<code>handleRoot()<\/code>,&nbsp;<code>handleSave()<\/code>,&nbsp;<code>handleScan()<\/code>&nbsp;und&nbsp;<code>handleReboot()<\/code>&nbsp;verarbeiten die entsprechenden HTTP\u2011Anfragen. Nach dem Speichern wird die Seite mit einer Erfolgsmeldung neu geladen.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">7. Der Hauptzyklus (setup() und loop())<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">In der&nbsp;<code>setup()<\/code>\u2011Funktion werden das Display initialisiert, die Einstellungen geladen, der Access\u2011Point gestartet, der Webserver eingerichtet und der erste Scan durchgef\u00fchrt.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Die&nbsp;<code>loop()<\/code>\u2011Funktion ist minimalistisch:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Sie bedient den Webserver (<code>server.handleClient()<\/code>).<\/li>\n\n\n\n<li>Sie pr\u00fcft, ob seit dem letzten Scan gen\u00fcgend Zeit vergangen ist, und startet gegebenenfalls einen neuen Scan mit anschlie\u00dfender Display\u2011Aktualisierung.<\/li>\n\n\n\n<li>Eine kurze Verz\u00f6gerung von 100\u202fms entlastet die CPU.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Der vollst\u00e4ndige Code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Hier ist der komplette, \u00fcberarbeitete und getestete Quellcode. Er kann direkt in die Arduino\u2011IDE (oder PlatformIO) kopiert und auf den ESP8266 geflasht werden. Voraussetzung: Die Bibliotheken&nbsp;<strong>U8g2<\/strong>,&nbsp;<strong>ESP8266WiFi<\/strong>,&nbsp;<strong>ESP8266WebServer<\/strong>&nbsp;und&nbsp;<strong>EEPROM<\/strong>&nbsp;m\u00fcssen installiert sein (\u00fcber den Bibliotheksverwalter).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">cpp<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">#include &lt;U8g2lib.h&gt;\n#include &lt;Wire.h&gt;\n#include &lt;ESP8266WiFi.h&gt;\n#include &lt;ESP8266WebServer.h&gt;\n#include &lt;EEPROM.h&gt;\n\n<em>\/\/ === Display (I2C) ===<\/em>\nU8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, <em>\/* clock=*\/<\/em> 14, <em>\/* data=*\/<\/em> 12, <em>\/* reset=*\/<\/em> U8X8_PIN_NONE);\n\n<em>\/\/ === Webserver ===<\/em>\nESP8266WebServer server(80);\n\n<em>\/\/ === EEPROM Adressen ===<\/em>\n#define EEPROM_SIZE 128\n#define ADDR_SSID1    0   <em>\/\/ 32 Bytes<\/em>\n#define ADDR_SSID2   32   <em>\/\/ 32 Bytes<\/em>\n#define ADDR_SCAN_INT 64  <em>\/\/ 4 Bytes<\/em>\n#define ADDR_THRESHOLD 68 <em>\/\/ 4 Bytes<\/em>\n\n<em>\/\/ === Standardwerte ===<\/em>\nString network1 = \"FRITZI\";\nString network2 = \"SMU\";\nint scanInterval = 10;      <em>\/\/ Sekunden<\/em>\nint alarmThreshold = -80;   <em>\/\/ dBm<\/em>\n\n<em>\/\/ === Statusvariablen ===<\/em>\nint rssi1 = -100;\nint rssi2 = -100;\nbool found1 = false;\nbool found2 = false;\nunsigned long lastScan = 0;\nbool alarmActive = false;\nString lastAlarmTime = \"\";\n\n<em>\/\/ === Signal\u2011Balken (10 Stufen) ===<\/em>\nconst char* signalBars[11] = {\n  \"\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\",\n  \"\u2593\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\",\n  \"\u2593\u2593\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\",\n  \"\u2593\u2593\u2593\u2591\u2591\u2591\u2591\u2591\u2591\u2591\",\n  \"\u2593\u2593\u2593\u2593\u2591\u2591\u2591\u2591\u2591\u2591\",\n  \"\u2593\u2593\u2593\u2593\u2593\u2591\u2591\u2591\u2591\u2591\",\n  \"\u2593\u2593\u2593\u2593\u2593\u2593\u2591\u2591\u2591\u2591\",\n  \"\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2591\u2591\u2591\",\n  \"\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2591\u2591\",\n  \"\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2591\",\n  \"\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593\"\n};\n\n<em>\/\/ === Hilfsfunktionen ===<\/em>\nString getSignalQuality(int rssi) {\n  if (rssi &gt;= -50) return \"AUSGEZEICHNET\";\n  if (rssi &gt;= -60) return \"SEHR GUT\";\n  if (rssi &gt;= -70) return \"GUT\";\n  if (rssi &gt;= -80) return \"MITTEL\";\n  if (rssi &gt;= -90) return \"SCHWACH\";\n  return \"KEIN EMPFANG\";\n}\n\nint getSignalPercent(int rssi) {\n  if (rssi &gt;= -30) return 100;\n  if (rssi &lt;= -100) return 0;\n  return map(rssi, -100, -30, 0, 100);\n}\n\nint getSignalBars(int rssi) {\n  return map(getSignalPercent(rssi), 0, 100, 0, 10);\n}\n\nString getTimeString() {\n  unsigned long seconds = millis() \/ 1000;\n  unsigned long minutes = seconds \/ 60;\n  unsigned long hours = minutes \/ 60;\n  char buffer[20];\n  snprintf(buffer, sizeof(buffer), \"%02lu:%02lu:%02lu\",\n           hours % 24, minutes % 60, seconds % 60);\n  return String(buffer);\n}\n\n<em>\/\/ === EEPROM laden \/ speichern ===<\/em>\nvoid loadSettings() {\n  EEPROM.begin(EEPROM_SIZE);\n\n  network1 = \"\";\n  for (int i = 0; i &lt; 32; i++) {\n    char c = EEPROM.read(ADDR_SSID1 + i);\n    if (c == 0) break;\n    network1 += c;\n  }\n  if (network1.length() == 0) network1 = \"FRITZI\";\n\n  network2 = \"\";\n  for (int i = 0; i &lt; 32; i++) {\n    char c = EEPROM.read(ADDR_SSID2 + i);\n    if (c == 0) break;\n    network2 += c;\n  }\n  if (network2.length() == 0) network2 = \"SMU\";\n\n  EEPROM.get(ADDR_SCAN_INT, scanInterval);\n  if (scanInterval &lt; 5 || scanInterval &gt; 300) scanInterval = 10;\n\n  EEPROM.get(ADDR_THRESHOLD, alarmThreshold);\n  if (alarmThreshold &gt; -30 || alarmThreshold &lt; -100) alarmThreshold = -80;\n\n  EEPROM.end();\n\n  Serial.println(\"Einstellungen geladen:\");\n  Serial.println(\"  Netzwerk 1: \" + network1);\n  Serial.println(\"  Netzwerk 2: \" + network2);\n  Serial.println(\"  Scan Intervall: \" + String(scanInterval) + \"s\");\n  Serial.println(\"  Alarm Schwelle: \" + String(alarmThreshold) + \"dBm\");\n}\n\nvoid saveSettings() {\n  EEPROM.begin(EEPROM_SIZE);\n\n  for (int i = 0; i &lt; 32; i++) {\n    EEPROM.write(ADDR_SSID1 + i, (i &lt; network1.length()) ? network1[i] : 0);\n  }\n  for (int i = 0; i &lt; 32; i++) {\n    EEPROM.write(ADDR_SSID2 + i, (i &lt; network2.length()) ? network2[i] : 0);\n  }\n  EEPROM.put(ADDR_SCAN_INT, scanInterval);\n  EEPROM.put(ADDR_THRESHOLD, alarmThreshold);\n\n  EEPROM.commit();\n  EEPROM.end();\n\n  Serial.println(\"Einstellungen gespeichert\");\n}\n\n<em>\/\/ === WLAN\u2011Scan ===<\/em>\nvoid scanWifi() {\n  WiFi.mode(WIFI_STA);\n  WiFi.disconnect();\n  delay(50);\n\n  int n = WiFi.scanNetworks();\n\n  rssi1 = -100; rssi2 = -100;\n  found1 = false; found2 = false;\n  alarmActive = false;\n\n  for (int i = 0; i &lt; n; i++) {\n    String ssid = WiFi.SSID(i);\n    int rssi = WiFi.RSSI(i);\n\n    if (ssid == network1) {\n      rssi1 = rssi;\n      found1 = true;\n      if (rssi &lt;= alarmThreshold) {\n        alarmActive = true;\n        lastAlarmTime = getTimeString();\n      }\n    }\n    if (ssid == network2) {\n      rssi2 = rssi;\n      found2 = true;\n      if (rssi &lt;= alarmThreshold) {\n        alarmActive = true;\n        lastAlarmTime = getTimeString();\n      }\n    }\n  }\n  WiFi.scanDelete();\n}\n\n<em>\/\/ === Display aktualisieren ===<\/em>\nvoid updateDisplay() {\n  u8g2.clearBuffer();\n\n  <em>\/\/ Kopfzeile<\/em>\n  u8g2.setFont(u8g2_font_6x10_tf);\n  u8g2.setCursor(0, 10);\n  u8g2.print(\"WLAN SIGNAL MONITOR\");\n  if (alarmActive) {\n    u8g2.setCursor(100, 10);\n    u8g2.print(\"ALARM!\");\n  }\n  u8g2.drawLine(0, 12, 128, 12);\n\n  <em>\/\/ ---- Netzwerk 1 ----<\/em>\n  u8g2.setFont(u8g2_font_7x13_tf);\n  u8g2.setCursor(0, 28);\n  String dn1 = network1;\n  if (dn1.length() &gt; 8) dn1 = dn1.substring(0, 8) + \".\";\n  u8g2.print(dn1 + \":\");\n\n  int bars1 = found1 ? getSignalBars(rssi1) : 0;\n  u8g2.setFont(u8g2_font_unifont_t_symbols);\n  u8g2.setCursor(0, 42);\n  u8g2.print(signalBars[bars1]);\n\n  u8g2.setFont(u8g2_font_6x10_tf);\n  u8g2.setCursor(0, 54);\n  if (found1) {\n    u8g2.print(getSignalQuality(rssi1));\n    if (rssi1 &lt;= alarmThreshold) {\n      u8g2.drawBox(0, 55, 128, 2);  <em>\/\/ Alarm\u2011Balken<\/em>\n    }\n  } else {\n    u8g2.print(\"NICHT GEFUNDEN\");\n  }\n\n  u8g2.drawLine(0, 56, 128, 56);  <em>\/\/ Trennlinie<\/em>\n\n  <em>\/\/ ---- Netzwerk 2 ----<\/em>\n  u8g2.setFont(u8g2_font_7x13_tf);\n  u8g2.setCursor(0, 64);\n  String dn2 = network2;\n  if (dn2.length() &gt; 8) dn2 = dn2.substring(0, 8) + \".\";\n  u8g2.print(dn2 + \":\");\n\n  <em>\/\/ F\u00fcr Netzwerk 2 zeigen wir nur einen kurzen Qualit\u00e4tstext (Platzmangel)<\/em>\n  u8g2.setFont(u8g2_font_6x10_tf);\n  u8g2.setCursor(60, 64);\n  if (found2) {\n    u8g2.print(getSignalQuality(rssi2));\n    if (rssi2 &lt;= alarmThreshold) {\n      u8g2.drawBox(60, 65, 68, 2);\n    }\n  } else {\n    u8g2.print(\"NICHT DA\");\n  }\n\n  u8g2.sendBuffer();\n}\n\n<em>\/\/ === Web\u2011Interface (HTML) ===<\/em>\nString getWebPage() {\n  String html = \"&lt;!DOCTYPE html&gt;&lt;html&gt;&lt;head&gt;\";\n  html += \"&lt;meta name='viewport' content='width=device-width, initial-scale=1'&gt;\";\n  html += \"&lt;meta charset='UTF-8'&gt;\";\n  html += \"&lt;title&gt;WLAN Monitor Einstellungen&lt;\/title&gt;\";\n  html += \"&lt;style&gt;\";\n  html += \"body { font-family: Arial, sans-serif; margin: 20px; background: #f0f0f0; }\";\n  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); }\";\n  html += \"h1 { color: #333; }\";\n  html += \"h2 { color: #555; border-bottom: 2px solid #4CAF50; padding-bottom: 5px; }\";\n  html += \"label { display: block; margin: 10px 0 5px; font-weight: bold; }\";\n  html += \"input, select { width: 100%; padding: 8px; margin-bottom: 15px; border: 1px solid #ddd; border-radius: 4px; }\";\n  html += \"button { background: #4CAF50; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; }\";\n  html += \"button:hover { background: #45a049; }\";\n  html += \".status { background: #e8f5e9; padding: 10px; border-radius: 5px; margin: 10px 0; }\";\n  html += \".alarm { background: #ffebee; padding: 10px; border-radius: 5px; margin: 10px 0; }\";\n  html += \"&lt;\/style&gt;\";\n  html += \"&lt;\/head&gt;&lt;body&gt;\";\n  html += \"&lt;div class='container'&gt;\";\n  html += \"&lt;h1&gt;\ud83d\udcf6 WLAN Monitor Einstellungen&lt;\/h1&gt;\";\n\n  <em>\/\/ Status<\/em>\n  html += \"&lt;div class='status'&gt;\";\n  html += \"&lt;h2&gt;Aktueller Status&lt;\/h2&gt;\";\n  html += \"&lt;p&gt;&lt;strong&gt;\" + network1 + \":&lt;\/strong&gt; \";\n  html += found1 ? String(rssi1) + \" dBm (\" + getSignalQuality(rssi1) + \")\" : \"Nicht gefunden\";\n  html += \"&lt;\/p&gt;\";\n  html += \"&lt;p&gt;&lt;strong&gt;\" + network2 + \":&lt;\/strong&gt; \";\n  html += found2 ? String(rssi2) + \" dBm (\" + getSignalQuality(rssi2) + \")\" : \"Nicht gefunden\";\n  html += \"&lt;\/p&gt;\";\n  html += \"&lt;p&gt;&lt;strong&gt;Letzter Scan:&lt;\/strong&gt; Vor \" + String((millis() - lastScan) \/ 1000) + \" Sekunden&lt;\/p&gt;\";\n  if (alarmActive) {\n    html += \"&lt;div class='alarm'&gt;\";\n    html += \"&lt;p&gt;&lt;strong&gt;\u26a0\ufe0f ALARM AKTIV!&lt;\/strong&gt; Signal unter \" + String(alarmThreshold) + \" dBm&lt;\/p&gt;\";\n    html += \"&lt;p&gt;Letzter Alarm: \" + lastAlarmTime + \"&lt;\/p&gt;\";\n    html += \"&lt;\/div&gt;\";\n  }\n  html += \"&lt;\/div&gt;\";\n\n  <em>\/\/ Formular<\/em>\n  html += \"&lt;h2&gt;Einstellungen&lt;\/h2&gt;\";\n  html += \"&lt;form action='\/save' method='POST'&gt;\";\n  html += \"&lt;label for='ssid1'&gt;Netzwerk 1 Name:&lt;\/label&gt;\";\n  html += \"&lt;input type='text' id='ssid1' name='ssid1' value='\" + network1 + \"' maxlength='31'&gt;\";\n  html += \"&lt;label for='ssid2'&gt;Netzwerk 2 Name:&lt;\/label&gt;\";\n  html += \"&lt;input type='text' id='ssid2' name='ssid2' value='\" + network2 + \"' maxlength='31'&gt;\";\n  html += \"&lt;label for='interval'&gt;Scan Intervall (Sekunden):&lt;\/label&gt;\";\n  html += \"&lt;input type='number' id='interval' name='interval' value='\" + String(scanInterval) + \"' min='5' max='300'&gt;\";\n  html += \"&lt;label for='threshold'&gt;Alarm Schwelle (dBm):&lt;\/label&gt;\";\n  html += \"&lt;select id='threshold' name='threshold'&gt;\";\n  String thresholds[] = {\"-70\", \"-75\", \"-80\", \"-85\", \"-90\"};\n  for (String t : thresholds) {\n    html += \"&lt;option value='\" + t + \"'\";\n    if (t.toInt() == alarmThreshold) html += \" selected\";\n    html += \"&gt;\" + t + \" dBm&lt;\/option&gt;\";\n  }\n  html += \"&lt;\/select&gt;\";\n  html += \"&lt;button type='submit'&gt;Einstellungen Speichern&lt;\/button&gt;\";\n  html += \"&lt;\/form&gt;\";\n\n  html += \"&lt;h2&gt;Steuerung&lt;\/h2&gt;\";\n  html += \"&lt;p&gt;&lt;a href='\/scan'&gt;&lt;button&gt;Sofort Scannen&lt;\/button&gt;&lt;\/a&gt;&lt;\/p&gt;\";\n  html += \"&lt;p&gt;&lt;a href='\/reboot'&gt;&lt;button&gt;Neustart&lt;\/button&gt;&lt;\/a&gt;&lt;\/p&gt;\";\n\n  html += \"&lt;h2&gt;System Info&lt;\/h2&gt;\";\n  html += \"&lt;p&gt;IP Adresse: \" + WiFi.softAPIP().toString() + \"&lt;\/p&gt;\";\n  html += \"&lt;p&gt;ESP8266 Chip ID: \" + String(ESP.getChipId()) + \"&lt;\/p&gt;\";\n  html += \"&lt;p&gt;Free Heap: \" + String(ESP.getFreeHeap()) + \" Bytes&lt;\/p&gt;\";\n\n  html += \"&lt;\/div&gt;&lt;\/body&gt;&lt;\/html&gt;\";\n  return html;\n}\n\n<em>\/\/ === Webserver\u2011Handler ===<\/em>\nvoid handleRoot() { server.send(200, \"text\/html\", getWebPage()); }\n\nvoid handleSave() {\n  if (server.hasArg(\"ssid1\")) network1 = server.arg(\"ssid1\");\n  if (server.hasArg(\"ssid2\")) network2 = server.arg(\"ssid2\");\n  if (server.hasArg(\"interval\")) scanInterval = server.arg(\"interval\").toInt();\n  if (server.hasArg(\"threshold\")) alarmThreshold = server.arg(\"threshold\").toInt();\n  saveSettings();\n\n  String html = \"&lt;!DOCTYPE html&gt;&lt;html&gt;&lt;head&gt;&lt;meta http-equiv='refresh' content='3;url=\/'&gt;\";\n  html += \"&lt;title&gt;Einstellungen gespeichert&lt;\/title&gt;&lt;\/head&gt;&lt;body&gt;\";\n  html += \"&lt;h1&gt;\u2705 Einstellungen gespeichert!&lt;\/h1&gt;&lt;p&gt;Weiterleitung in 3 Sekunden...&lt;\/p&gt;&lt;\/body&gt;&lt;\/html&gt;\";\n  server.send(200, \"text\/html\", html);\n}\n\nvoid handleScan() {\n  scanWifi();\n  server.sendHeader(\"Location\", \"\/\");\n  server.send(302, \"text\/plain\", \"Scan gestartet\");\n}\n\nvoid handleReboot() {\n  server.send(200, \"text\/html\", \"&lt;h1&gt;Neustart...&lt;\/h1&gt;\");\n  delay(1000);\n  ESP.restart();\n}\n\n<em>\/\/ === Setup und Loop ===<\/em>\nvoid setup() {\n  Serial.begin(115200);\n  Serial.println(\"\\n=== WLAN Monitor mit Webinterface ===\");\n\n  u8g2.begin();\n  u8g2.clearBuffer();\n  u8g2.setFont(u8g2_font_10x20_tf);\n  u8g2.setCursor(10, 30);\n  u8g2.print(\"WLAN\");\n  u8g2.setCursor(10, 55);\n  u8g2.print(\"MONITOR\");\n  u8g2.sendBuffer();\n  delay(2000);\n\n  loadSettings();\n\n  WiFi.mode(WIFI_AP);\n  String apName = \"WLAN-Monitor-\" + String(ESP.getChipId());\n  WiFi.softAP(apName.c_str());\n  Serial.println(\"Access Point gestartet: SSID = \" + apName + \", IP = \" + WiFi.softAPIP().toString());\n\n  server.on(\"\/\", handleRoot);\n  server.on(\"\/save\", handleSave);\n  server.on(\"\/scan\", handleScan);\n  server.on(\"\/reboot\", handleReboot);\n  server.begin();\n\n  scanWifi();\n  updateDisplay();\n  lastScan = millis();\n\n  Serial.println(\"Web Server bereit unter \" + WiFi.softAPIP().toString());\n}\n\nvoid loop() {\n  server.handleClient();\n\n  unsigned long now = millis();\n  if (now - lastScan &gt;= scanInterval * 1000UL) {\n    scanWifi();\n    updateDisplay();\n    lastScan = now;\n\n    Serial.print(\"Scan: \");\n    Serial.print(network1 + \": \" + (found1 ? String(rssi1) + \"dBm\" : \"nicht da\"));\n    Serial.print(\" | \" + network2 + \": \" + (found2 ? String(rssi2) + \"dBm\" : \"nicht da\"));\n    if (alarmActive) Serial.println(\" \u26a0\ufe0f ALARM!\");\n    else Serial.println();\n  }\n  delay(100);\n}<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Anpassungsm\u00f6glichkeiten und Erweiterungen<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Der Code ist bewusst modular gehalten, sodass er leicht erweitert werden kann:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Mehr als zwei Netzwerke<\/strong>\u00a0\u2013 durch einfaches Erweitern der Arrays und der Scan\u2011Logik lassen sich beliebig viele SSIDs \u00fcberwachen.<\/li>\n\n\n\n<li><strong>Alarm\u2011Ausgabe<\/strong>\u00a0\u2013 statt nur einer Display\u2011Meldung kann ein GPIO\u2011Pin angesteuert werden, um eine LED oder einen Summer zu schalten (z.\u202fB.\u00a0<code>digitalWrite(ALARM_PIN, HIGH)<\/code>).<\/li>\n\n\n\n<li><strong>Datenprotokollierung<\/strong>\u00a0\u2013 mit einer SD\u2011Karte oder \u00fcber MQTT k\u00f6nnen die Messwerte langfristig gespeichert und analysiert werden.<\/li>\n\n\n\n<li><strong>Energiesparmodus<\/strong>\u00a0\u2013 bei batteriebetriebenem Einsatz kann der ESP zwischen den Scans in den Deep\u2011Sleep versetzt werden.<\/li>\n\n\n\n<li><strong>Visuelles Feedback<\/strong>\u00a0\u2013 das Display l\u00e4sst sich durch Animationen oder Farbwechsel (bei farbigen Displays) noch aufwerten.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Der komplette Quellcode ist auch auf Plattformen wie GitHub verf\u00fcgbar, wo die Community st\u00e4ndig neue Features beisteuert.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Fazit zum praktischen Teil<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Dieser praxisnahe WLAN\u2011Monitor zeigt, wie einfach es heute ist, mit wenig Aufwand und geringen Kosten eine professionelle Signal\u00fcberwachung aufzubauen. Die Kombination aus ESP8266, OLED und Webserver er\u00f6ffnet eine F\u00fclle von Einsatzm\u00f6glichkeiten \u2013 sei es im privaten Heimnetz, im B\u00fcro oder in der industriellen Vorfertigung. Der mitgelieferte Code ist nicht nur funktionsf\u00e4hig, sondern auch eine hervorragende Basis f\u00fcr eigene Experimente und Weiterentwicklungen. Wer die Grundlagen verstanden hat, kann das System schnell an seine individuellen Anforderungen anpassen und so zum echten WLAN\u2011Profi werden.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Quellen (erg\u00e4nzt)<\/h2>\n\n\n\n<ol start=\"7\" class=\"wp-block-list\">\n<li>Arduino\u2011Bibliothek\u00a0<em>U8g2<\/em>\u00a0\u2013 Dokumentation und Beispiele:\u00a0<a href=\"https:\/\/github.com\/olikraus\/u8g2\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/olikraus\/u8g2<\/a><\/li>\n\n\n\n<li>ESP8266\u2011WLAN\u2011Bibliothek \u2013 offizielle ESP8266\u2011Kern\u2011Dokumentation:\u00a0<a href=\"https:\/\/arduino-esp8266.readthedocs.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/arduino-esp8266.readthedocs.io\/<\/a><\/li>\n<\/ol>\n","protected":false},"excerpt":{"rendered":"<p>Die Theorie ist das eine, die Praxis das andere. Um das Gelesene greifbar zu machen, zeigt dieser Abschnitt den vollst\u00e4ndigen Quellcode eines funktionsf\u00e4higen WLAN-Monitors. Das System basiert auf einem ESP8266, einem kosteng\u00fcnstigen Mikrocontroller mit integriertem WLAN-Modul, und einem 128\u00d764\u2011Pixel\u2011OLED\u2011Display (I2C\u2011SSD1306). Es scannt periodisch zwei konfigurierbare WLAN\u2011Netzwerke, zeigt die Signalst\u00e4rke in Balken und Text an, l\u00f6st [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[42,18,26,37],"tags":[9649,9645,2237,3406,9647,5078,9644,9648,9646,7819],"class_list":["post-6560","post","type-post","status-publish","format-standard","hentry","category-elektrotechnik","category-im-kopf-methoden-werkzeuge","category-mit-den-handen","category-wissenspeicher","tag-arduino-code","tag-dbm","tag-esp8266","tag-iot","tag-netzwerkdiagnose","tag-oled-display","tag-rssi","tag-signalueberwachung","tag-wlan-monitor","tag-wlan-signalstarke"],"_links":{"self":[{"href":"https:\/\/technodidact.de\/en\/wp-json\/wp\/v2\/posts\/6560","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/technodidact.de\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/technodidact.de\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/technodidact.de\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/technodidact.de\/en\/wp-json\/wp\/v2\/comments?post=6560"}],"version-history":[{"count":1,"href":"https:\/\/technodidact.de\/en\/wp-json\/wp\/v2\/posts\/6560\/revisions"}],"predecessor-version":[{"id":6561,"href":"https:\/\/technodidact.de\/en\/wp-json\/wp\/v2\/posts\/6560\/revisions\/6561"}],"wp:attachment":[{"href":"https:\/\/technodidact.de\/en\/wp-json\/wp\/v2\/media?parent=6560"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/technodidact.de\/en\/wp-json\/wp\/v2\/categories?post=6560"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/technodidact.de\/en\/wp-json\/wp\/v2\/tags?post=6560"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}