Grotti 3.5.2: Zu wenig Rest blockiert den Start nicht mehr, und ein anlaufender Druck wird nicht als Neustart neu eingereiht.

This commit is contained in:
Dominic Gundel committed 2026-10-21 21:04:28 +02:00
1 parent 5166ed6530
commit d1eb87ba25
13 files changed
+98 -77

No files matched your search

+5 -1
View File
@@ -255,13 +255,17 @@ def build(conn, watcher_latest, printers=None, send_jobs=None):
live_by_host[host]["display_step_index"] = job.get("display_step_index")
live_by_host[host]["step_index"] = job.get("step_index")
else:
phase = (job.get("phase") or "").lower()
starting = phase in ("starting", "await_print")
running.append({
"host": host,
"name": job.get("printer_name") or host,
"state": "running",
"state": "starting" if starting else "running",
"starting": starting,
"progress": job.get("progress") or 0,
"remain": job.get("remain") or 0,
"filename": job.get("filename") or "",
"message": job.get("message") or "",
"queue_id": job.get("id"),
"is_ablauf": bool(job.get("is_ablauf")),
})
+2 -38
View File
@@ -415,44 +415,8 @@ def sync_history(conn, cfg):
def assert_filament_enough(conn, file_row, spool_id=None, ace_map=None, host=""):
"""Dateien-Gewicht gegen Spule oder ACE-Plätze prüfen."""
need = float((file_row or {}).get("weight_g") or 0)
if need <= 0:
return
mapping = acm.normalize_mapping(ace_map)
if mapping and host:
leftovers = []
for item in mapping:
slot = int(item["ams_index"]) + 1
row = conn.execute(
"""
SELECT remaining_g, name FROM spools
WHERE printer_host = %s AND ace_slot = %s
ORDER BY id LIMIT 1
""",
(host, slot),
).fetchone()
if row:
leftovers.append(float(row["remaining_g"] or 0))
if leftovers and sum(leftovers) + 0.5 < need:
raise GrottiError(
"ACE-Plätze haben nur %.0f g, Datei braucht %.0f g."
% (sum(leftovers), need),
"spule",
)
return
if not spool_id:
return
spool = store.get_spool_row(conn, spool_id)
if not spool:
return
left = float(spool["remaining_g"] or 0)
if left + 0.5 < need:
raise GrottiError(
"Spule „%s“ hat nur %.0f g, Datei braucht %.0f g."
% (spool["name"], left, need),
"spule",
)
"""Zu wenig Rest bricht den Start nicht ab. Die Oberfläche warnt vorher."""
return
def prepare_send(conn, file_id, host, confirm, override, spool_id, ace_map=None):
+24 -6
View File
@@ -30,7 +30,9 @@ BUSY = {"printing", "paused", "heating", "error"}
KEEPALIVE_S = 120
# Nach Start ohne print_active: Upload/MQTT/LeviQ brauchen Zeit, aber nicht ewig
PRINT_START_TIMEOUT_S = 600
# Einfacher Queue-Start ohne Worker (typisch Grotti-Neustart)
# Upload-Worker weg (typisch Grotti-Neustart mitten im Hochladen).
# Nach erfolgreichem Senden ist die Phase await_print — dort gilt das nicht,
# sonst wird ein noch anlaufender Druck als „Neustart“ neu eingereiht.
SIMPLE_START_STUCK_S = 45
@@ -421,16 +423,18 @@ class PrintQueueDispatcher:
from grotti import orders
orders.attach_order_item_to_live_run(wconn, host, name, oid)
# Phase „starting“ lassen, bis Moonraker printing meldet.
# Sofort print_active + alter complete/standby → fälschlich „Fertig.“
# await_print: Datei ist raus, Drucker meldet printing oft erst
# nach einer Minute. Nicht bei starting lassen — sonst gilt nach
# 45 s der Neustart-Retry und der zweite Start trifft „druckt schon“.
store.update_print_queue(
wconn,
item_id,
{
"status": "running",
"message": "Startet auf %s: %s" % (printer_copy["name"], name),
"message": "Datei ist auf %s. Druckstart kann etwa eine Minute dauern."
% printer_copy["name"],
"host": host,
"phase": "starting",
"phase": "await_print",
"phase_started_at": "now",
},
)
@@ -487,7 +491,7 @@ class PrintQueueDispatcher:
"""Einfach-Druck: running bleiben bis Moonraker fertig/abgebrochen."""
phase = (item.get("phase") or "").lower()
state = (live.get("state") or "").lower()
if phase == "starting":
if phase in ("starting", "await_print"):
if state in ("printing", "paused", "heating") or live.get("print_active"):
store.update_print_queue(
conn,
@@ -498,6 +502,20 @@ class PrintQueueDispatcher:
"message": "Druckt…",
},
)
return
if phase == "await_print" and self._print_start_timed_out(item):
store.update_print_queue(
conn,
item["id"],
{
"status": "error",
"message": "Druckstart-Timeout: Drucker hat nach %d min nicht angefangen."
% (PRINT_START_TIMEOUT_S // 60),
"phase": "",
"phase_started_at": None,
},
)
return
# complete/standby vom vorherigen Auftrag: noch warten, nicht „Fertig.“
return
if phase != "print_active":
+4 -11
View File
@@ -2858,7 +2858,10 @@ def _match_spools_for_need(need, have, used_ids=None):
def material_fit_score(conn, host, file_id):
"""Höher = besser. -1 = warten (falsche Sorte mit Profil, oder zu wenig Rest)."""
"""Höher = besser. -1 = warten (falsche Sorte mit Profil).
Zu wenig Rest blockiert nicht; genug Rest gibt Zusatzpunkte.
"""
need, weight = _file_needs(conn, file_id)
have = _spool_material_set(conn, host)
if not need:
@@ -2883,11 +2886,6 @@ def material_fit_score(conn, host, file_id):
score += 5
if weight and best["remaining_g"] + 1e-6 >= weight:
score += 5
elif weight and all(h["remaining_g"] + 1e-6 < weight for h in matched):
return -1, "braucht %s (%s g fehlen)" % (
_need_label(n),
int(max(0, weight - max(h["remaining_g"] for h in matched))),
)
return score, ""
have_types = {h["material_type"] for h in have if h["material_type"]}
@@ -2900,11 +2898,6 @@ def material_fit_score(conn, host, file_id):
score += 5
if weight and any(h["remaining_g"] + 1e-6 >= weight for h in matched):
score += 5
elif weight and all(h["remaining_g"] + 1e-6 < weight for h in matched):
return -1, "braucht %s (%s g fehlen)" % (
_need_label(n),
int(max(0, weight - max(h["remaining_g"] for h in matched))),
)
return score, ""
+1 -1
View File
@@ -1,3 +1,3 @@
"""Version von Grotti (SemVer). Bei jeder Änderung CHANGELOG.md mitführen."""
__version__ = "3.5.1"
__version__ = "3.5.2"