ams_slot_presence.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
  1. """Is there a spool in this AMS slot?
  2. Three backend decisions turn on the answer -- whether to push
  3. ``ams_filament_setting`` when a spool is assigned, whether a pre-assigned slot
  4. has just been filled, and whether an assignment has gone stale -- and all three
  5. used to read it off the tray's ``state`` field. That field cannot carry it.
  6. ## Why ``state`` is the wrong source
  7. ``state`` is firmware-variant. The A1 Mini BMCU and the P1S Standard AMS report
  8. 3 for a loaded slot and never emit 11; the AMS-HT's codes differ again (#2670).
  9. Bambuddy already works around that on the printer card, where
  10. ``getEmptySlotKind`` (``PrintersPage.tsx``) reads the presence bit first and
  11. only falls back to the 9/10 heuristic when there is no bit to read.
  12. Worse, ``state`` is partly Bambuddy's own writing. ``apply_tray_exist_bits``
  13. sets ``state = 9`` on every slot whose presence bit is 0 -- and when the bit
  14. comes back it leaves the 9 exactly where it was, because the "slot occupied"
  15. branch only annotates ``exists`` and moves on. So a slot the firmware says is
  16. full can sit in the cache reading ``exists=True, state=9`` indefinitely. That
  17. is #3084: a non-Bambu spool is swapped in, Assign Spool reads the stale 9,
  18. calls the slot empty, sends no MQTT, and the printer keeps showing ``?``. The
  19. deferred-configuration replay could not rescue it either, because its own
  20. "loaded" test was the same 9/10 heuristic -- the exact deadlock #1322 removed
  21. elsewhere. #3100 is the same stale 9 one step further on: the replay does not
  22. fire, the assignment keeps the empty fingerprint it was stored with, and the
  23. first real tray report is read as a spool swap and deleted.
  24. ## What this module answers
  25. ``tray_exist_bits`` is the firmware's own "which slots have a spool" bitmask --
  26. the one BambuStudio draws its ``?`` from -- and ``apply_tray_exist_bits``
  27. records it per tray as ``exists``. That bit is authoritative where it exists,
  28. and absent otherwise; it is never a guess. Callers that want a decision for a
  29. payload carrying no bit at all keep their own fallback, because the right
  30. fallback differs per caller: the assign path wants to know whether the push is
  31. doomed, the unlink pass wants to know whether a spool was removed, and a state
  32. of 26 ("unloaded", mid-runout) answers those two questions differently.
  33. """
  34. from collections.abc import Mapping
  35. from typing import Any
  36. def spool_present(tray: Mapping[str, Any] | None) -> bool | None:
  37. """Does firmware's presence bit say a spool is in this slot?
  38. ``True`` / ``False`` straight from ``tray_exist_bits``; ``None`` when the
  39. tray carries no presence annotation, which means the caller has to decide
  40. on its own terms rather than assume either way.
  41. Only the internal AMS path annotates ``exists`` (``apply_tray_exist_bits``
  42. is called with ``annotate_exists=True`` there and False for the VP bridge),
  43. so the external spool's ``vt_tray`` entries answer ``None`` -- they have no
  44. bit in the mask.
  45. """
  46. if not isinstance(tray, Mapping):
  47. return None
  48. exists = tray.get("exists")
  49. return exists if isinstance(exists, bool) else None