print_jobs.py 4.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. """Telling the printer's own internal jobs apart from a user's print.
  2. Bambu firmware runs jobs on its own behalf -- bed levelling, vibration
  3. compensation, the pressure-advance line it lays down before a print when flow
  4. dynamics calibration is on -- and reports them over MQTT through exactly the
  5. same print-start and print-complete events a real print uses. Nothing about the
  6. event says "this one is mine": Bambuddy has to recognise the job by name.
  7. Getting that wrong is not free. An unrecognised calibration run has no 3MF
  8. anywhere on the printer, so the archive path sweeps FTP for a file that cannot
  9. exist -- six candidate names across five directories, with retries -- and then
  10. writes a no-3MF archive named after the calibration, on a printer that is in
  11. the middle of calibrating.
  12. Kept as a leaf module with no imports of its own so both the print-start and
  13. print-complete callbacks can share one answer.
  14. """
  15. # Job names the printer runs for itself. Matched exactly (after normalising),
  16. # not by prefix or substring: "auto" and "calib" are ordinary words in a user's
  17. # own filenames, and a rule loose enough to catch an unnamed future calibration
  18. # would silently swallow somebody's print.
  19. #
  20. # ``auto_cali_for_user`` is the bed-levelling / vibration run, normally reported
  21. # with a ``/usr/etc/print/`` path that the rule below catches on its own; it is
  22. # listed anyway because the path is not guaranteed and a name-only report of it
  23. # would otherwise slip through.
  24. #
  25. # ``auto_pa_line_calib_mode`` is the pressure-advance (K profile) line. This one
  26. # is reported as a *subtask name* with no ``/usr/`` path at all, which is why
  27. # the path rule alone was never enough.
  28. #
  29. # ``pa_line_calib_mode`` and ``pa_pattern_calib_mode`` are the same calibration
  30. # started by hand rather than automatically before a print. Manual flow dynamics
  31. # offers both shapes -- a line and a pattern -- and each reports under its own
  32. # name with no ``auto_`` prefix, so neither is covered by the automatic entry
  33. # above and both produced the same no-3MF archive. They are listed as two
  34. # literals rather than matched by a shared ``pa_`` stem for the reason the whole
  35. # set is exact: a stem rule would also swallow a user's own ``pa_bracket.3mf``.
  36. INTERNAL_JOB_NAMES = frozenset(
  37. {
  38. "auto_cali_for_user",
  39. # A1, A1 Mini, P1 and A2L report their built-in calibration under this
  40. # bare name, with no /usr/ path (support bundles, A1 firmware 01.08).
  41. "auto_cali_for_user_param",
  42. "auto_pa_line_calib_mode",
  43. "pa_line_calib_mode",
  44. "pa_pattern_calib_mode",
  45. }
  46. )
  47. # Longest first: ``.gcode.3mf`` has to be stripped whole, or ``.3mf`` would
  48. # match first and leave a trailing ``.gcode`` behind.
  49. _PRINT_SUFFIXES = (".gcode.3mf", ".gcode", ".3mf")
  50. def _normalize_job_name(value: str) -> str:
  51. """Reduce a reported name to something comparable against the set above.
  52. Drops any directory part, one print-file suffix, and case. The printer is
  53. not consistent about which of these it includes -- the same calibration
  54. reports as a bare name in ``subtask_name`` and, when it appears at all, as
  55. a full path in ``gcode_file``.
  56. """
  57. name = value.rsplit("/", 1)[-1].rsplit("\\", 1)[-1].strip().casefold()
  58. for suffix in _PRINT_SUFFIXES:
  59. if name.endswith(suffix):
  60. return name[: -len(suffix)]
  61. return name
  62. def is_internal_printer_job(filename: str | None, subtask_name: str | None = None) -> bool:
  63. """True when this print event belongs to the printer, not to a user.
  64. Both fields are tested because neither is reliably populated: the
  65. pressure-advance line arrives as a subtask name with no filename, while the
  66. levelling run arrives as a ``/usr/etc/print/...`` path. A job is internal if
  67. *either* field says so.
  68. """
  69. if filename and filename.startswith("/usr/"):
  70. # Bambu keeps its own calibration gcode on the read-only system
  71. # partition. Nothing a user can print ever lives there, so the whole
  72. # prefix is safe to treat as internal without naming each file.
  73. return True
  74. return any(_normalize_job_name(value) in INTERNAL_JOB_NAMES for value in (filename, subtask_name) if value)