Skip to content

Commit 63fc2f6

Browse files
committed
fixed local print in lan only mode
1 parent fad70b9 commit 63fc2f6

3 files changed

Lines changed: 302 additions & 140 deletions

File tree

mqtt_bambulab.py

Lines changed: 214 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11

22

3-
import json
4-
import ssl
5-
import traceback
6-
from threading import Thread
7-
from typing import Any, Iterable
3+
import json
4+
import ssl
5+
import traceback
6+
from threading import Thread
7+
from typing import Any, Iterable
8+
from urllib.parse import urlparse
89

910
import paho.mqtt.client as mqtt
1011

@@ -44,9 +45,49 @@
4445

4546
PENDING_PRINT_METADATA = {}
4647
FILAMENT_TRACKER = FilamentUsageTracker()
47-
LOG_FILE = "/home/app/logs/mqtt.log"
48-
49-
def getPrinterModel():
48+
LOG_FILE = "/home/app/logs/mqtt.log"
49+
50+
def _is_local_project_file(print_obj: dict | None) -> bool:
51+
if not print_obj:
52+
return False
53+
if print_obj.get("print_type") == "local":
54+
return True
55+
url = print_obj.get("url") or ""
56+
if not url:
57+
return False
58+
scheme = urlparse(url).scheme
59+
return scheme in ("file", "local", "ftp", "ftps")
60+
61+
def _load_project_metadata(url: str) -> dict | None:
62+
metadata = getMetaDataFrom3mf(url)
63+
if not metadata or not metadata.get("filaments"):
64+
log("[filament-tracker] No metadata/filaments found in 3MF; skipping tracking for this job")
65+
return None
66+
metadata["metadata_loaded"] = True
67+
return metadata
68+
69+
def _insert_filament_usage_entries(print_id, filaments: dict) -> None:
70+
for id, filament in filaments.items():
71+
parsed_grams = _parse_grams(filament.get("used_g"))
72+
parsed_length_m = _parse_grams(filament.get("used_m"))
73+
estimated_length_mm = parsed_length_m * 1000 if parsed_length_m is not None else None
74+
grams_used = parsed_grams if parsed_grams is not None else 0.0
75+
length_used = estimated_length_mm if estimated_length_mm is not None else 0.0
76+
if TRACK_LAYER_USAGE:
77+
grams_used = 0.0
78+
length_used = 0.0
79+
insert_filament_usage(
80+
print_id,
81+
filament["type"],
82+
filament["color"],
83+
grams_used,
84+
id,
85+
estimated_grams=parsed_grams,
86+
length_used=length_used,
87+
estimated_length=estimated_length_mm,
88+
)
89+
90+
def getPrinterModel():
5091
global PRINTER_ID
5192
model_code = PRINTER_ID[:3]
5293

@@ -265,71 +306,84 @@ def processMessage(data):
265306
global LAST_AMS_CONFIG, PRINTER_STATE, PRINTER_STATE_LAST, PENDING_PRINT_METADATA
266307

267308
# Prepare AMS spending estimation
268-
if "print" in data:
269-
update_dict(PRINTER_STATE, data)
270-
309+
if "print" in data:
310+
update_dict(PRINTER_STATE, data)
311+
312+
# Handle project_file events (3MF metadata load) separately for local vs cloud jobs.
271313
if data["print"].get("command") == "project_file" and data["print"].get("url"):
272-
PENDING_PRINT_METADATA = getMetaDataFrom3mf(data["print"]["url"])
273-
if not PENDING_PRINT_METADATA or not PENDING_PRINT_METADATA.get("filaments"):
274-
log("[filament-tracker] No metadata/filaments found in 3MF; skipping tracking for this job")
314+
# Local project_file: stash metadata for the local flow and wait for RUNNING.
315+
if _is_local_project_file(data["print"]):
316+
metadata = _load_project_metadata(data["print"]["url"])
317+
if metadata is None:
318+
PENDING_PRINT_METADATA = {}
319+
PRINTER_STATE_LAST = copy.deepcopy(PRINTER_STATE)
320+
return
321+
PENDING_PRINT_METADATA = metadata
322+
PENDING_PRINT_METADATA["print_type"] = "local"
323+
PENDING_PRINT_METADATA["task_id"] = PRINTER_STATE["print"].get("task_id")
324+
PENDING_PRINT_METADATA["subtask_id"] = PRINTER_STATE["print"].get("subtask_id")
325+
326+
normalized = normalize_ams_mapping2(
327+
PRINTER_STATE["print"].get("ams_mapping2"),
328+
PRINTER_STATE["print"].get("ams_mapping"),
329+
)
330+
if normalized:
331+
PENDING_PRINT_METADATA["ams_mapping"] = normalized
332+
PENDING_PRINT_METADATA["ams_mapping2"] = normalized
333+
PRINTER_STATE_LAST = copy.deepcopy(PRINTER_STATE)
334+
return
335+
336+
# Cloud project_file: fully initialize tracking and upfront usage records.
337+
metadata = _load_project_metadata(data["print"]["url"])
338+
if metadata is None:
275339
PENDING_PRINT_METADATA = {}
276340
return
277-
PENDING_PRINT_METADATA["metadata_loaded"] = True
341+
PENDING_PRINT_METADATA = metadata
278342
PENDING_PRINT_METADATA["print_type"] = PRINTER_STATE["print"].get("print_type")
279343
PENDING_PRINT_METADATA["task_id"] = PRINTER_STATE["print"].get("task_id")
280344
PENDING_PRINT_METADATA["subtask_id"] = PRINTER_STATE["print"].get("subtask_id")
281345
if TRACK_LAYER_USAGE:
282-
FILAMENT_TRACKER.set_print_metadata(PENDING_PRINT_METADATA)
283-
284-
print_id = insert_print(PRINTER_STATE["print"]["subtask_name"], "cloud", PENDING_PRINT_METADATA["image"])
285-
286-
normalized = normalize_ams_mapping2(
287-
PRINTER_STATE["print"].get("ams_mapping2"),
288-
PRINTER_STATE["print"].get("ams_mapping"),
289-
)
290-
use_ams_flag = PRINTER_STATE["print"].get("use_ams")
291-
use_ams = bool(use_ams_flag) if use_ams_flag is not None else bool(normalized)
292-
if not use_ams:
293-
normalized = [normalize_ams_mapping_entry(EXTERNAL_SPOOL_ID)]
294-
295-
PENDING_PRINT_METADATA["ams_mapping"] = normalized
296-
PENDING_PRINT_METADATA["ams_mapping2"] = normalized
297-
298-
PENDING_PRINT_METADATA["print_id"] = print_id
299-
PENDING_PRINT_METADATA["complete"] = True
300-
301-
for id, filament in PENDING_PRINT_METADATA["filaments"].items():
302-
parsed_grams = _parse_grams(filament.get("used_g"))
303-
parsed_length_m = _parse_grams(filament.get("used_m"))
304-
estimated_length_mm = parsed_length_m * 1000 if parsed_length_m is not None else None
305-
grams_used = parsed_grams if parsed_grams is not None else 0.0
306-
length_used = estimated_length_mm if estimated_length_mm is not None else 0.0
307-
if TRACK_LAYER_USAGE:
308-
grams_used = 0.0
309-
length_used = 0.0
310-
insert_filament_usage(
311-
print_id,
312-
filament["type"],
313-
filament["color"],
314-
grams_used,
315-
id,
316-
estimated_grams=parsed_grams,
317-
length_used=length_used,
318-
estimated_length=estimated_length_mm,
319-
)
346+
FILAMENT_TRACKER.set_print_metadata(PENDING_PRINT_METADATA)
347+
348+
print_id = insert_print(PRINTER_STATE["print"]["subtask_name"], "cloud", PENDING_PRINT_METADATA["image"])
349+
350+
normalized = normalize_ams_mapping2(
351+
PRINTER_STATE["print"].get("ams_mapping2"),
352+
PRINTER_STATE["print"].get("ams_mapping"),
353+
)
354+
use_ams_flag = PRINTER_STATE["print"].get("use_ams")
355+
use_ams = bool(use_ams_flag) if use_ams_flag is not None else bool(normalized)
356+
if not use_ams:
357+
normalized = [normalize_ams_mapping_entry(EXTERNAL_SPOOL_ID)]
358+
359+
PENDING_PRINT_METADATA["ams_mapping"] = normalized
360+
PENDING_PRINT_METADATA["ams_mapping2"] = normalized
361+
362+
PENDING_PRINT_METADATA["print_id"] = print_id
363+
PENDING_PRINT_METADATA["complete"] = True
364+
365+
_insert_filament_usage_entries(print_id, PENDING_PRINT_METADATA["filaments"])
320366

321367
#if ("gcode_state" in data["print"] and data["print"]["gcode_state"] == "RUNNING") and ("print_type" in data["print"] and data["print"]["print_type"] != "local") \
322368
# and ("tray_tar" in data["print"] and data["print"]["tray_tar"] != "255") and ("stg_cur" in data["print"] and data["print"]["stg_cur"] == 0 and PRINT_CURRENT_STAGE != 0):
323369

324370
#TODO: What happens when printed from external spool, is ams and tray_tar set?
325-
if PRINTER_STATE.get("print", {}).get("print_type") == "local" and PRINTER_STATE_LAST.get("print"):
326-
327-
if (
328-
PRINTER_STATE["print"].get("gcode_state") == "RUNNING" and
329-
PRINTER_STATE_LAST["print"].get("gcode_state") == "PREPARE" and
330-
PRINTER_STATE["print"].get("gcode_file")
331-
):
332-
371+
# Local print flow: start tracking once the job is RUNNING and metadata is available.
372+
if PRINTER_STATE.get("print", {}).get("print_type") == "local" and PRINTER_STATE_LAST.get("print"):
373+
if (
374+
PRINTER_STATE["print"].get("gcode_state") == "RUNNING" and
375+
PRINTER_STATE["print"].get("gcode_file") and
376+
(
377+
PRINTER_STATE_LAST["print"].get("gcode_state") == "PREPARE" or
378+
(
379+
PENDING_PRINT_METADATA
380+
and PENDING_PRINT_METADATA.get("metadata_loaded")
381+
and not PENDING_PRINT_METADATA.get("tracking_started")
382+
)
383+
)
384+
):
385+
386+
# Ensure metadata is loaded from the 3MF before starting tracking.
333387
if not PENDING_PRINT_METADATA or not PENDING_PRINT_METADATA.get("metadata_loaded"):
334388
PENDING_PRINT_METADATA = getMetaDataFrom3mf(PRINTER_STATE["print"]["gcode_file"])
335389
if PENDING_PRINT_METADATA and PENDING_PRINT_METADATA.get("filaments"):
@@ -338,86 +392,106 @@ def processMessage(data):
338392
PENDING_PRINT_METADATA["print_type"] = PRINTER_STATE["print"].get("print_type")
339393
PENDING_PRINT_METADATA["task_id"] = PRINTER_STATE["print"].get("task_id")
340394
PENDING_PRINT_METADATA["subtask_id"] = PRINTER_STATE["print"].get("subtask_id")
341-
342-
if not PENDING_PRINT_METADATA.get("tracking_started"):
343-
print_id = insert_print(PENDING_PRINT_METADATA["file"], PRINTER_STATE["print"]["print_type"], PENDING_PRINT_METADATA["image"])
344-
345-
PENDING_PRINT_METADATA["ams_mapping"] = []
346-
PENDING_PRINT_METADATA["ams_mapping2"] = []
347-
PENDING_PRINT_METADATA["filamentChanges"] = []
348-
PENDING_PRINT_METADATA["assigned_trays"] = []
349-
PENDING_PRINT_METADATA["complete"] = False
350-
PENDING_PRINT_METADATA["print_id"] = print_id
351-
FILAMENT_TRACKER.start_local_print_from_metadata(PENDING_PRINT_METADATA)
352-
353-
for id, filament in PENDING_PRINT_METADATA["filaments"].items():
354-
parsed_grams = _parse_grams(filament.get("used_g"))
355-
parsed_length_m = _parse_grams(filament.get("used_m"))
356-
estimated_length_mm = parsed_length_m * 1000 if parsed_length_m is not None else None
357-
grams_used = parsed_grams if parsed_grams is not None else 0.0
358-
length_used = estimated_length_mm if estimated_length_mm is not None else 0.0
359-
if TRACK_LAYER_USAGE:
360-
grams_used = 0.0
361-
length_used = 0.0
362-
insert_filament_usage(
363-
print_id,
364-
filament["type"],
365-
filament["color"],
366-
grams_used,
367-
id,
368-
estimated_grams=parsed_grams,
369-
length_used=length_used,
370-
estimated_length=estimated_length_mm,
371-
)
372-
373-
PENDING_PRINT_METADATA["tracking_started"] = True
374-
375-
#TODO
395+
396+
# Start tracking once per job, using AMS mapping when available.
397+
if not PENDING_PRINT_METADATA.get("tracking_started"):
398+
if FILAMENT_TRACKER.active_model is not None:
399+
PENDING_PRINT_METADATA["tracking_started"] = True
400+
else:
401+
print_id = PENDING_PRINT_METADATA.get("print_id")
402+
if not print_id:
403+
print_id = insert_print(PENDING_PRINT_METADATA["file"], PRINTER_STATE["print"]["print_type"], PENDING_PRINT_METADATA["image"])
404+
405+
normalized = normalize_ams_mapping2(
406+
PRINTER_STATE["print"].get("ams_mapping2"),
407+
PRINTER_STATE["print"].get("ams_mapping"),
408+
)
409+
use_ams_flag = PRINTER_STATE["print"].get("use_ams")
410+
has_mapping = any(entry is not None for entry in normalized)
411+
use_ams = bool(use_ams_flag) if use_ams_flag is not None else has_mapping
412+
if use_ams and normalized:
413+
PENDING_PRINT_METADATA["ams_mapping"] = normalized
414+
PENDING_PRINT_METADATA["ams_mapping2"] = normalized
415+
else:
416+
PENDING_PRINT_METADATA.setdefault("ams_mapping", [])
417+
PENDING_PRINT_METADATA.setdefault("ams_mapping2", [])
418+
419+
PENDING_PRINT_METADATA["filamentChanges"] = []
420+
PENDING_PRINT_METADATA["assigned_trays"] = []
421+
422+
filament_order = PENDING_PRINT_METADATA.get("filamentOrder") or {}
423+
target_filaments = set()
424+
for filament_id in filament_order.keys():
425+
try:
426+
target_filaments.add(int(filament_id))
427+
except (TypeError, ValueError):
428+
continue
429+
assigned_filaments = {
430+
idx for idx, tray in enumerate(PENDING_PRINT_METADATA.get("ams_mapping") or [])
431+
if tray is not None
432+
}
433+
if target_filaments:
434+
mapping_complete = target_filaments.issubset(assigned_filaments)
435+
else:
436+
mapping_complete = any(
437+
tray is not None for tray in (PENDING_PRINT_METADATA.get("ams_mapping") or [])
438+
)
439+
PENDING_PRINT_METADATA["complete"] = mapping_complete
440+
PENDING_PRINT_METADATA["print_id"] = print_id
441+
FILAMENT_TRACKER.start_local_print_from_metadata(PENDING_PRINT_METADATA)
442+
443+
_insert_filament_usage_entries(print_id, PENDING_PRINT_METADATA["filaments"])
444+
445+
PENDING_PRINT_METADATA["tracking_started"] = True
446+
447+
#TODO
376448

377-
# When stage changed to "change filament" and PENDING_PRINT_METADATA is set
378-
if (PENDING_PRINT_METADATA and
379-
(
380-
(
381-
int(PRINTER_STATE["print"].get("stg_cur", -1)) == 4 and # change filament stage (beginning of print)
382-
(
383-
PRINTER_STATE_LAST["print"].get("stg_cur", -1) == -1 or # last stage not known
384-
(
385-
int(PRINTER_STATE_LAST["print"].get("stg_cur")) != int(PRINTER_STATE["print"].get("stg_cur")) and
386-
PRINTER_STATE_LAST["print"].get("ams", {}).get("tray_tar") == "255" # stage has changed and last state was 255 (retract to ams)
387-
)
388-
or not PRINTER_STATE_LAST["print"].get("ams") # ams not set in last state
389-
)
390-
)
391-
or # filament changes during printing are in mc_print_sub_stage
392-
(
393-
int(PRINTER_STATE_LAST["print"].get("mc_print_sub_stage", -1)) == 4 # last state was change filament
394-
and int(PRINTER_STATE["print"].get("mc_print_sub_stage", -1)) == 2 # current state
395-
)
396-
or (
397-
PRINTER_STATE["print"].get("ams", {}).get("tray_tar") == "254"
398-
)
399-
or
400-
(
401-
int(PRINTER_STATE["print"].get("stg_cur", -1)) == 24 and int(PRINTER_STATE_LAST["print"].get("stg_cur", -1)) == 13
402-
)
403-
or (
404-
int(PRINTER_STATE["print"].get("stg_cur", -1)) == 4 and
405-
PRINTER_STATE["print"].get("ams", {}).get("tray_tar") not in (None, "255") and
406-
(PRINTER_STATE_LAST["print"].get("ams", {}).get("tray_tar") is None or PRINTER_STATE_LAST["print"].get("ams", {}).get("tray_tar") != PRINTER_STATE["print"].get("ams", {}).get("tray_tar"))
407-
)
408-
409-
)
410-
):
411-
if PRINTER_STATE["print"].get("ams"):
412-
mapped = False
413-
tray_tar_value = PRINTER_STATE["print"].get("ams").get("tray_tar")
414-
if tray_tar_value and tray_tar_value != "255":
415-
mapped = map_filament(int(tray_tar_value))
416-
FILAMENT_TRACKER.apply_ams_mapping(PENDING_PRINT_METADATA.get("ams_mapping") or [])
417-
if mapped:
418-
PENDING_PRINT_METADATA["complete"] = True
419-
420-
if PENDING_PRINT_METADATA and PENDING_PRINT_METADATA.get("complete"):
449+
# Update AMS mapping once the printer reports concrete tray assignments.
450+
# When stage changed to "change filament" and PENDING_PRINT_METADATA is set
451+
if (PENDING_PRINT_METADATA and
452+
(
453+
(
454+
int(PRINTER_STATE["print"].get("stg_cur", -1)) == 4 and # change filament stage (beginning of print)
455+
(
456+
PRINTER_STATE_LAST["print"].get("stg_cur", -1) == -1 or # last stage not known
457+
(
458+
int(PRINTER_STATE_LAST["print"].get("stg_cur")) != int(PRINTER_STATE["print"].get("stg_cur")) and
459+
PRINTER_STATE_LAST["print"].get("ams", {}).get("tray_tar") == "255" # stage has changed and last state was 255 (retract to ams)
460+
)
461+
or not PRINTER_STATE_LAST["print"].get("ams") # ams not set in last state
462+
)
463+
)
464+
or # filament changes during printing are in mc_print_sub_stage
465+
(
466+
int(PRINTER_STATE_LAST["print"].get("mc_print_sub_stage", -1)) == 4 # last state was change filament
467+
and int(PRINTER_STATE["print"].get("mc_print_sub_stage", -1)) == 2 # current state
468+
)
469+
or (
470+
PRINTER_STATE["print"].get("ams", {}).get("tray_tar") == "254"
471+
)
472+
or
473+
(
474+
int(PRINTER_STATE["print"].get("stg_cur", -1)) == 24 and int(PRINTER_STATE_LAST["print"].get("stg_cur", -1)) == 13
475+
)
476+
or (
477+
int(PRINTER_STATE["print"].get("stg_cur", -1)) == 4 and
478+
PRINTER_STATE["print"].get("ams", {}).get("tray_tar") not in (None, "255") and
479+
(PRINTER_STATE_LAST["print"].get("ams", {}).get("tray_tar") is None or PRINTER_STATE_LAST["print"].get("ams", {}).get("tray_tar") != PRINTER_STATE["print"].get("ams", {}).get("tray_tar"))
480+
)
481+
482+
)
483+
):
484+
if PRINTER_STATE["print"].get("ams"):
485+
mapped = False
486+
tray_tar_value = PRINTER_STATE["print"].get("ams").get("tray_tar")
487+
if tray_tar_value and tray_tar_value != "255":
488+
mapped = map_filament(int(tray_tar_value))
489+
FILAMENT_TRACKER.apply_ams_mapping(PENDING_PRINT_METADATA.get("ams_mapping") or [])
490+
if mapped:
491+
PENDING_PRINT_METADATA["complete"] = True
492+
493+
# Finalize or spend once metadata is complete.
494+
if PENDING_PRINT_METADATA and PENDING_PRINT_METADATA.get("complete"):
421495
if not PENDING_PRINT_METADATA.get("complete_handled"):
422496
if TRACK_LAYER_USAGE:
423497
if PENDING_PRINT_METADATA.get("print_type") == "local":

0 commit comments

Comments
 (0)