|
@@ -0,0 +1,86 @@
|
|
|
|
|
+"""A partial vt_tray push updates the external slot instead of replacing it (#3513).
|
|
|
|
|
+
|
|
|
|
|
+The A1 and A1 mini report a change to the external spool as a delta with only
|
|
|
|
|
+the changed keys, often without the slot id: a colour-only change pushes just
|
|
|
|
|
+``{"tray_color": ...}``. Stored as it came, it emptied the slot (no type, no
|
|
|
|
|
+preset) until the next full push, and without its id the assignment check
|
|
|
|
|
+could not find slot 254 and timed out on the old preset.
|
|
|
|
|
+"""
|
|
|
|
|
+
|
|
|
|
|
+from unittest.mock import MagicMock
|
|
|
|
|
+
|
|
|
|
|
+import pytest
|
|
|
|
|
+
|
|
|
|
|
+from backend.app.services.bambu_mqtt import BambuMQTTClient
|
|
|
|
|
+
|
|
|
|
|
+pytestmark = pytest.mark.unit
|
|
|
|
|
+
|
|
|
|
|
+# The reporter's slot after a full push.
|
|
|
|
|
+FULL = {
|
|
|
|
|
+ "id": "254",
|
|
|
|
|
+ "tray_type": "PLA",
|
|
|
|
|
+ "tray_sub_brands": "",
|
|
|
|
|
+ "tray_color": "C0C0C0FF",
|
|
|
|
|
+ "tray_info_idx": "P0ea739d",
|
|
|
|
|
+ "nozzle_temp_min": "190",
|
|
|
|
|
+ "nozzle_temp_max": "230",
|
|
|
|
|
+ "k": 0.02,
|
|
|
|
|
+ "cali_idx": 5,
|
|
|
|
|
+ "remain": 0,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _client(model: str = "A1 mini") -> BambuMQTTClient:
|
|
|
|
|
+ client = BambuMQTTClient(ip_address="10.0.0.1", serial_number="SN", access_code="c", model=model)
|
|
|
|
|
+ client._process_message({"print": {"command": "push_status", "vt_tray": dict(FULL)}})
|
|
|
|
|
+ return client
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _push(client: BambuMQTTClient, vt_tray) -> dict:
|
|
|
|
|
+ client._process_message({"print": {"command": "push_status", "vt_tray": vt_tray}})
|
|
|
|
|
+ return client.state.raw_data["vt_tray"]
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class TestTheDelta:
|
|
|
|
|
+ def test_a_colour_only_change_keeps_the_rest_of_the_slot(self):
|
|
|
|
|
+ client = _client()
|
|
|
|
|
+
|
|
|
|
|
+ slots = _push(client, {"tray_color": "FFFFFFFF"})
|
|
|
|
|
+
|
|
|
|
|
+ assert slots == [{**FULL, "tray_color": "FFFFFFFF"}]
|
|
|
|
|
+ assert client._find_verify_tray(255, 0) is slots[0]
|
|
|
|
|
+
|
|
|
|
|
+ def test_the_same_id_merges_too(self):
|
|
|
|
|
+ slots = _push(_client(), {"id": "254", "tray_info_idx": "GFL99"})
|
|
|
|
|
+
|
|
|
|
|
+ assert slots == [{**FULL, "tray_info_idx": "GFL99"}]
|
|
|
|
|
+
|
|
|
|
|
+ def test_the_delta_confirms_a_pending_assignment(self):
|
|
|
|
|
+ client = _client()
|
|
|
|
|
+ seen = MagicMock()
|
|
|
|
|
+ client.on_assignment_verified = seen
|
|
|
|
|
+ client.register_assignment_verification(255, 0, "GFL03", "FFFFFFFF", None)
|
|
|
|
|
+
|
|
|
|
|
+ _push(client, {"tray_info_idx": "GFL03", "tray_color": "FFFFFFFF"})
|
|
|
|
|
+
|
|
|
|
|
+ seen.assert_called_once()
|
|
|
|
|
+ assert seen.call_args.args[:3] == (255, 0, True)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class TestWholesale:
|
|
|
|
|
+ def test_an_emptied_slot_keeps_nothing_of_the_spool(self):
|
|
|
|
|
+ emptied = {"id": "254", "tray_type": "", "tray_color": "", "tray_info_idx": ""}
|
|
|
|
|
+
|
|
|
|
|
+ assert _push(_client(), emptied) == [emptied]
|
|
|
|
|
+
|
|
|
|
|
+ def test_another_slot_id_replaces_it(self):
|
|
|
|
|
+ other = {"id": "255", "tray_type": "PETG"}
|
|
|
|
|
+
|
|
|
|
|
+ assert _push(_client(), other) == [other]
|
|
|
|
|
+
|
|
|
|
|
+ def test_two_external_slots_ignore_a_single_dict(self):
|
|
|
|
|
+ client = BambuMQTTClient(ip_address="10.0.0.1", serial_number="SN", access_code="c", model="H2D")
|
|
|
|
|
+ both = [{**FULL, "id": "254"}, {**FULL, "id": "255", "tray_type": "PETG"}]
|
|
|
|
|
+ client._process_message({"print": {"command": "push_status", "vir_slot": [dict(s) for s in both]}})
|
|
|
|
|
+
|
|
|
|
|
+ assert _push(client, {"tray_color": "FFFFFFFF"}) == both
|