fix(satellite): http.get liefert ganze Statusseiten + contains-Filter, kein Scan-Overhead
ARIA erreichte den Drucker per Satellit, bekam aber nur die ersten 2000 Zeichen (r.text[:2000]) — die Tintenbalken stehen weiter unten und fielen weg; ein Offset-Versuch wurde ignoriert (nicht unterstuetzt). Zudem lief vor JEDEM sat_command ein voller LAN-Scan, auch fuer http.get, das die URL direkt nutzt -> sehr lange Antwortzeiten. _do_http: - Body-Limit env-konfigurierbar (HTTP_MAX_CHARS Default 20000, harter Deckel HTTP_MAX_CHARS_HARD 200000) statt fixer 2000. - params.offset + params.max_chars zum Paginieren. - params.contains (String/Liste): nur Zeilen mit einem der Begriffe -> zieht aus einer grossen Statusseite gezielt die Tintenwerte, ohne Paging. - Antwort meldet total_chars/returned_chars/offset/truncated/filtered. - HTTP_TIMEOUT_SEC (Default 10s) statt fixer 6s. sat_command: LAN-Scan nur noch bei dial.launch oder wenn ein device-Ref mitkommt; http.get/http.post/wol nutzen die gecachte Liste -> deutlich schneller. agent.py + seed_rules.py: satellite_command-Tool + Drucker-Beispiel um contains/offset/max_chars ergaenzt, damit ARIA den Filter nutzt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+9
-2
@@ -1220,8 +1220,15 @@ META_TOOLS = [
|
|||||||
"Beispiel YouTube-Video auf Fire TV: action='dial.launch', "
|
"Beispiel YouTube-Video auf Fire TV: action='dial.launch', "
|
||||||
"device='Fire TV', params={'app':'YouTube','v':'<videoId>'}. Weitere "
|
"device='Fire TV', params={'app':'YouTube','v':'<videoId>'}. Weitere "
|
||||||
"Aktionen: 'wol' (params={'mac':'...'}) zum Aufwecken, "
|
"Aktionen: 'wol' (params={'mac':'...'}) zum Aufwecken, "
|
||||||
"'http.get'/'http.post' (params={'url':'...'}) fuer lokale Webhooks. "
|
"'http.get'/'http.post' (params={'url':'...'}) fuer lokale Webhooks UND "
|
||||||
"Geht nur, wenn der Satellit Steuerung erlaubt (siehe satellite_list)."
|
"um Geraete-Statusseiten zu lesen (Drucker-Tinte, NAS ...). Bei grossen "
|
||||||
|
"Seiten NICHT blind paginieren: setze params['contains'] (String oder "
|
||||||
|
"Liste) — dann kommen nur Zeilen zurueck, die einen der Begriffe enthalten "
|
||||||
|
"(z.B. contains=['ink','toner','cyan','magenta','yellow','black','%'] fuer "
|
||||||
|
"Tinte). Zusaetzlich moeglich: params['offset'] und params['max_chars'] "
|
||||||
|
"(Default 20000). Die Antwort meldet total_chars + truncated, damit Du "
|
||||||
|
"siehst, ob noch mehr da ist. Geht nur, wenn der Satellit Steuerung "
|
||||||
|
"erlaubt (siehe satellite_list)."
|
||||||
),
|
),
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
@@ -493,8 +493,13 @@ SEED_RULES: List[dict] = [
|
|||||||
"\n"
|
"\n"
|
||||||
"Beispiel 'Patronenstand vom Drucker zuhause': satellite_list -> Satellit im "
|
"Beispiel 'Patronenstand vom Drucker zuhause': satellite_list -> Satellit im "
|
||||||
"Heimnetz online? -> satellite_command(satellite='<Heim>', action='http.get', "
|
"Heimnetz online? -> satellite_command(satellite='<Heim>', action='http.get', "
|
||||||
"params={'url':'http://<drucker-ip>/general/status.html'}) -> Fuellstaende "
|
"params={'url':'http://<drucker-ip>/general/status.html', "
|
||||||
"(BK/C/M/Y) aus dem HTML vorlesen. NICHT aus dem Gedaechtnis raten.\n"
|
"'contains':['ink','toner','cyan','magenta','yellow','black','%']}) -> aus den "
|
||||||
|
"gefilterten Zeilen die Fuellstaende (BK/C/M/Y) vorlesen. Der contains-Filter "
|
||||||
|
"holt nur die relevanten Zeilen aus einer grossen Statusseite (sonst wird der "
|
||||||
|
"Body bei max_chars, Default 20000, abgeschnitten). Kommt nichts Brauchbares: "
|
||||||
|
"andere Statuspfade probieren (/general/information.html?kind=item, SNMP-UI) "
|
||||||
|
"oder offset erhoehen. NICHT aus dem Gedaechtnis raten.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Beispiel 'spiel YouTube-Video auf dem Buero-Stick':\n"
|
"Beispiel 'spiel YouTube-Video auf dem Buero-Stick':\n"
|
||||||
" satellite_command(satellite='Buero', device='Fire TV', "
|
" satellite_command(satellite='Buero', device='Fire TV', "
|
||||||
|
|||||||
+61
-3
@@ -121,6 +121,13 @@ SCAN_INTERVAL_SEC = int(os.environ.get("SCAN_INTERVAL_SEC", "300") or "300")
|
|||||||
DISCOVER_TIMEOUT_SEC = float(os.environ.get("DISCOVER_TIMEOUT_SEC", "6") or "6")
|
DISCOVER_TIMEOUT_SEC = float(os.environ.get("DISCOVER_TIMEOUT_SEC", "6") or "6")
|
||||||
DEVICE_CACHE_TTL_SEC = int(os.environ.get("DEVICE_CACHE_TTL_SEC", "120") or "120")
|
DEVICE_CACHE_TTL_SEC = int(os.environ.get("DEVICE_CACHE_TTL_SEC", "120") or "120")
|
||||||
|
|
||||||
|
# http.get/http.post: Body-Ausschnitt. Default grosszuegig (ganze Statusseiten
|
||||||
|
# passen), mit hartem Deckel gegen Riesen-Payloads durchs RVS. offset/max_chars
|
||||||
|
# pro Request ueberschreibbar; contains-Filter zieht nur relevante Zeilen.
|
||||||
|
HTTP_TIMEOUT_SEC = float(os.environ.get("HTTP_TIMEOUT_SEC", "10") or "10")
|
||||||
|
HTTP_MAX_CHARS = int(os.environ.get("HTTP_MAX_CHARS", "20000") or "20000")
|
||||||
|
HTTP_MAX_CHARS_HARD = int(os.environ.get("HTTP_MAX_CHARS_HARD", "200000") or "200000")
|
||||||
|
|
||||||
HEARTBEAT_SEC = 25
|
HEARTBEAT_SEC = 25
|
||||||
|
|
||||||
# mDNS-Servicetypen, die fuer ARIA interessant sind.
|
# mDNS-Servicetypen, die fuer ARIA interessant sind.
|
||||||
@@ -472,14 +479,60 @@ def _do_wol(params: dict) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
def _do_http(action: str, params: dict) -> dict:
|
def _do_http(action: str, params: dict) -> dict:
|
||||||
|
"""HTTP-GET/POST vom Satelliten aus (lokale Webhooks, Geraete-Statusseiten …).
|
||||||
|
|
||||||
|
params:
|
||||||
|
url Pflicht (http/https).
|
||||||
|
body/headers optional (POST).
|
||||||
|
offset ab welchem Zeichen der Body zurueckgegeben wird (Default 0).
|
||||||
|
max_chars wie viele Zeichen max. (Default HTTP_MAX_CHARS, hart gedeckelt).
|
||||||
|
contains String oder Liste: nur Zeilen, die (case-insensitive) einen der
|
||||||
|
Begriffe enthalten, werden zurueckgegeben. Ideal um aus einer
|
||||||
|
grossen Statusseite nur die relevanten Werte (z.B. Tinte) zu
|
||||||
|
ziehen, ohne die ganze Seite zu paginieren.
|
||||||
|
Antwort enthaelt total_chars + truncated, damit der Aufrufer weiss, ob noch
|
||||||
|
mehr da ist."""
|
||||||
import requests
|
import requests
|
||||||
url = params.get("url") or ""
|
url = params.get("url") or ""
|
||||||
if not url.startswith(("http://", "https://")):
|
if not url.startswith(("http://", "https://")):
|
||||||
return {"ok": False, "error": "url (http/https) erforderlich."}
|
return {"ok": False, "error": "url (http/https) erforderlich."}
|
||||||
method = "GET" if action == "http.get" else "POST"
|
method = "GET" if action == "http.get" else "POST"
|
||||||
r = requests.request(method, url, data=params.get("body"),
|
r = requests.request(method, url, data=params.get("body"),
|
||||||
headers=params.get("headers"), timeout=6)
|
headers=params.get("headers"), timeout=HTTP_TIMEOUT_SEC)
|
||||||
return {"ok": True, "result": {"status": r.status_code, "body": r.text[:2000]}}
|
text = r.text
|
||||||
|
total = len(text)
|
||||||
|
|
||||||
|
contains = params.get("contains")
|
||||||
|
if contains:
|
||||||
|
terms = [contains] if isinstance(contains, str) else list(contains)
|
||||||
|
terms = [str(t).lower() for t in terms if str(t).strip()]
|
||||||
|
if terms:
|
||||||
|
lines = [ln for ln in text.splitlines()
|
||||||
|
if any(t in ln.lower() for t in terms)]
|
||||||
|
text = "\n".join(lines)
|
||||||
|
|
||||||
|
try:
|
||||||
|
offset = max(0, int(params.get("offset", 0)))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
offset = 0
|
||||||
|
try:
|
||||||
|
max_chars = int(params.get("max_chars", HTTP_MAX_CHARS))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
max_chars = HTTP_MAX_CHARS
|
||||||
|
max_chars = max(1, min(max_chars, HTTP_MAX_CHARS_HARD))
|
||||||
|
|
||||||
|
body = text[offset:offset + max_chars]
|
||||||
|
returned_end = offset + len(body)
|
||||||
|
truncated = returned_end < len(text)
|
||||||
|
return {"ok": True, "result": {
|
||||||
|
"status": r.status_code,
|
||||||
|
"body": body,
|
||||||
|
"total_chars": total, # Groesse der Roh-Antwort
|
||||||
|
"filtered": bool(contains), # contains-Filter aktiv?
|
||||||
|
"offset": offset,
|
||||||
|
"returned_chars": len(body),
|
||||||
|
"truncated": truncated, # noch mehr Text nach diesem Ausschnitt?
|
||||||
|
}}
|
||||||
|
|
||||||
|
|
||||||
# ─── Helpers ────────────────────────────────────────────────────────
|
# ─── Helpers ────────────────────────────────────────────────────────
|
||||||
@@ -597,7 +650,12 @@ class Satellite:
|
|||||||
params = payload.get("params") or {}
|
params = payload.get("params") or {}
|
||||||
if payload.get("device") and "device" not in params:
|
if payload.get("device") and "device" not in params:
|
||||||
params["device"] = payload.get("device")
|
params["device"] = payload.get("device")
|
||||||
devices = await self._scan()
|
# Geraeteliste nur scannen, wenn die Aktion sie wirklich braucht
|
||||||
|
# (dial.launch loest ein Geraet auf, oder es wurde ein device-Ref
|
||||||
|
# mitgegeben). http.get/http.post/wol arbeiten direkt mit url/mac —
|
||||||
|
# ein voller LAN-Scan davor kostete nur unnoetig viele Sekunden.
|
||||||
|
needs_devices = action == "dial.launch" or bool(params.get("device"))
|
||||||
|
devices = await self._scan() if needs_devices else self._devices
|
||||||
result = await _control(action, params, devices)
|
result = await _control(action, params, devices)
|
||||||
await self._send({
|
await self._send({
|
||||||
"type": "sat_result",
|
"type": "sat_result",
|
||||||
|
|||||||
Reference in New Issue
Block a user