Files
skrift-auto-recover/esp/skrift_mosfet.ino

222 lines
7.9 KiB
C++

/*
* 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)
* /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=<token>.
*
* Verdrahtung: GPIO 23 -> Gate (100 Ohm), Gate -> 100 kOhm -> GND,
* Source -> GND, Drain -> Minus der Last, Massen verbinden.
*/
#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>
// ===== Konfiguration =====================================================
const char* WIFI_SSID = "Netz-Orth";
const char* WIFI_PASS = "PLATZHALTER@2025";
// 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
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 = "";
// 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);
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);
}
bool autorisiert() {
if (strlen(AUTH_TOKEN) == 0) return true;
return server.hasArg("key") && server.arg("key") == AUTH_TOKEN;
}
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 += "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 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
pulsAktiv = true;
pulsBis = millis() + PULSE_MS;
sendeStatus("OK trigger (puls)");
} else { // dauerhaft an
pulsAktiv = false;
sendeStatus("OK trigger (latch)");
}
}
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);
delay(50);
pinMode(MOSFET_PIN, OUTPUT);
mosfetSchreiben(false); // sicher AUS beim Start
wlanStarten();
Serial.print("Verbinde WLAN");
unsigned long start = millis();
while (WiFi.status() != WL_CONNECTED && millis() - start < WIFI_BOOT_TIMEOUT_MS) {
delay(300);
Serial.print(".");
}
Serial.println();
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);
server.onNotFound([]() { server.send(404, "text/plain", "nicht gefunden\n"); });
server.begin();
Serial.println("HTTP-Server auf Port 80 gestartet.");
}
// ===== Loop ==============================================================
void loop() {
server.handleClient();
// WLAN ueberwachen, Reconnect statt erneutem begin().
static unsigned long letzteWlanPruefung = 0;
if (millis() - letzteWlanPruefung > WIFI_CHECK_MS) {
letzteWlanPruefung = millis();
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 (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);
}
}