Skip to content

Commit 5b60e89

Browse files
authored
Merge pull request #70 from Xento/implement-bambuspool-assignment
the type of the AMS is now shown implemented linking of unknown bambulab spools empty trays can now automatically clear the assigned spool (config option) assigned spool id is now shown in tray view
2 parents ae8d438 + d619e6d commit 5b60e89

17 files changed

Lines changed: 524 additions & 112 deletions

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ SpoolMan can print QR-code stickers for every spool; follow the SpoolMan label g
177177
- set `TRACK_LAYER_USAGE` to `True` to switch to per-layer tracking/consumption **while `AUTO_SPEND` is also `True`**. If `AUTO_SPEND` is `False`, all filament tracking remains disabled regardless of `TRACK_LAYER_USAGE`.
178178
- set `AUTO_SPEND` to `True` if you want automatic filament usage tracking (see the AUTO SPEND notes below).
179179
- set `DISABLE_MISMATCH_WARNING` to `True` to hide mismatch warnings in the UI (mismatches are still detected and logged to `data/filament_mismatch.json`).
180+
- set `CLEAR_ASSIGNMENT_WHEN_EMPTY` to `True` if you want OpenSpoolMan to clear any SpoolMan assignment and reset the AMS tray whenever the printer reports no spool in that slot.
180181
- By default, the app reads `data/3d_printer_logs.db` for print history; override it through `OPENSPOOLMAN_PRINT_HISTORY_DB` or via the screenshot helper (which targets `data/demo.db` by default).
181182

182183
- Run SpoolMan.

app.py

Lines changed: 123 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44
import traceback
55
import uuid
6+
from collections import Counter
67

78
from flask import Flask, request, render_template, redirect, url_for
89

@@ -38,7 +39,51 @@
3839

3940
@app.context_processor
4041
def fronted_utilities():
41-
return dict(SPOOLMAN_BASE_URL=SPOOLMAN_BASE_URL, AUTO_SPEND=AUTO_SPEND, color_is_dark=color_is_dark, BASE_URL=BASE_URL, EXTERNAL_SPOOL_AMS_ID=EXTERNAL_SPOOL_AMS_ID, EXTERNAL_SPOOL_ID=EXTERNAL_SPOOL_ID, PRINTER_MODEL=mqtt_bambulab.getPrinterModel(), PRINTER_NAME=PRINTER_NAME)
42+
printer_model = mqtt_bambulab.getPrinterModel() or {}
43+
ams_models_by_id = mqtt_bambulab.getDetectedAmsModelsById()
44+
45+
return dict(
46+
SPOOLMAN_BASE_URL=SPOOLMAN_BASE_URL,
47+
AUTO_SPEND=AUTO_SPEND,
48+
AMS_MODELS_BY_ID=ams_models_by_id,
49+
color_is_dark=color_is_dark,
50+
BASE_URL=BASE_URL,
51+
EXTERNAL_SPOOL_AMS_ID=EXTERNAL_SPOOL_AMS_ID,
52+
EXTERNAL_SPOOL_ID=EXTERNAL_SPOOL_ID,
53+
PRINTER_MODEL=printer_model,
54+
PRINTER_NAME=PRINTER_NAME,
55+
)
56+
57+
58+
def build_ams_labels(ams_data):
59+
models_by_id = mqtt_bambulab.getDetectedAmsModelsById()
60+
base_labels = []
61+
for ams in ams_data:
62+
ams_id = ams.get("id")
63+
key = str(ams_id)
64+
base_name = models_by_id.get(key) or models_by_id.get(ams_id) or "AMS"
65+
base_labels.append(base_name)
66+
67+
totals = Counter(base_labels)
68+
takt = Counter()
69+
labels = {}
70+
71+
for ams, base_name in zip(ams_data, base_labels):
72+
takt[base_name] += 1
73+
suffix = f" {takt[base_name]}" if totals[base_name] > 1 else ""
74+
label = f"{base_name}{suffix}"
75+
ams_id = ams.get("id")
76+
labels[str(ams_id)] = label
77+
labels[ams_id] = label
78+
79+
return labels
80+
81+
82+
def _augment_tray(spool_list, tray_data, ams_id, tray_id):
83+
augmentTrayDataWithSpoolMan(spool_list, tray_data, ams_id, tray_id)
84+
if tray_data.get("unmapped_bambu_tag"):
85+
spoolman_service.clear_active_spool_for_tray(ams_id, tray_id)
86+
augmentTrayDataWithSpoolMan(spool_list, tray_data, ams_id, tray_id)
4287

4388
@app.route("/issue")
4489
def issue():
@@ -72,12 +117,12 @@ def issue():
72117

73118
active_spool = None
74119
for spool in spool_list:
75-
if spool.get("extra") and spool["extra"].get("active_tray") and spool["extra"]["active_tray"] == json.dumps(trayUid(ams_id, tray_id)):
120+
if spool.get("extra") and spool["extra"].get("active_tray") and spool["extra"].get("active_tray") == json.dumps(trayUid(ams_id, tray_id)):
76121
active_spool = spool
77122
break
78123

79124
if tray_data:
80-
augmentTrayDataWithSpoolMan(spool_list, tray_data, trayUid(ams_id, tray_id))
125+
_augment_tray(spool_list, tray_data, ams_id, tray_id)
81126

82127
#TODO: Determine issue
83128
#New bambulab spool
@@ -135,6 +180,73 @@ def fill():
135180

136181
return render_template('fill.html', spools=spools, ams_id=ams_id, tray_id=tray_id, materials=materials, selected_materials=selected_materials)
137182

183+
@app.route("/assign_bambu_spool")
184+
def assign_bambu_spool():
185+
if not mqtt_bambulab.isMqttClientConnected():
186+
return render_template('error.html', exception="MQTT is disconnected. Is the printer online?")
187+
188+
bambu_tag = request.args.get("tag")
189+
ams_id = request.args.get("ams")
190+
tray_id = request.args.get("tray")
191+
spool_id = request.args.get("spool_id")
192+
193+
if not all([bambu_tag, ams_id, tray_id]):
194+
return render_template('error.html', exception="Missing AMS ID, Tray ID, or Bambu spool tag.")
195+
196+
if bambu_tag == "00000000000000000000000000000000":
197+
return render_template('error.html', exception="No Bambu spool was detected in this tray.")
198+
199+
if spool_id:
200+
if READ_ONLY_MODE:
201+
return render_template('error.html', exception="Live read-only mode: linking Bambu spools is disabled.")
202+
203+
spool_data = spoolman_client.getSpoolById(spool_id)
204+
extras = spool_data.get("extra") or {}
205+
206+
spoolman_client.patchExtraTags(spool_id, extras, {
207+
"tag": json.dumps(bambu_tag),
208+
})
209+
210+
mqtt_bambulab.setActiveTray(spool_id, extras, ams_id, tray_id)
211+
setActiveSpool(ams_id, tray_id, spool_data)
212+
213+
return redirect(url_for('home', success_message=f"Linked Bambu spool to SpoolMan spool {spool_id} on AMS {ams_id}, Tray {tray_id}."))
214+
215+
spools = mqtt_bambulab.fetchSpools()
216+
materials = extract_materials(spools)
217+
selected_materials = []
218+
219+
try:
220+
last_ams_config = mqtt_bambulab.getLastAMSConfig()
221+
default_material = None
222+
223+
if ams_id == EXTERNAL_SPOOL_AMS_ID:
224+
default_material = last_ams_config.get("vt_tray", {}).get("tray_type")
225+
else:
226+
for ams in last_ams_config.get("ams", []):
227+
if str(ams.get("id")) != str(ams_id):
228+
continue
229+
230+
for tray in ams.get("tray", []):
231+
if str(tray.get("id")) == str(tray_id):
232+
default_material = tray.get("tray_type")
233+
break
234+
235+
if default_material and default_material in materials:
236+
selected_materials.append(default_material)
237+
except Exception:
238+
pass
239+
240+
return render_template(
241+
'assign_bambu_spool.html',
242+
spools=spools,
243+
ams_id=ams_id,
244+
tray_id=tray_id,
245+
bambu_tag=bambu_tag,
246+
materials=materials,
247+
selected_materials=selected_materials,
248+
)
249+
138250
@app.route("/spool_info")
139251
def spool_info():
140252
if not mqtt_bambulab.isMqttClientConnected():
@@ -150,12 +262,12 @@ def spool_info():
150262

151263
issue = False
152264
#TODO: Fix issue when external spool info is reset via bambulab interface
153-
augmentTrayDataWithSpoolMan(spool_list, vt_tray_data, trayUid(EXTERNAL_SPOOL_AMS_ID, EXTERNAL_SPOOL_ID))
265+
_augment_tray(spool_list, vt_tray_data, EXTERNAL_SPOOL_AMS_ID, EXTERNAL_SPOOL_ID)
154266
issue |= vt_tray_data.get("issue", False)
155267

156268
for ams in ams_data:
157269
for tray in ams["tray"]:
158-
augmentTrayDataWithSpoolMan(spool_list, tray, trayUid(ams["id"], tray["id"]))
270+
_augment_tray(spool_list, tray, ams["id"], tray["id"])
159271
issue |= tray.get("issue", False)
160272

161273
if not tag_id and not spool_id:
@@ -194,7 +306,8 @@ def spool_info():
194306
break
195307

196308
if current_spool:
197-
return render_template('spool_info.html', tag_id=tag_id, current_spool=current_spool, ams_data=ams_data, vt_tray_data=vt_tray_data, issue=issue)
309+
ams_labels = build_ams_labels(ams_data)
310+
return render_template('spool_info.html', tag_id=tag_id, current_spool=current_spool, ams_data=ams_data, vt_tray_data=vt_tray_data, issue=issue, ams_labels=ams_labels)
198311
else:
199312
return render_template('error.html', exception="Spool not found")
200313
except Exception as e:
@@ -302,15 +415,16 @@ def home():
302415

303416
issue = False
304417
#TODO: Fix issue when external spool info is reset via bambulab interface
305-
augmentTrayDataWithSpoolMan(spool_list, vt_tray_data, trayUid(EXTERNAL_SPOOL_AMS_ID, EXTERNAL_SPOOL_ID))
418+
_augment_tray(spool_list, vt_tray_data, EXTERNAL_SPOOL_AMS_ID, EXTERNAL_SPOOL_ID)
306419
issue |= vt_tray_data["issue"]
307420

308421
for ams in ams_data:
309422
for tray in ams["tray"]:
310-
augmentTrayDataWithSpoolMan(spool_list, tray, trayUid(ams["id"], tray["id"]))
423+
_augment_tray(spool_list, tray, ams["id"], tray["id"])
311424
issue |= tray["issue"]
312425

313-
return render_template('index.html', success_message=success_message, ams_data=ams_data, vt_tray_data=vt_tray_data, issue=issue)
426+
ams_labels = build_ams_labels(ams_data)
427+
return render_template('index.html', success_message=success_message, ams_data=ams_data, vt_tray_data=vt_tray_data, issue=issue, ams_labels=ams_labels)
314428
except Exception as e:
315429
traceback.print_exc()
316430
return render_template('error.html', exception=str(e))

config.env.template

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ PRINTER_IP=
66
PRINTER_NAME=
77
SPOOLMAN_BASE_URL=
88
AUTO_SPEND=False
9-
TRACK_LAYER_USAGE=False
10-
SPOOL_SORTING=filament.material:asc,filament.vendor.name:asc,filament.name:asc
9+
TRACK_LAYER_USAGE=False
10+
SPOOL_SORTING=filament.material:asc,filament.vendor.name:asc,filament.name:asc
11+
CLEAR_ASSIGNMENT_WHEN_EMPTY=False

config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,4 @@ def _env_to_bool(name: str, default: bool = False) -> bool:
3333
"SPOOL_SORTING", "filament.material:asc,filament.vendor.name:asc,filament.name:asc"
3434
)
3535
DISABLE_MISMATCH_WARNING = _env_to_bool("DISABLE_MISMATCH_WARNING", False)
36+
CLEAR_ASSIGNMENT_WHEN_EMPTY = _env_to_bool("CLEAR_ASSIGNMENT_WHEN_EMPTY", False)

docker-compose.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ services:
44
build: .
55
env_file: "config.env"
66
ports:
7-
- "8001:8001"
7+
- "8001:8000"
88
volumes:
99
- ./logs:/home/app/logs
1010
- ./data:/home/app/data

0 commit comments

Comments
 (0)