From e2c64da8d9b8f53550a209b9fd653717eddc3d4d Mon Sep 17 00:00:00 2001 From: Lucas Orth Date: Wed, 23 Sep 2026 15:33:47 +0200 Subject: [PATCH] esp/skrift_mosfet.ino aktualisiert --- esp/skrift_mosfet.ino | 218 +++++++++++++++++++++++++++++------------- 1 file changed, 152 insertions(+), 66 deletions(-) diff --git a/esp/skrift_mosfet.ino b/esp/skrift_mosfet.ino index 764e7a4..978c314 100644 --- a/esp/skrift_mosfet.ino +++ b/esp/skrift_mosfet.ino @@ -1,64 +1,67 @@ /* - * Skrift – ESP MOSFET-Trigger („Stromzufuhr aktivieren") - * =========================================================================== - * Kleiner WLAN-Empfänger für einen N-Kanal-MOSFET (Low-Side-Schalter). - * AutoRecover ruft bei jedem Plotter-Recover-Fenster den Endpunkt /trigger auf; - * der ESP schaltet dann den MOSFET (GPIO) und aktiviert damit die Stromzufuhr. - * - * Läuft auf ESP8266 (NodeMCU/Wemos D1 mini) UND ESP32 – die passenden Bibliotheken - * werden über die Board-Auswahl in der Arduino IDE automatisch gewählt. + * Skrift ESP32 MOSFET-Trigger ("Stromzufuhr aktivieren") + * ========================================================================= + * WLAN-Empfaenger fuer einen N-Kanal-MOSFET (Low-Side) oder ein Relaismodul. + * AutoRecover ruft /trigger auf, der ESP32 schaltet GPIO 23. * * Endpunkte (HTTP GET, Port 80): - * / Statusseite (Text) – Pin-Zustand, Uptime, IP - * /trigger MOSFET aktivieren (Puls von PULSE_MS ms; 0 = dauerhaft an) - * /on MOSFET dauerhaft AN - * /off MOSFET AUS - * Optionaler Schutz: ist AUTH_TOKEN gesetzt, muss jeder Aufruf ?key= - * mitschicken (muss zu esp_key in der AutoRecover-config.json passen). + * / Statusseite (Text) + * /status Status als JSON (fuer AutoRecover geeignet) + * /trigger Puls von PULSE_MS ms (0 = dauerhaft an / Latch) + * /on dauerhaft AN + * /off AUS + * Ist AUTH_TOKEN gesetzt, braucht jeder Aufruf ?key=. * - * Verdrahtung (Low-Side, N-Kanal-Logic-Level-MOSFET, z. B. IRLZ44N / AO3400-Modul): - * ESP GPIO (MOSFET_PIN) ─► Gate (ggf. 100–220 Ω in Reihe) - * Gate ─► 100 kΩ ─► GND (Pulldown, damit der MOSFET beim Booten sicher AUS ist) - * Last(+) an Versorgung+, Last(−) ─► Drain ; Source ─► GND (gemeinsame Masse!) - * GND von ESP und Last-Versorgung verbinden. - * ACTIVE_HIGH=true: GPIO HIGH = MOSFET leitet = „Strom an". + * Verdrahtung: GPIO 23 -> Gate (100 Ohm), Gate -> 100 kOhm -> GND, + * Source -> GND, Drain -> Minus der Last, Massen verbinden. */ -#if defined(ESP32) - #include - #include - WebServer server(80); -#else - #include - #include - ESP8266WebServer server(80); -#endif +#include +#include +#include -// ── Konfiguration ────────────────────────────────────────────────────────── -const char* WIFI_SSID = "DEIN_WLAN"; -const char* WIFI_PASS = "DEIN_WLAN_PASSWORT"; +// ===== Konfiguration ===================================================== +const char* WIFI_SSID = "Netz-Orth"; +const char* WIFI_PASS = "PLATZHALTER@2025"; -// GPIO, an dem das MOSFET-Gate hängt. ESP8266: 5 = D1 (Wemos/NodeMCU). ESP32: z. B. 23. -const int MOSFET_PIN = 5; -const bool ACTIVE_HIGH = true; // true: HIGH = MOSFET an ("Strom an") -const unsigned long PULSE_MS = 3000; // /trigger: Dauer der Aktivierung (0 = latch, bleibt an) +// GPIO 23 ist am ESP32 sicher: kein Strapping-Pin, kein Boot-Puls. +// Weitere sichere Pins: 13, 14, 16, 17, 18, 19, 21, 22, 25, 26, 27, 32, 33 +const int MOSFET_PIN = 23; +const bool ACTIVE_HIGH = true; // true: GPIO HIGH = Last an -// Optionaler Schutz. Leer = kein Schutz. Muss zu esp_key in der config.json passen. +const unsigned long PULSE_MS = 3000; // /trigger: Einschaltdauer, 0 = Latch +const unsigned long MAX_LATCH_MS = 600000; // Fail-safe: Latch nach 10 min beenden + +// Leer = kein Schutz. Muss zu esp_key in der AutoRecover-config.json passen. const char* AUTH_TOKEN = ""; -// Optionale feste IP (empfohlen, damit AutoRecover eine stabile Adresse hat). -// Auf true setzen und Werte anpassen; sonst DHCP. -const bool USE_STATIC_IP = false; -IPAddress IP_ADDR (192, 168, 2, 50); -IPAddress IP_GATE (192, 168, 2, 1); -IPAddress IP_MASK (255, 255, 255, 0); -IPAddress IP_DNS (192, 168, 2, 1); +// Feste IP empfohlen, damit AutoRecover eine stabile Adresse hat. +const bool USE_STATIC_IP = false; +IPAddress IP_ADDR (192, 168, 178, 152); +IPAddress IP_GATE (192, 168, 178, 1); +IPAddress IP_MASK (255, 255, 255, 0); +IPAddress IP_DNS (192, 168, 178, 1); + +// Erreichbar als http://skrift-trigger.local/ (Windows braucht Bonjour). +const bool USE_MDNS = true; +const char* MDNS_NAME = "skrift-trigger"; + +const unsigned long WIFI_BOOT_TIMEOUT_MS = 20000; // Erstverbindung +const unsigned long WIFI_CHECK_MS = 5000; // Reconnect-Intervall +const unsigned long WIFI_GIVEUP_MS = 600000; // 10 min offline -> Neustart + +// ===== Zustand =========================================================== +WebServer server(80); -// ── Zustand ──────────────────────────────────────────────────────────────── bool mosfetAn = false; bool pulsAktiv = false; unsigned long pulsBis = 0; +unsigned long letzterRequest = 0; // fuer Latch-Fail-safe +unsigned long triggerZaehler = 0; +unsigned long letzterTriggerS = 0; // Uptime-Sekunden des letzten Triggers +unsigned long offlineSeit = 0; // 0 = online +// ===== Hilfsfunktionen =================================================== void mosfetSchreiben(bool an) { mosfetAn = an; digitalWrite(MOSFET_PIN, (an == ACTIVE_HIGH) ? HIGH : LOW); @@ -71,45 +74,108 @@ bool autorisiert() { void sendeStatus(const char* hinweis) { String s = String(hinweis) + "\n"; - s += "mosfet="; s += (mosfetAn ? "an" : "aus"); s += "\n"; - s += "puls="; s += (pulsAktiv ? "aktiv" : "-"); s += "\n"; - s += "ip="; s += WiFi.localIP().toString(); s += "\n"; - s += "uptime_s="; s += String(millis() / 1000); s += "\n"; + s += "mosfet="; s += (mosfetAn ? "an" : "aus"); s += "\n"; + s += "puls="; s += (pulsAktiv ? "aktiv" : "-"); s += "\n"; + s += "trigger_count="; s += String(triggerZaehler); s += "\n"; + s += "letzter_trigger_s="; s += String(letzterTriggerS); s += "\n"; + s += "wlan="; s += (WiFi.status() == WL_CONNECTED ? "ok" : "weg"); s += "\n"; + s += "rssi="; s += String(WiFi.RSSI()); s += "\n"; + s += "ip="; s += WiFi.localIP().toString(); s += "\n"; + s += "uptime_s="; s += String(millis() / 1000); s += "\n"; server.send(200, "text/plain; charset=utf-8", s); } -void handleRoot() { sendeStatus("Skrift ESP MOSFET-Trigger"); } +void sendeJson() { + String j = "{"; + j += "\"mosfet\":"; j += (mosfetAn ? "true" : "false"); + j += ",\"puls\":"; j += (pulsAktiv ? "true" : "false"); + j += ",\"trigger_count\":"; j += String(triggerZaehler); + j += ",\"letzter_trigger_s\":";j += String(letzterTriggerS); + j += ",\"rssi\":"; j += String(WiFi.RSSI()); + j += ",\"uptime_s\":"; j += String(millis() / 1000); + j += "}"; + server.send(200, "application/json", j); +} + +// ===== Handler =========================================================== +void handleRoot() { sendeStatus("Skrift ESP32 MOSFET-Trigger"); } +void handleStatus() { sendeJson(); } void handleTrigger() { if (!autorisiert()) { server.send(403, "text/plain", "verboten\n"); return; } + letzterRequest = millis(); + triggerZaehler++; + letzterTriggerS = millis() / 1000; mosfetSchreiben(true); - if (PULSE_MS > 0) { // getimter Puls + if (PULSE_MS > 0) { // getimter Puls pulsAktiv = true; pulsBis = millis() + PULSE_MS; sendeStatus("OK trigger (puls)"); - } else { // dauerhaft an (latch) + } else { // dauerhaft an pulsAktiv = false; sendeStatus("OK trigger (latch)"); } } -void handleOn() { if (!autorisiert()) { server.send(403, "text/plain", "verboten\n"); return; } pulsAktiv = false; mosfetSchreiben(true); sendeStatus("OK on"); } -void handleOff() { if (!autorisiert()) { server.send(403, "text/plain", "verboten\n"); return; } pulsAktiv = false; mosfetSchreiben(false); sendeStatus("OK off"); } +void handleOn() { + if (!autorisiert()) { server.send(403, "text/plain", "verboten\n"); return; } + letzterRequest = millis(); + pulsAktiv = false; + mosfetSchreiben(true); + sendeStatus("OK on"); +} +void handleOff() { + if (!autorisiert()) { server.send(403, "text/plain", "verboten\n"); return; } + letzterRequest = millis(); + pulsAktiv = false; + mosfetSchreiben(false); + sendeStatus("OK off"); +} + +// ===== WLAN ============================================================== +void wlanStarten() { + if (USE_STATIC_IP) WiFi.config(IP_ADDR, IP_GATE, IP_MASK, IP_DNS); + WiFi.persistent(false); // keine NVS-Schreibzyklen bei jedem begin() + WiFi.mode(WIFI_STA); + WiFi.setSleep(false); // WLAN dauerhaft aktiv, kein Modem-Sleep + WiFi.setAutoReconnect(true); // Treiber verbindet selbst nach + WiFi.begin(WIFI_SSID, WIFI_PASS); +} + +// ===== Setup ============================================================= void setup() { Serial.begin(115200); - pinMode(MOSFET_PIN, OUTPUT); - mosfetSchreiben(false); // sicher AUS beim Start + delay(50); - if (USE_STATIC_IP) WiFi.config(IP_ADDR, IP_GATE, IP_MASK, IP_DNS); - WiFi.mode(WIFI_STA); - WiFi.begin(WIFI_SSID, WIFI_PASS); + pinMode(MOSFET_PIN, OUTPUT); + mosfetSchreiben(false); // sicher AUS beim Start + + wlanStarten(); Serial.print("Verbinde WLAN"); - while (WiFi.status() != WL_CONNECTED) { delay(400); Serial.print("."); } + unsigned long start = millis(); + while (WiFi.status() != WL_CONNECTED && millis() - start < WIFI_BOOT_TIMEOUT_MS) { + delay(300); + Serial.print("."); + } Serial.println(); - Serial.print("Verbunden. IP: "); Serial.println(WiFi.localIP()); + + if (WiFi.status() == WL_CONNECTED) { + Serial.print("Verbunden. IP: "); Serial.println(WiFi.localIP()); + Serial.print("RSSI: "); Serial.println(WiFi.RSSI()); + } else { + // Kein Bootloop: Server startet trotzdem, loop versucht weiter. + Serial.println("WLAN noch nicht da, versuche im Hintergrund weiter."); + offlineSeit = millis(); + } + + if (USE_MDNS && MDNS.begin(MDNS_NAME)) { + MDNS.addService("http", "tcp", 80); + Serial.printf("Erreichbar als http://%s.local/\n", MDNS_NAME); + } server.on("/", handleRoot); + server.on("/status", handleStatus); server.on("/trigger", handleTrigger); server.on("/on", handleOn); server.on("/off", handleOff); @@ -118,19 +184,39 @@ void setup() { Serial.println("HTTP-Server auf Port 80 gestartet."); } +// ===== Loop ============================================================== void loop() { server.handleClient(); - // WLAN bei Bedarf neu verbinden (Plotter läuft lange). + // WLAN ueberwachen, Reconnect statt erneutem begin(). static unsigned long letzteWlanPruefung = 0; - if (millis() - letzteWlanPruefung > 10000) { + if (millis() - letzteWlanPruefung > WIFI_CHECK_MS) { letzteWlanPruefung = millis(); - if (WiFi.status() != WL_CONNECTED) WiFi.begin(WIFI_SSID, WIFI_PASS); + if (WiFi.status() == WL_CONNECTED) { + offlineSeit = 0; + } else { + if (offlineSeit == 0) offlineSeit = millis(); + WiFi.reconnect(); + // Dauerhaft offline: einmal sauber neu starten. + if (millis() - offlineSeit > WIFI_GIVEUP_MS) { + Serial.println("WLAN dauerhaft weg, Neustart."); + mosfetSchreiben(false); // Last vor dem Reset abschalten + delay(100); + ESP.restart(); + } + } } - // Getimten Puls beenden. + // Getimten Puls beenden (rollover-sicher). if (pulsAktiv && (long)(millis() - pulsBis) >= 0) { pulsAktiv = false; mosfetSchreiben(false); } -} + + // Fail-safe: Latch beenden, wenn AutoRecover sich nicht mehr meldet. + if (mosfetAn && !pulsAktiv && MAX_LATCH_MS > 0 + && millis() - letzterRequest > MAX_LATCH_MS) { + Serial.println("Latch-Timeout, schalte aus."); + mosfetSchreiben(false); + } +} \ No newline at end of file