AutoRecover: bei Recover-Fenster ESP-MOSFET triggern („Stromzufuhr aktivieren")
Erkennt AutoRecover das Plotter-Recover-Fenster, ruft es jetzt zuerst einen ESP auf (HTTP /trigger) und klickt danach „Recover". Der ESP schaltet einen MOSFET und aktiviert damit die Stromzufuhr. Konfiguration über config.json (esp_url/esp_path/esp_key/esp_timeout) bzw. Env-Vars; ohne esp_url unverändertes Verhalten. Neu: esp/skrift_mosfet.ino (ESP8266/ESP32-Firmware, WLAN + HTTP-Server, Endpunkte /trigger /on /off, getimter Puls oder Latch) + esp/README.md (Verdrahtung/Flashen) + config.example.json. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -47,6 +47,61 @@ def _lade_directus_cfg():
|
||||
_DX_URL, _DX_TOKEN, _DX_MID = _lade_directus_cfg()
|
||||
|
||||
|
||||
def _lade_esp_cfg():
|
||||
"""ESP-Zugang (MOSFET-Trigger) aus config.json bzw. Env-Variablen.
|
||||
|
||||
Erwartete Schlüssel in der config.json (neben diesem Skript oder beim Agenten):
|
||||
"esp_url": "http://192.168.2.50" Basis-URL des ESP (Pflicht, sonst aus)
|
||||
"esp_path": "/trigger" Endpunkt (Default /trigger)
|
||||
"esp_key": "geheim" optionaler Schutz-Schlüssel (?key=)
|
||||
"esp_timeout": 5 Sekunden (Default 5)
|
||||
"""
|
||||
url = path = key = None
|
||||
timeout = 5
|
||||
for pfad in (os.path.join(_HIER, "config.json"),
|
||||
os.path.join(_HIER, "..", "skrift-agent", "config.json")):
|
||||
try:
|
||||
with open(pfad, "r", encoding="utf-8") as f:
|
||||
c = json.load(f)
|
||||
if c.get("esp_url"):
|
||||
url = c.get("esp_url")
|
||||
path = c.get("esp_path")
|
||||
key = c.get("esp_key")
|
||||
timeout = c.get("esp_timeout", 5)
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
url = url or os.environ.get("SKRIFT_ESP_URL")
|
||||
path = path or os.environ.get("SKRIFT_ESP_PATH") or "/trigger"
|
||||
key = key or os.environ.get("SKRIFT_ESP_KEY") or ""
|
||||
try:
|
||||
timeout = float(timeout)
|
||||
except Exception:
|
||||
timeout = 5.0
|
||||
return (url.rstrip("/") if url else None), path, key, timeout
|
||||
|
||||
|
||||
_ESP_URL, _ESP_PATH, _ESP_KEY, _ESP_TIMEOUT = _lade_esp_cfg()
|
||||
|
||||
|
||||
def esp_trigger():
|
||||
"""Sagt dem ESP „Stromzufuhr aktivieren" (schaltet den MOSFET). Nie fatal.
|
||||
|
||||
Wird bei jedem erkannten Recover-Fenster aufgerufen. Ohne konfigurierte
|
||||
esp_url passiert nichts (AutoRecover läuft normal weiter).
|
||||
"""
|
||||
if not (requests and _ESP_URL):
|
||||
return
|
||||
try:
|
||||
params = {"key": _ESP_KEY} if _ESP_KEY else None
|
||||
r = requests.get(_ESP_URL + _ESP_PATH, params=params, timeout=_ESP_TIMEOUT)
|
||||
print(f" → ESP getriggert ({_ESP_URL}{_ESP_PATH}): HTTP {r.status_code}")
|
||||
dx_log("info", f"ESP-Stromzufuhr aktiviert (HTTP {r.status_code}).")
|
||||
except Exception as e:
|
||||
print(f" → ESP-Trigger fehlgeschlagen: {e}")
|
||||
dx_log("warn", f"ESP-Trigger fehlgeschlagen: {e}")
|
||||
|
||||
|
||||
def dx_log(level, message):
|
||||
"""Meldet ein Ereignis an Directus (agent_logs). Fehler nie fatal."""
|
||||
if not (requests and _DX_URL and _DX_TOKEN):
|
||||
@@ -122,6 +177,9 @@ def main():
|
||||
print(f"[Versuch {versuche}/{MAX_VERSUCHE}] Fehlerfenster gefunden!")
|
||||
dx_log("error", f"Plotter-Fehlerfenster erkannt (Versuch {versuche}/{MAX_VERSUCHE}): {meldung}")
|
||||
|
||||
# ESP zuerst: MOSFET/„Stromzufuhr aktivieren", dann Recover klicken.
|
||||
esp_trigger()
|
||||
|
||||
time.sleep(0.5)
|
||||
click_recover_button(hwnd)
|
||||
|
||||
|
||||
12
config.example.json
Normal file
12
config.example.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"_hinweis": "Kopie als config.json speichern und Werte anpassen. Directus-Felder sind optional (nur fürs Logging); ohne esp_url wird kein ESP getriggert.",
|
||||
|
||||
"directus_url": "https://directus.example.de",
|
||||
"directus_token": "",
|
||||
"machine_id": 1,
|
||||
|
||||
"esp_url": "http://192.168.2.50",
|
||||
"esp_path": "/trigger",
|
||||
"esp_key": "",
|
||||
"esp_timeout": 5
|
||||
}
|
||||
65
esp/README.md
Normal file
65
esp/README.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Skrift ESP MOSFET-Trigger
|
||||
|
||||
Kleiner WLAN-„Schalter": Wenn am Plotter das **Recover-Fenster** erscheint, ruft
|
||||
AutoRecover den ESP auf (`/trigger`), der einen **MOSFET** schaltet und damit die
|
||||
**Stromzufuhr aktiviert**.
|
||||
|
||||
## Hardware
|
||||
|
||||
- ESP8266 (NodeMCU / Wemos D1 mini) **oder** ESP32.
|
||||
- N-Kanal-Logic-Level-MOSFET als **Low-Side-Schalter** (z. B. IRLZ44N, oder ein
|
||||
fertiges AO3400-/IRF520-Modul).
|
||||
|
||||
### Verdrahtung (Low-Side)
|
||||
|
||||
```
|
||||
ESP GPIO (MOSFET_PIN) ──[100–220 Ω]── Gate
|
||||
Gate ──[100 kΩ]── GND (Pulldown: MOSFET beim Booten sicher AUS)
|
||||
Last(+) ── Versorgung+
|
||||
Last(−) ── Drain
|
||||
Source ── GND
|
||||
GND(ESP) ── GND(Versorgung) (gemeinsame Masse ist Pflicht!)
|
||||
```
|
||||
|
||||
`ACTIVE_HIGH = true`: GPIO **HIGH = MOSFET leitet = Strom an**.
|
||||
Bei einem fertigen Modul mit „VCC/GND/SIG": SIG an `MOSFET_PIN`, GND gemeinsam.
|
||||
|
||||
## Firmware flashen
|
||||
|
||||
1. Arduino IDE, passendes Board wählen (ESP8266 **oder** ESP32 – der Sketch
|
||||
erkennt beides automatisch).
|
||||
2. In `skrift_mosfet.ino` oben anpassen:
|
||||
- `WIFI_SSID` / `WIFI_PASS`
|
||||
- `MOSFET_PIN` (ESP8266: `5` = D1; ESP32: z. B. `23`)
|
||||
- `PULSE_MS` – wie lange „Strom an" bleibt (Default 3000 ms; `0` = bleibt an bis `/off`)
|
||||
- optional `AUTH_TOKEN` (dann in der config.json `esp_key` gleich setzen)
|
||||
- optional feste IP (`USE_STATIC_IP = true`) – **empfohlen**, damit AutoRecover
|
||||
eine stabile Adresse hat.
|
||||
3. Hochladen, seriellen Monitor (115200) öffnen → zeigt die IP.
|
||||
|
||||
## Endpunkte (HTTP GET, Port 80)
|
||||
|
||||
| Pfad | Wirkung |
|
||||
|------------|-----------------------------------------------------|
|
||||
| `/` | Statusseite (Text) |
|
||||
| `/trigger` | MOSFET aktivieren (Puls `PULSE_MS`; `0` = dauerhaft) |
|
||||
| `/on` | MOSFET dauerhaft AN |
|
||||
| `/off` | MOSFET AUS |
|
||||
|
||||
Test im Browser: `http://<ESP-IP>/trigger`
|
||||
|
||||
## AutoRecover verbinden
|
||||
|
||||
In `Produktion/AutoRecover/config.json` (siehe `config.example.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"esp_url": "http://192.168.2.50",
|
||||
"esp_path": "/trigger",
|
||||
"esp_key": "",
|
||||
"esp_timeout": 5
|
||||
}
|
||||
```
|
||||
|
||||
Ab dann triggert AutoRecover bei **jedem** erkannten Recover-Fenster zuerst den
|
||||
ESP und klickt danach „Recover". Ohne `esp_url` bleibt alles beim Alten.
|
||||
136
esp/skrift_mosfet.ino
Normal file
136
esp/skrift_mosfet.ino
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* 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=<token>
|
||||
* mitschicken (muss zu esp_key in der AutoRecover-config.json passen).
|
||||
*
|
||||
* 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".
|
||||
*/
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <WiFi.h>
|
||||
#include <WebServer.h>
|
||||
WebServer server(80);
|
||||
#else
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
ESP8266WebServer server(80);
|
||||
#endif
|
||||
|
||||
// ── Konfiguration ──────────────────────────────────────────────────────────
|
||||
const char* WIFI_SSID = "DEIN_WLAN";
|
||||
const char* WIFI_PASS = "DEIN_WLAN_PASSWORT";
|
||||
|
||||
// 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)
|
||||
|
||||
// Optionaler Schutz. Leer = kein Schutz. Muss zu esp_key in der 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);
|
||||
|
||||
// ── Zustand ────────────────────────────────────────────────────────────────
|
||||
bool mosfetAn = false;
|
||||
bool pulsAktiv = false;
|
||||
unsigned long pulsBis = 0;
|
||||
|
||||
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 += "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 handleTrigger() {
|
||||
if (!autorisiert()) { server.send(403, "text/plain", "verboten\n"); return; }
|
||||
mosfetSchreiben(true);
|
||||
if (PULSE_MS > 0) { // getimter Puls
|
||||
pulsAktiv = true;
|
||||
pulsBis = millis() + PULSE_MS;
|
||||
sendeStatus("OK trigger (puls)");
|
||||
} else { // dauerhaft an (latch)
|
||||
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 setup() {
|
||||
Serial.begin(115200);
|
||||
pinMode(MOSFET_PIN, OUTPUT);
|
||||
mosfetSchreiben(false); // sicher AUS beim Start
|
||||
|
||||
if (USE_STATIC_IP) WiFi.config(IP_ADDR, IP_GATE, IP_MASK, IP_DNS);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(WIFI_SSID, WIFI_PASS);
|
||||
Serial.print("Verbinde WLAN");
|
||||
while (WiFi.status() != WL_CONNECTED) { delay(400); Serial.print("."); }
|
||||
Serial.println();
|
||||
Serial.print("Verbunden. IP: "); Serial.println(WiFi.localIP());
|
||||
|
||||
server.on("/", handleRoot);
|
||||
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.");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
server.handleClient();
|
||||
|
||||
// WLAN bei Bedarf neu verbinden (Plotter läuft lange).
|
||||
static unsigned long letzteWlanPruefung = 0;
|
||||
if (millis() - letzteWlanPruefung > 10000) {
|
||||
letzteWlanPruefung = millis();
|
||||
if (WiFi.status() != WL_CONNECTED) WiFi.begin(WIFI_SSID, WIFI_PASS);
|
||||
}
|
||||
|
||||
// Getimten Puls beenden.
|
||||
if (pulsAktiv && (long)(millis() - pulsBis) >= 0) {
|
||||
pulsAktiv = false;
|
||||
mosfetSchreiben(false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user