Grotti 2.5.1: Druckbericht, Statistik, Spulenakte und Protokoll-Fixes.

This commit is contained in:
Dominic Gundel committed 2026-10-17 15:32:51 +02:00
1 parent b277ca48aa
commit efd0801cd1
30 files changed
+3393 -178

No files matched your search

+159 -1
View File
@@ -418,6 +418,64 @@ def steuern(host, action, params=None):
if not re.fullmatch(r"[A-Za-z0-9._+\-]+", name):
raise PrinterError("Ungültiger Objektname.")
return gcode_script(host, "EXCLUDE_OBJECT NAME=%s" % name, timeout=20)
if action == "dryer_start":
try:
unit = int(params.get("id"))
except (TypeError, ValueError) as exc:
raise PrinterError("ACE-Box muss 0 oder 1 sein.") from exc
if unit not in (0, 1):
raise PrinterError("ACE-Box muss 0 oder 1 sein.")
try:
temp = int(round(float(params.get("temp"))))
duration = int(round(float(params.get("duration"))))
except (TypeError, ValueError) as exc:
raise PrinterError("Temperatur und Dauer müssen Zahlen sein.") from exc
if temp < 35 or temp > 65:
raise PrinterError("Trockner-Temperatur außerhalb 35–65 °C.")
if duration < 1 or duration > 720:
raise PrinterError("Trockner-Dauer außerhalb 1–720 Minuten.")
return post_json(
host,
"/server/filament_hub/start_drying",
{"id": unit, "temp": temp, "duration": duration, "fan_speed": 0},
timeout=15,
)
if action == "dryer_stop":
try:
unit = int(params.get("id"))
except (TypeError, ValueError) as exc:
raise PrinterError("ACE-Box muss 0 oder 1 sein.") from exc
if unit not in (0, 1):
raise PrinterError("ACE-Box muss 0 oder 1 sein.")
return post_json(host, "/server/filament_hub/stop_drying", {"id": unit}, timeout=15)
if action == "slot_setzen":
hub_id = _int_or_none(params.get("id"))
index = _int_or_none(params.get("index"))
if hub_id not in (0, 1) or index is None or index < 0 or index > 3:
raise PrinterError("ACE-Platz ungültig.")
material = str(params.get("type") or "").strip()
if not material:
raise PrinterError("Die Spule braucht eine Sorte.")
color = params.get("color")
if not isinstance(color, dict):
raise PrinterError("Die Spule braucht eine Farbe.")
try:
red = max(0, min(255, int(color.get("R"))))
green = max(0, min(255, int(color.get("G"))))
blue = max(0, min(255, int(color.get("B"))))
except (TypeError, ValueError) as exc:
raise PrinterError("Die Spule braucht eine Farbe.") from exc
return post_json(
host,
"/printer/filament_hub/set_filament_info",
{
"id": hub_id,
"index": index,
"type": material,
"color": {"R": red, "G": green, "B": blue},
},
timeout=15,
)
if action == "firmware_restart":
# GoKlipper/Rinkhals: FIRMWARE_RESTART deadlocked gklib (Rinkhals #32).
raise PrinterError(
@@ -600,6 +658,80 @@ def extract_slots(status):
return found[:8]
def _int_or_none(value):
if isinstance(value, bool) or not isinstance(value, (int, float)):
return None
return int(value)
def extract_dryers(status):
"""Echte ACE-Boxen aus mmu_machine (num_gates > 0), Dummy-Unit bleibt weg."""
machine = (status or {}).get("mmu_machine")
if not isinstance(machine, dict):
return []
found = []
for unit_id in (0, 1):
unit = machine.get("unit_%d" % unit_id)
if not isinstance(unit, dict):
continue
gates = _int_or_none(unit.get("num_gates"))
if gates is None or gates < 1:
continue
found.append({
"id": unit_id,
"status": str(unit.get("dryer_status") or "stop"),
"temp": _int_or_none(unit.get("dryer_temp")) or 0,
"target": _int_or_none(unit.get("dryer_target_temp")) or 0,
"remain": _int_or_none(unit.get("dryer_remaining")) or 0,
"humidity": _int_or_none(unit.get("dryer_humidity")) or 0,
"gates": gates,
})
return found
def ace_hub_index(ace_slot):
"""Grotti-Platz 1–8 → Moonraker id (0/1) und index in der Box (0–3)."""
try:
slot = int(ace_slot)
except (TypeError, ValueError) as exc:
raise PrinterError("ACE-Platz muss 1–8 sein.") from exc
if slot < 1 or slot > 8:
raise PrinterError("ACE-Platz muss 1–8 sein.")
return (slot - 1) // 4, (slot - 1) % 4
def spool_rgb(spool):
"""Erste Farbe der Spule als RGB-Dict für set_filament_info."""
colors = (spool or {}).get("colors") or []
if isinstance(colors, str):
try:
colors = json.loads(colors)
except (json.JSONDecodeError, TypeError):
colors = []
hex_value = ""
if isinstance(colors, list):
for item in colors:
if isinstance(item, dict) and item.get("hex"):
hex_value = str(item["hex"]).strip()
break
if isinstance(item, str) and item.strip():
hex_value = item.strip()
break
if not hex_value:
hex_value = str((spool or {}).get("color") or "").strip()
if hex_value.startswith("#"):
hex_value = hex_value[1:]
if len(hex_value) == 8:
hex_value = hex_value[:6]
if len(hex_value) != 6 or any(ch not in "0123456789abcdefABCDEF" for ch in hex_value):
return None
return {
"R": int(hex_value[0:2], 16),
"G": int(hex_value[2:4], 16),
"B": int(hex_value[4:6], 16),
}
def slots(host, status=None):
if status is not None:
found = extract_slots(status)
@@ -868,6 +1000,8 @@ def query(host):
extras = []
if "probe" in names:
extras.append("probe")
if "mmu_machine" in names:
extras.append("mmu_machine")
url = "http://%s/printer/objects/query?%s" % (host, "&".join([base] + extras))
payload = fetch_json(url)
status = payload["result"]["status"]
@@ -875,6 +1009,21 @@ def query(host):
card = status.get("virtual_sdcard") or {}
extruder = status.get("extruder") or {}
bed = status.get("heater_bed") or {}
mmu = status.get("mmu") or {}
mmu_tool = mmu.get("tool")
if not isinstance(mmu_tool, (int, float)) or isinstance(mmu_tool, bool):
mmu_tool = mmu.get("gate")
if isinstance(mmu_tool, (int, float)) and not isinstance(mmu_tool, bool):
mmu_tool = int(mmu_tool)
if mmu_tool < 0:
mmu_tool = None
else:
mmu_tool = None
filament_used = stats.get("filament_used")
if not isinstance(filament_used, (int, float)) or isinstance(filament_used, bool):
filament_used = None
else:
filament_used = float(filament_used)
raw_name = stats.get("filename") or ""
filename = raw_name.replace("\\", "/").rsplit("/", 1)[-1] if raw_name else ""
meta = metadata(host, raw_name) if raw_name else {}
@@ -933,8 +1082,15 @@ def query(host):
"bed_target": bed.get("target"),
"chamber": chamber,
"chamber_target": chamber_target,
"filament_used": filament_used,
"filament_total": meta.get("filament_total") or 0,
"mmu_tool": mmu_tool,
"material": card.get("filament_type") or meta.get("filament_type") or "",
"weight": meta.get("filament_weight_total") or 0,
"weight": (
sum(float(x or 0) for x in meta["filament_weight_total"])
if isinstance(meta.get("filament_weight_total"), list)
else (meta.get("filament_weight_total") or 0)
),
"height": meta.get("object_height") or 0,
"layer_height": meta.get("layer_height") or card.get("layer_height") or 0,
"estimated": meta.get("estimated_time") or 0,
@@ -952,6 +1108,7 @@ def query(host):
"speed_factor": speed_factor,
"extrude_factor": extrude_factor,
"slots": slots(host, status),
"dryers": extract_dryers(status),
"exclude": exclude_status(host),
"anycubic_task": _anycubic_task_field(host),
}
@@ -993,6 +1150,7 @@ def snapshot(printers):
"speed_factor": None,
"extrude_factor": None,
"slots": [],
"dryers": [],
"anycubic_task": None,
})
card.update(mesh_fields(printer["host"]))