test_use_ams_no_ams_3356.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. """A printer with no AMS gets use_ams=False (#3356).
  2. A P1S with no AMS and nothing set on its spool holder: the matcher had nothing
  3. to match, so the print went out with no mapping and use_ams=True, which the
  4. firmware refuses with 07FF_8012 "Failed to get AMS mapping table". The
  5. mapping-based reconcile (#2595) never ran without a mapping. The printer's own
  6. push says it has no AMS (``"ams": []``, from the reporter's support package),
  7. so the command builder sends use_ams=False, as the slicer does.
  8. """
  9. import json
  10. from unittest.mock import MagicMock
  11. import pytest
  12. from backend.app.services.bambu_mqtt import BambuMQTTClient
  13. @pytest.fixture
  14. def client():
  15. client = BambuMQTTClient(ip_address="192.168.1.100", serial_number="01P00A000000001", access_code="12345678")
  16. client.model = "P1S"
  17. client._client = MagicMock()
  18. client.state.connected = True
  19. return client
  20. def _use_ams(client) -> bool:
  21. assert client._client.publish.called, "start_print did not publish"
  22. return json.loads(client._client.publish.call_args.args[1])["print"]["use_ams"]
  23. def _no_ams(client):
  24. client._handle_ams_data({"ams": []})
  25. def test_no_ams_and_no_mapping(client):
  26. _no_ams(client)
  27. assert client.start_print("case_body.3mf", ams_mapping=None, use_ams=True)
  28. assert _use_ams(client) is False
  29. @pytest.mark.parametrize("mapping", [[254], [-1], [-1, 254]])
  30. def test_no_ams_with_a_mapping(client, mapping):
  31. _no_ams(client)
  32. assert client.start_print("case_body.3mf", ams_mapping=mapping, use_ams=True)
  33. assert _use_ams(client) is False
  34. def test_not_heard_from_the_ams_yet(client):
  35. """No AMS push handled yet (just reconnected): the AMS may be there."""
  36. assert "ams" not in client.state.raw_data
  37. assert client.start_print("case_body.3mf", ams_mapping=None, use_ams=True)
  38. assert _use_ams(client) is True
  39. def test_with_an_ams(client):
  40. client._handle_ams_data({"ams": [{"id": "0", "tray": [{"id": "0"}, {"id": "1"}, {"id": "2"}, {"id": "3"}]}]})
  41. assert client.start_print("case_body.3mf", ams_mapping=None, use_ams=True)
  42. assert _use_ams(client) is True
  43. def test_dual_nozzle_is_left_alone(client):
  44. """On an H2D use_ams is nozzle routing, not an AMS on/off flag."""
  45. client.model = "H2D"
  46. _no_ams(client)
  47. assert client.start_print("case_body.3mf", ams_mapping=None, use_ams=True)
  48. assert _use_ams(client) is True
  49. # The AMS blocks below are shaped like Martin's X1C and H2C captures: the unit
  50. # list and ams_exist_bits arrive together in every push.
  51. def _unit(unit_id, trays=4):
  52. return {"id": str(unit_id), "tray": [{"id": str(t), "tray_type": "PLA"} for t in range(trays)]}
  53. def _push(units, bits):
  54. return {"ams": units, "ams_exist_bits": bits, "tray_now": "255", "power_on_flag": False}
  55. def _unit_ids(client):
  56. return sorted((u["id"] for u in client.state.raw_data["ams"]), key=int)
  57. class TestAnUnpluggedAms:
  58. def test_the_x1c_unplug_and_replug(self, client):
  59. client.model = "X1C"
  60. client._handle_ams_data(_push([_unit(1)], "2"))
  61. client._handle_ams_data(_push([], "0"))
  62. assert _unit_ids(client) == []
  63. client._handle_ams_data(_push([_unit(1)], "2"))
  64. assert _unit_ids(client) == ["1"]
  65. def test_printing_from_the_spool_holder_after_the_unplug(self, client):
  66. """No restart needed: the no-AMS rule sees the unplug."""
  67. client._handle_ams_data(_push([_unit(0)], "1"))
  68. client._handle_ams_data(_push([], "0"))
  69. assert client.start_print("case_body.3mf", ams_mapping=None, use_ams=True)
  70. assert _use_ams(client) is False
  71. def test_a_unit_whose_bit_is_cleared_goes(self, client):
  72. client.model = "H2C"
  73. client._handle_ams_data(_push([_unit(0), _unit(1), _unit(2), _unit(128, 1)], "17"))
  74. client._handle_ams_data(_push([_unit(0), _unit(2), _unit(128, 1)], "15"))
  75. assert _unit_ids(client) == ["0", "2", "128"]
  76. def test_a_partial_list_keeps_the_units_it_leaves_out(self, client):
  77. client.model = "H2C"
  78. client._handle_ams_data(_push([_unit(0), _unit(1), _unit(2), _unit(128, 1)], "17"))
  79. client._handle_ams_data(_push([_unit(0)], "17"))
  80. assert _unit_ids(client) == ["0", "1", "2", "128"]
  81. def test_an_ams_ht_next_to_other_units_is_kept(self, client):
  82. """Its bit is not read: measured once, on one printer."""
  83. client.model = "H2C"
  84. client._handle_ams_data(_push([_unit(0), _unit(128, 1)], "11"))
  85. client._handle_ams_data(_push([_unit(0)], "1"))
  86. assert _unit_ids(client) == ["0", "128"]
  87. def test_the_last_ams_ht_goes_with_everything_else(self, client):
  88. client._handle_ams_data(_push([_unit(128, 1)], "10"))
  89. client._handle_ams_data(_push([], "0"))
  90. assert _unit_ids(client) == []
  91. def test_a_push_without_bits_changes_nothing(self, client):
  92. client._handle_ams_data(_push([_unit(0)], "1"))
  93. client._handle_ams_data({"ams": []})
  94. assert _unit_ids(client) == ["0"]
  95. def test_no_bits_next_to_a_listed_unit_keeps_an_ams_ht(self, client):
  96. """All-zero bits while units are still listed is the shutdown pattern
  97. (#765), not a report that the AMS HT is gone."""
  98. client._handle_ams_data(_push([_unit(0), _unit(128, 1)], "11"))
  99. client._handle_ams_data(_push([_unit(0)], "0"))
  100. assert _unit_ids(client) == ["0", "128"]