Multiple printers, printer/slot in Spoolman, kept in sync
- One worker per printer (name, IP, CC2 Control URL, own pinned host key); a spool sits in at most one slot of one printer, moves between printers - Spoolman: location "<printer> Slot N" plus extra fields "drucker"/"slot" (created by the bridge), cleared and "Lager" on removal - Full comparison every minute: the printer wins over manual edits of loaded spools; stale printer locations go to storage; own locations untouched - Automatic migration of v1 settings, state and host key - Status hint when a tag is still in a tray but unmapped (e.g. after "Take out" in CC2 Control) - SSH test: connection drops during exec give a clean error, not a 500 - Mock environment with several printers; tests for all of the above Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014GcEk5tkfFSYD6NTF66NkC
This commit is contained in:
1 parent
bf32667f77
commit
a9041eb538
15 files changed
+1400
-684
No files matched your search
+30
-2
@@ -2,11 +2,21 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
# Extra fields the bridge keeps on loaded spools (Spoolman stores values JSON-encoded).
|
||||
FIELD_PRINTER = "drucker"
|
||||
FIELD_SLOT = "slot"
|
||||
FIELD_DEFS = {
|
||||
FIELD_PRINTER: {"name": "Drucker", "field_type": "text", "order": 100},
|
||||
FIELD_SLOT: {"name": "Slot", "field_type": "integer", "order": 101},
|
||||
}
|
||||
|
||||
|
||||
class SpoolmanError(RuntimeError):
|
||||
pass
|
||||
|
||||
@@ -42,8 +52,14 @@ class Spoolman:
|
||||
def spools(self, archived: bool = False) -> list[dict]:
|
||||
return self._request("GET", "/spool", params={"allow_archived": str(archived).lower()})
|
||||
|
||||
def set_location(self, spool_id: int, location: str) -> dict:
|
||||
return self._request("PATCH", f"/spool/{int(spool_id)}", json={"location": location})
|
||||
def update_spool(self, spool_id: int, body: dict) -> dict:
|
||||
return self._request("PATCH", f"/spool/{int(spool_id)}", json=body)
|
||||
|
||||
def fields(self) -> list[dict]:
|
||||
return self._request("GET", "/field/spool")
|
||||
|
||||
def add_field(self, key: str, params: dict) -> list[dict]:
|
||||
return self._request("POST", f"/field/spool/{key}", json=params)
|
||||
|
||||
def use(self, spool_id: int, grams: float) -> dict:
|
||||
return self._request("PUT", f"/spool/{int(spool_id)}/use", json={"use_weight": round(float(grams), 3)})
|
||||
@@ -73,6 +89,16 @@ def color_hex(spool: dict) -> str | None:
|
||||
return color if re.fullmatch(r"[0-9A-F]{6}", color) else None
|
||||
|
||||
|
||||
def extra_value(spool: dict, key: str):
|
||||
raw = (spool.get("extra") or {}).get(key)
|
||||
if raw is None:
|
||||
return None
|
||||
try:
|
||||
return json.loads(raw)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def summary(spool: dict) -> dict:
|
||||
fil = spool.get("filament") or {}
|
||||
return {
|
||||
@@ -91,4 +117,6 @@ def summary(spool: dict) -> dict:
|
||||
"location": spool.get("location") or "",
|
||||
"archived": bool(spool.get("archived")),
|
||||
"used_weight": spool.get("used_weight"),
|
||||
"printer": extra_value(spool, FIELD_PRINTER),
|
||||
"slot": extra_value(spool, FIELD_SLOT),
|
||||
}
|
||||
Reference in new issue
Block a user