Never touch spools of printers this bridge never ran
A fresh instance (empty data volume, no printers configured yet) treated every spool whose "drucker" field named a printer as stale and moved it to storage. Now only spools at our slot locations or naming a printer this bridge runs or has written are corrected; written printer names are kept in the state (seeded from the applied locations on update). 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
60d583e66e
commit
c04964066f
4 files changed
+35
-2
No files matched your search
@@ -189,6 +189,10 @@ docker build -t cc2-spoolman-bridge:dev . # Image
|
||||
python tools/mock_env.py --host mock --printers 2 # Mock: 2 Drucker (SSH :2222/2223, CC2 :8081/8082), Spoolman
|
||||
```
|
||||
|
||||
Einen Test-Container der Bridge gegen den Mock nur in einem Netz ohne Außenverbindung starten
|
||||
(`docker network create --internal …`) und mit `SPOOLMAN_URL=http://mock:7912`. Sonst spricht er bis zum Speichern
|
||||
der Einstellungen mit der voreingestellten echten Spoolman-Instanz.
|
||||
|
||||
Die Tests decken ab: Tag-Kodierung gegen die verifizierte Vorlage, ID-Dekodierung, Palette und Materialvorschlag,
|
||||
Zuordnung bei verschwindender ID, Verbrauchs-Delta und Idempotenz (auch nach Abbruch), Dry-Run, mehrere Drucker
|
||||
(Umzug, Ausfall eines Druckers, Entfernen, Umbenennen), Spoolman-Ort und Zusatzfelder inkl. Korrektur von Hand-
|
||||
|
||||
+10
-2
@@ -948,6 +948,10 @@ class Bridge:
|
||||
ours.discard(self.settings.location_storage)
|
||||
return ours
|
||||
|
||||
def _our_printers(self) -> set[str]:
|
||||
"""Printer names this bridge runs or has written. Spoolman's "drucker" field naming anyone else is not ours."""
|
||||
return set(self.state.data.get("written_printers", [])) | {w.name for w in self.workers.values()}
|
||||
|
||||
def _ensure_fields(self) -> bool:
|
||||
if self.fields_ok or not self.settings.spoolman_fields:
|
||||
return True
|
||||
@@ -979,14 +983,15 @@ class Bridge:
|
||||
if self.clock() - self.last_sync >= SYNC_SECONDS:
|
||||
# Full comparison: the printer wins over manual edits in Spoolman.
|
||||
ours = self._our_locations()
|
||||
our_printers = self._our_printers()
|
||||
for sp in self.spoolman.spools():
|
||||
sid = int(sp["id"])
|
||||
names[sid] = label(sp)
|
||||
actual = self._actual(sp)
|
||||
want = desired.get(sid)
|
||||
if want is None:
|
||||
if actual["location"] not in ours and actual["printer"] is None:
|
||||
# a place of yours (e.g. a shelf): not ours to touch
|
||||
if actual["location"] not in ours and actual["printer"] not in our_printers:
|
||||
# a place of yours (e.g. a shelf), or a printer this bridge never ran: not ours to touch
|
||||
targets.pop(sid, None)
|
||||
applied.pop(str(sid), None)
|
||||
continue
|
||||
@@ -1016,6 +1021,9 @@ class Bridge:
|
||||
if t["location"] != storage["location"]:
|
||||
written = set(self.state.data["written_locations"]) | {t["location"]}
|
||||
self.state.data["written_locations"] = sorted(written)
|
||||
if t.get("printer"):
|
||||
printers = set(self.state.data.get("written_printers", [])) | {t["printer"]}
|
||||
self.state.data["written_printers"] = sorted(printers)
|
||||
except SpoolmanError as e:
|
||||
self.fields_ok = False
|
||||
self._spoolman_error(str(e))
|
||||
|
||||
@@ -267,4 +267,6 @@ def migrate_state(data: dict, first_printer: str) -> dict:
|
||||
data["version"] = 2
|
||||
data.setdefault("applied", {})
|
||||
data.setdefault("written_locations", [])
|
||||
if "written_printers" not in data:
|
||||
data["written_printers"] = sorted({a["printer"] for a in data["applied"].values() if a.get("printer")})
|
||||
return data
|
||||
@@ -417,6 +417,7 @@ def test_stale_printer_locations_go_to_storage(env):
|
||||
env["sm"].add(3, location="Lager", extra={"drucker": '"Links"', "slot": "1"})
|
||||
env["sm"].add(4, location="Regal 1")
|
||||
env["bridge"].state.data["written_locations"] = ["CC2 Slot 2"]
|
||||
env["bridge"].state.data["written_printers"] = ["Links"] # a printer this bridge used to run
|
||||
poll(env)
|
||||
assert env["sm"].db[1]["location"] == "Lager"
|
||||
assert env["sm"].db[2]["location"] == "Lager"
|
||||
@@ -773,3 +774,21 @@ def test_job_progress_only_read_while_printing(env):
|
||||
job = b.snapshot["printers"][0]["job"]
|
||||
assert job["file"] == "cube.gcode" and job["grams"] == 1.23 and job["slots"] == [{"slot": 1, "grams": 1.23}]
|
||||
assert job["progress"] == 40 and job["layer"] == 12 and job["layers"] == 30 and job["remaining"] == 600
|
||||
|
||||
|
||||
def test_fresh_bridge_leaves_other_bridges_spools_alone(env):
|
||||
"""A new instance (empty data volume, no printers yet) must not pull spools out of printers it never ran."""
|
||||
env["settings"].save({"printers": []})
|
||||
env["sm"].add(11, location="CC2 Links Slot 4", extra={"drucker": '"CC2 Links"', "slot": "4"})
|
||||
env["sm"].add(1, location="CC2 Rechts Slot 2", extra={"drucker": '"CC2 Rechts"', "slot": "2"})
|
||||
poll(env, 13)
|
||||
assert env["sm"].db[11]["location"] == "CC2 Links Slot 4" and env["sm"].extra(11)["drucker"] == "CC2 Links"
|
||||
assert env["sm"].db[1]["location"] == "CC2 Rechts Slot 2"
|
||||
assert not [c for c in env["sm"].calls if c[0] in ("location", "extra")]
|
||||
|
||||
|
||||
def test_printer_names_written_are_remembered(env):
|
||||
env["sm"].add(5)
|
||||
insert(env, 0, 5)
|
||||
poll(env)
|
||||
assert env["bridge"].state.data["written_printers"] == ["Rechts"]
|
||||
Reference in new issue
Block a user