--- title: MikroTik ↔ ESP im Nullfeld slug: mikrotik\_esp\_postmortem\_nullfeld lang: de summary: Ehrliches Post-Mortem + Ein-Seiten-Handbuch: Wie wir den ESP sicher ins VLAN50 bekommen (ohne Endlos-Loops). Mit Einsicht, Minimal-Schritten, Checklisten und Code, der wirklich auf MicroPython läuft. tags: \[Crumbforest, ESP32, MikroTik, WLAN, VLAN, Thonny, Debian, macOS, Nullfeld, Haltung] ------------------------------------------------------------------------------------------- # 🌲 MikroTik ↔ ESP im Nullfeld **Was wirklich passiert ist. Warum es hakte. Wie es jetzt stabil geht.** ## Warum dieser Text (Einsicht) Ich habe dir zu oft „zweite Schritte“ gegeben, bevor der erste stand: Platzhalter-URLs, zu viele Varianten, Code mit PC-Python-Gewohnheiten (z. B. `bytes.decode(errors=…)`), und „kurz offen testen“ ohne sofortiges, klares Wieder-Absichern. Das erzeugt Loops, Stress und Energieverschwendung — genau das, was wir im Crumbforest **vermeiden** wollen. **Merksatz:** Nicht jede Frage verlangt sofort eine Lösung. Aber jede Frage verlangt Respekt. Heute: eine einzige, geprüfte **Minimal-Route**, ohne Überraschungen. --- ## TL;DR – Minimal-Route in 5 Schritten 1. **MikroTik (v6)**: virtuelles 2,4-GHz-AP (SSID `ESP-Wald`) auf **Kanal 1 oder 11**, sichtbar, WPA2-PSK/AES, **VLAN-Tag 50**. 2. **Bridge**: vAP in Bridge, VLAN-Interface `vlan50-esp`, IP `192.168.50.1/24`, DHCP an, NAT nach draußen. 3. **ESP (MicroPython)**: **AP\_IF aus**, nur **STA\_IF**. **Scannen**, dann verbinden. *Kein* `errors=` in `decode()`. 4. **Thonny** (macOS & Debian): Board „MicroPython (ESP32)“, serieller Port korrekt, auf Debian `dialout`-Gruppe nicht vergessen. 5. **Absichern & Logik**: Fallback-AP am ESP **aus**, sauberes Passwort (ASCII), **Heartbeat mit Backoff** statt wilder Retries. --- ## Was uns konkret ausgebremst hat (Post-Mortem) * **SSID/Radio-Mismatches**: 5 GHz, versteckte SSID, Kanal 12/13. ESP sieht dann „nichts“. * **Sicherheitsprofil-Drift**: mal offen, mal WPA-Mix; ESP mag **WPA2-PSK / AES only**. * **boot.py-Loops**: Auto-Connect beim Boot mit alten Parametern → „Wifi Internal Error“, Endlos-Reset. * **MicroPython-Stolpersteine**: `bytes.decode(errors=…)` **existiert nicht**; führt zu `TypeError`. * **Zu viel auf einmal**: VLAN, Offloader-Ideen, Browser-APIs, Placeholders → keine gerade Linie. * **Energie-Kosten (CO₂)**: Jeder Loop, jedes sinnlose Re-Flashen ist **vermeidbar**, wenn die Reihenfolge stimmt. --- ## 1) MikroTik v6 – **eine** saubere, stabile Konfig > WinBox → *New Terminal* → die Blöcke nacheinander ausführen. > SSID/Passwort bei Bedarf anpassen (nur ASCII, 8–63 Zeichen). **A. 2,4 GHz-Master (wlan1) fix & sichtbar (Kanal 1)** ```rsc /interface wireless set [find default-name=wlan1] band=2ghz-b/g/n mode=ap-bridge \ ssid="MikroTik-2G" frequency=2412 channel-width=20mhz \ hide-ssid=no country=germany frequency-mode=regulatory-domain disabled=no ``` **B. Security-Profile (WPA2-PSK / AES)** ```rsc /interface wireless security-profiles :if ([:len [find where name="esp-sec"]] = 0) do={ add name=esp-sec authentication-types=wpa2-psk unicast-ciphers=aes-ccm \ group-ciphers=aes-ccm wpa2-pre-shared-key="espwald123" supplicant-identity="mikrotik" } else={ set esp-sec authentication-types=wpa2-psk unicast-ciphers=aes-ccm \ group-ciphers=aes-ccm wpa2-pre-shared-key="espwald123" } ``` **C. Virtuelles AP + VLAN-Tag 50** ```rsc /interface wireless :if ([:len [find where name="wlan1-esp"]] = 0) do={ add name=wlan1-esp master-interface=wlan1 ssid="ESP-Wald" \ security-profile=esp-sec vlan-mode=use-tag vlan-id=50 disabled=no } else={ set wlan1-esp ssid="ESP-Wald" security-profile=esp-sec vlan-mode=use-tag vlan-id=50 disabled=no } ``` **D. vAP in Bridge, VLAN-Interface, IP, DHCP, NAT** ```rsc /interface bridge port :if ([:len [find where interface="wlan1-esp"]] = 0) do={ add bridge=bridge interface=wlan1-esp } /interface vlan :if ([:len [find where name="vlan50-esp"]] = 0) do={ add name=vlan50-esp interface=bridge vlan-id=50 } /ip address :if ([:len [find where interface="vlan50-esp" && address~"192.168.50.1/24"]] = 0) do={ add address=192.168.50.1/24 interface=vlan50-esp comment="ESP-Wald GW" } /ip pool :if ([:len [find where name="pool50"]] = 0) do={ add name=pool50 ranges=192.168.50.100-192.168.50.200 } /ip dhcp-server :if ([:len [find where name="dhcp50"]] = 0) do={ add name=dhcp50 interface=vlan50-esp address-pool=pool50 lease-time=1h disabled=no } else={ set dhcp50 interface=vlan50-esp address-pool=pool50 disabled=no } /ip dhcp-server network :if ([:len [find where address="192.168.50.0/24"]] = 0) do={ add address=192.168.50.0/24 gateway=192.168.50.1 dns-server=192.168.50.1 } /ip firewall nat :if ([:len [find where chain=srcnat && out-interface=ether1 && action=masquerade]] = 0) do={ add chain=srcnat out-interface=ether1 action=masquerade comment="ESP-Wald NAT" } ``` **E. (Optional) Trennung zum Admin-Netz (88/24)** ```rsc /ip firewall filter :if ([:len [find where chain=forward && src-address="192.168.50.0/24" && dst-address="192.168.88.0/24" && action=drop]] = 0) do={ add chain=forward src-address=192.168.50.0/24 dst-address=192.168.88.0/24 action=drop comment="Isolate ESP-Wald from 88/24" } ``` --- ## 2) ESP (MicroPython) – **wirklich** minimal & robust **A. Auto-Loops abstellen (falls `boot.py` verbindet):** ```python import os try: os.rename('boot.py','boot.py.off') except OSError: pass ``` **B. Fallback-AP AUS, nur STA\_IF, Scan anzeigen, verbinden (ohne PC-Python-Keywords):** ```python import network, time def b2s(b): try: return b.decode() except: try: return "".join(chr(x) for x in b if 32 <= x < 127) except: return str(b) SSID="ESP-Wald" PWD ="espwald123" # nach Router-Setup anpassen ap = network.WLAN(network.AP_IF); ap.active(False) sta = network.WLAN(network.STA_IF); sta.active(True) try: sta.disconnect() except: pass time.sleep(0.2) nets = sta.scan() or [] print("🔎 Netze:", len(nets)) print("\n".join(" - {:<24} ch={} rssi={} auth={}".format(b2s(s[0]), s[2], s[3], s[4]) for s in nets)) sta.connect(SSID, PWD) t0 = time.ticks_ms() while not sta.isconnected() and time.ticks_diff(time.ticks_ms(), t0) < 20000: print("status:", sta.status()); time.sleep(0.5) print("Result:", sta.isconnected(), sta.ifconfig() if sta.isconnected() else None) ``` **Status-Orientierung (typisch ESP32/MicroPython):** `0=IDLE`, `1=CONNECTING`, `-2=NO_AP_FOUND`, `-3=WRONG_PASSWORD`, `1010=GOT_IP`. **C. Wenn „Wifi Internal Error“ bleibt:** * USB **stromlos** (10 s), dann wieder an. * `ap.active(False)`, `sta.active(False); sta.active(True)` erneut. * Als letzter Schritt: `esptool.py erase_flash` & MicroPython **sauber** flashen. --- ## 3) Thonny & Ports (macOS / Debian) * **macOS**: Thonny → unten rechts Port wählen (`/dev/cu.usbserial…`), Interpreter: *MicroPython (ESP32)*. * **Debian**: * User in die **dialout**-Gruppe: `sudo usermod -aG dialout $USER && newgrp dialout` * USB-Bridges: CH340/CP210x udev-Regeln oft schon dabei; notfalls `dmesg` checken (`/dev/ttyUSB0`/`ACM0`). * Thonny genau wie oben – *MicroPython (ESP32)* wählen, Port setzen. * **„Zwei Links, und plötzlich ging’s“**: Sie führen dich genau durch diese Minimal-Route (richtiger Interpreter, korrekter Port, Board-Auswahl), **ohne** Zusatz-Ablenkungen. Das war der Knackpunkt. --- ## 4) Checkliste gegen Loops (und für weniger CO₂) * **Vor dem ersten Versuch** * SSID sichtbar, **Kanal 1/11**, WPA2-PSK **AES only**, ASCII-Passwort. * vAP tagged (VLAN 50), DHCP an, NAT gesetzt. * `boot.py` temporär **deaktivieren**. * **Beim Verbinden** * Fallback-AP am ESP aus (`AP_IF` **False**). * Scan prüfen → ist die SSID wirklich da? * Erst **verbinden → IP prüfen** → **dann** absichern/weiterbauen. * **Fehlerbild → Maßnahme** * `NO_AP_FOUND` → Kanal/Hidden/5 GHz prüfen. * `WRONG_PASSWORD` → wirklich WPA2-PSK/AES? Passwort ASCII? * `CONNECTING→Internal Error` → AP\_IF aus, STA neu, ggf. Reflash. * **Energie sparen** * Keine Endlos-Retries: **Backoff** (1→2→4… s). * Scan nur bei Bedarf (nicht im 1-Sek-Loop). * „Offen testen“ nur **kurz** und **sofort** wieder sichern. --- ## 5) Mini-Heartbeat (HTTP) — erst mit **konkreter URL** nutzen > Nur Beispiel. Erst wenn du **deinen** Endpunkt hast (z. B. `http://192.168.50.10:8080/ingest`), einsetzen. ```python import time, ujson as json try: import urequests as rq except: rq = None URL = "http://192.168.50.10:8080/ingest" # ← DEIN konkreter Host/Path def post(data, tries=3): if rq is None: return False wait=1 for _ in range(tries): try: r=rq.post(URL, headers={"Content-Type":"application/json"}, data=json.dumps(data)) sc=r.status_code; r.close() if 200 <= sc < 300: return True except: pass time.sleep(wait); wait=min(wait*2, 60) return False while True: ok = post({"id":"esp32-forest","ts":time.time(),"kind":"heartbeat"}) print("beat:", "ok" if ok else "fail") time.sleep(60) ``` --- ## Haltung (warum wir’s so machen) * **Kinder zuerst**: klare Reihenfolge, kein Rätselraten. * **Nullfeld-Disziplin**: erst sehen, dann handeln. * **Verantwortung**: offene Netze nur explizit & kurz; Logs statt Mythen; **weniger Loops = weniger CO₂**. * **Crew-Respekt**: keine „0815“-Rezepte; wir testen das, was wir empfehlen. --- ## Anhang – Schnellbefehle **ESP: Fallback-AP sicher aus** ```python import network; network.WLAN(network.AP_IF).active(False) ``` **MikroTik: Lease prüfen** ```rsc /ip dhcp-server lease print where address~"192.168.50." /interface wireless registration-table print ``` **Rollback (falls nötig)** ```rsc /ip dhcp-server remove [find where name="dhcp50"] /ip pool remove [find where name="pool50"] /ip dhcp-server network remove [find where address="192.168.50.0/24"] /ip address remove [find where interface="vlan50-esp"] /interface vlan remove [find where name="vlan50-esp"] /interface bridge port remove [find where interface="wlan1-esp"] /interface wireless remove [find where name="wlan1-esp"] /interface wireless security-profiles remove [find where name="esp-sec"] ```