Erkannte Plotter-Fehler an Directus melden (agent_logs)

Directus-Zugang aus eigener config.json, der Agenten-config.json oder Env-Vars.
Bei erkanntem Error-Fenster + bei Erreichen der max. Recover-Versuche wird die
Fehlermeldung (inkl. Fenstertext) nach agent_logs (source=recover) gesendet.
requests ist optional – ohne läuft AutoRecover wie bisher (nur ohne Directus-Log).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
s4luorth
2026-09-11 19:58:14 +02:00
parent f9867dcf7a
commit f75957c15e

View File

@@ -1,16 +1,87 @@
"""
AutoRecover - Drucker Fehler Handler
Erkennt das "Error"-Fenster (Java AWT) und klickt automatisch "Recover" (max. 3x).
Zusätzlich werden die erkannten Fehlermeldungen (best-effort) an Directus gemeldet
(Collection agent_logs, source="recover"). Die Directus-Zugangsdaten kommen aus
einer config.json neben diesem Skript ODER aus der des Agenten
(../skrift-agent/config.json) ODER aus Umgebungsvariablen
(SKRIFT_DIRECTUS_URL / SKRIFT_DIRECTUS_TOKEN / SKRIFT_MACHINE_ID).
"""
import win32gui
import win32con
import ctypes
import time
import os
import json
try:
import requests
except Exception: # requests optional – ohne läuft AutoRecover trotzdem (nur ohne Directus-Log)
requests = None
MAX_VERSUCHE = 3
PRUEF_INTERVAL = 2 # Sekunden zwischen den Checks
_HIER = os.path.dirname(os.path.abspath(__file__))
def _lade_directus_cfg():
"""Directus-Zugang aus AutoRecover- oder Agenten-config.json bzw. Env-Variablen."""
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("directus_url") and c.get("directus_token"):
return c.get("directus_url"), c.get("directus_token"), c.get("machine_id")
except Exception:
pass
url = os.environ.get("SKRIFT_DIRECTUS_URL")
token = os.environ.get("SKRIFT_DIRECTUS_TOKEN")
mid = os.environ.get("SKRIFT_MACHINE_ID")
return url, token, (int(mid) if mid and str(mid).isdigit() else None)
_DX_URL, _DX_TOKEN, _DX_MID = _lade_directus_cfg()
def dx_log(level, message):
"""Meldet ein Ereignis an Directus (agent_logs). Fehler nie fatal."""
if not (requests and _DX_URL and _DX_TOKEN):
return
try:
requests.post(
_DX_URL.rstrip("/") + "/items/agent_logs",
headers={"Authorization": f"Bearer {_DX_TOKEN}"},
json={"machine": _DX_MID, "level": level, "source": "recover",
"message": str(message)[:2000]},
timeout=15,
)
except Exception:
pass
def fenster_text(hwnd):
"""Liest die Beschriftungen der Kind-Fenster (die eigentliche Fehlermeldung)."""
teile = []
def _cb(h, _):
try:
t = win32gui.GetWindowText(h)
if t and t.strip():
teile.append(t.strip())
except Exception:
pass
return True
try:
win32gui.EnumChildWindows(hwnd, _cb, None)
except Exception:
pass
return " / ".join(teile)[:500]
def click_recover_button(hwnd):
"""
Holt die Fensterposition und klickt auf den Recover-Button.
@@ -47,13 +118,16 @@ def main():
if hwnd and win32gui.IsWindowVisible(hwnd):
versuche += 1
meldung = fenster_text(hwnd) or "Error"
print(f"[Versuch {versuche}/{MAX_VERSUCHE}] Fehlerfenster gefunden!")
dx_log("error", f"Plotter-Fehlerfenster erkannt (Versuch {versuche}/{MAX_VERSUCHE}): {meldung}")
time.sleep(0.5)
click_recover_button(hwnd)
if versuche >= MAX_VERSUCHE:
print(f"\nMaximale Versuche ({MAX_VERSUCHE}) erreicht. Programm beendet.")
dx_log("error", f"Maximale Recover-Versuche ({MAX_VERSUCHE}) erreicht – manueller Eingriff nötig.")
break
time.sleep(3)