Recognise rolls put back, use CC2 Control's log for bookings
CI / test (push) Successful in 1m13s
CI / image (push) Successful in 48s

- A roll put back into the slot it left, with the tag data unchanged, is
  mapped again and CC2 Control's question answered; if the touchscreen then
  replaces the id, it was another roll and goes to storage.
- A touchscreen edit of a scanned roll keeps the mapping and re-answers
  CC2 Control's question so its use is counted.
- Bookings name the print and its result from CC2 Control's log.
- A weigh-in in CC2 Control sets Spoolman's remaining weight instead of
  being booked as use.
- After "Take out" with the roll still in the tray, the tray's logged use
  is booked to that roll until it leaves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GcEk5tkfFSYD6NTF66NkC
This commit is contained in:
Jonas MenzelandClaude Opus 5.5 committed 2026-10-25 10:29:33 +01:00
1 parent a9041eb538
commit 67c3b54903
7 files changed
+607 -40

No files matched your search

+60 -4
View File
@@ -3,8 +3,11 @@
python tools/mock_env.py --host mock --printers 2
Per printer i: SSH 2222+i (keys added via POST /authorize), CC2 Control 8081+i. Spoolman 7912, control 8099.
Control endpoints (port 8099, optional &printer=i): POST /insert?slot=&spool=, /remove?slot=,
/consume?slot=&grams=, /job?active=0|1, /touch?slot= (touchscreen edit: weight falls back to 1000).
Control endpoints (port 8099, optional &printer=i): POST /insert?slot=&spool= (scanned roll in),
/remove?slot= (roll out, the record keeps its tag data), /putback?slot= (roll in without a scan),
/takeout?slot= (CC2 Control's "Take out"), /consume?slot=&grams=, /job?active=0|1,
/touch?slot= (touchscreen edit: weight falls back to 1000), /print?slot=&mm=&job=&result= (logged use of
a tray, charged to its spool if it has one), /weigh?spool=&remaining= (weigh-in in CC2 Control).
"""
from __future__ import annotations
@@ -15,6 +18,7 @@ import json
import socket
import sys
import threading
import time
from pathlib import Path
import paramiko
@@ -32,6 +36,7 @@ def printer_state():
"slots": [{"slot": i, "spool": "", "last": "", "question": "", "printer": {"status": 0}} for i in range(5)],
"spools": [],
"job": {"active": False, "file": "", "slots": []},
"log": [],
"ids": (f"m{i:04d}" for i in itertools.count(1)),
}
st["slots"][4]["printer"] = None
@@ -91,7 +96,7 @@ def make_cc2(host_header: str, P: dict) -> FastAPI:
with LOCK:
STATE["requests"].append("GET /api/spools")
return {"available": True, "enabled": P["enabled"], "slots": P["slots"], "spools": P["spools"],
"job": P["job"], "log": []}
"job": P["job"], "log": P["log"]}
@app.get("/api/canvas")
def canvas():
@@ -178,6 +183,9 @@ def make_spoolman() -> FastAPI:
if value is not None and key not in STATE["fields"]:
raise HTTPException(400, f"Unknown extra field {key}")
sp.update({k: v for k, v in body.items() if k in ("location",)})
if "used_weight" in body:
sp["used_weight"] = float(body["used_weight"])
sp["remaining_weight"] = (sp.get("initial_weight") or 1000.0) - sp["used_weight"]
for key, value in (body.get("extra") or {}).items():
if value is None:
sp["extra"].pop(key, None)
@@ -235,7 +243,55 @@ def make_control() -> FastAPI:
P = PRINTERS[printer]
with LOCK:
P["slots"][slot]["printer"] = {"status": 0}
P["record"][f"channels{slot}"] = channel("")
if P["slots"][slot]["spool"]:
P["slots"][slot]["last"] = P["slots"][slot]["spool"]
P["slots"][slot]["spool"] = ""
P["slots"][slot]["question"] = ""
return {"ok": True}
@app.post("/putback")
def putback(slot: int, printer: int = 0):
P = PRINTERS[printer]
with LOCK:
P["slots"][slot]["printer"] = {"status": 1}
P["slots"][slot]["spool"] = ""
P["slots"][slot]["question"] = "inserted"
return {"ok": True}
@app.post("/takeout")
def takeout(slot: int, printer: int = 0):
P = PRINTERS[printer]
with LOCK:
P["slots"][slot]["last"] = P["slots"][slot]["spool"]
P["slots"][slot]["spool"] = ""
P["slots"][slot]["question"] = ""
return {"ok": True}
def log(P, kind, spool, slot, grams, mm, job="", result=""):
P["log"].insert(0, {"time": int(time.time()), "spool": spool, "slot": slot, "kind": kind, "grams": grams,
"mm": mm, "job": job, "result": result})
del P["log"][150:]
@app.post("/print")
def print_(slot: int, mm: float, job: str = "test.gcode", result: str = "complete", printer: int = 0):
P = PRINTERS[printer]
with LOCK:
sid = P["slots"][slot]["spool"]
sp = next((s for s in P["spools"] if s["id"] == sid), None)
d, rho = (sp["diameter"], sp["density"]) if sp else (1.75, 1.24)
grams = mm * 3.141592653589793 * (d / 2) ** 2 * rho / 1000
if sp:
sp["remaining"] -= grams
log(P, "print", sid, slot, -round(grams, 3), mm, job, result)
return {"ok": True, "grams": grams}
@app.post("/weigh")
def weigh(spool: str, remaining: float, printer: int = 0):
P = PRINTERS[printer]
with LOCK:
sp = next(s for s in P["spools"] if s["id"] == spool)
log(P, "correction", spool, -1, remaining - sp["remaining"], 0, "", "weighed")
sp["remaining"] = remaining
return {"ok": True}
@app.post("/touch")