瀏覽代碼

fix(mqtt): don't let K-profile responses clobber the nozzle size (#2663)

Fetching K-profiles probes every nozzle size in turn (extrusion_cali_get
for 0.2/0.4/0.6/0.8mm), and each response echoes the requested diameter at
the top level. _process_message passed every "print" message -- including
these responses -- to _update_state, which treats a top-level
nozzle_diameter as the installed hardware. So the last size probed (0.8)
overwrote the real nozzle in memory; a genuine status push corrected it and
the next K-profile fetch broke it again, which is why it flickered between
0.8, empty and the correct 0.4. Since 1.2.5 the #1899 mismatch guard then
refused to dispatch, failing prints with a bogus "printer has 0.8mm".

Handle extrusion_cali_get responses only via the K-profile parser and skip
_update_state for them, mirroring the existing get_accessories guard. The
nozzle size now comes solely from the real status push. In-memory only --
affected printers self-correct on the next push after updating.
maziggy 1 月之前
父節點
當前提交
1234f0830a
共有 3 個文件被更改,包括 81 次插入2 次删除
  1. 1 0
      CHANGELOG.md
  2. 11 2
      backend/app/services/bambu_mqtt.py
  3. 69 0
      backend/tests/unit/services/test_bambu_mqtt.py

文件差異過大導致無法顯示
+ 1 - 0
CHANGELOG.md


+ 11 - 2
backend/app/services/bambu_mqtt.py

@@ -1427,10 +1427,19 @@ class BambuMQTTClient:
                 elif cmd == "ams_filament_setting":
                     self._last_ams_cmd_time = 0.0
                     self._ams_cmd_unanswered = 0
-            if "command" in print_data and print_data.get("command") == "extrusion_cali_get":
+            is_kprofile_response = "command" in print_data and print_data.get("command") == "extrusion_cali_get"
+            if is_kprofile_response:
                 self._handle_kprofile_response(print_data)
 
-            self._update_state(print_data)
+            # An extrusion_cali_get response echoes the *requested* nozzle
+            # diameter (get_kprofiles probes 0.2/0.4/0.6/0.8 in turn), not the
+            # installed hardware. Feeding it to _update_state clobbered the real
+            # nozzle size (#2663) — typically leaving 0.8, the last size probed,
+            # which then failed the #1899 dispatch guard. The response carries no
+            # status telemetry, so skip it; the true nozzle comes from pushall.
+            # (Same reasoning as get_accessories in _handle_system_response.)
+            if not is_kprofile_response:
+                self._update_state(print_data)
 
     def _handle_system_response(self, data: dict):
         """Handle system responses including accessories info.

+ 69 - 0
backend/tests/unit/services/test_bambu_mqtt.py

@@ -6468,3 +6468,72 @@ class TestPresumedPowerOffRecovery:
         self._report(mqtt_client, {"print": {"wifi_signal": "-30dBm"}})
 
         assert mqtt_client.state.state == "FINISH"
+
+
+class TestKProfileResponseDoesNotClobberNozzle:
+    """#2663: the K-profile fetch (get_kprofiles) probes every nozzle size
+    0.2/0.4/0.6/0.8 with an ``extrusion_cali_get`` request. Each response
+    echoes the *requested* nozzle_diameter at the top level, which is NOT the
+    installed hardware. _process_message must not feed those responses to
+    _update_state, or the real nozzle size gets overwritten (typically to 0.8,
+    the last size probed) and the #1899 dispatch guard then blocks prints.
+    """
+
+    @pytest.fixture
+    def mqtt_client(self):
+        from backend.app.services.bambu_mqtt import BambuMQTTClient
+
+        return BambuMQTTClient(
+            ip_address="192.168.1.100",
+            serial_number="A1TEST",
+            access_code="12345678",
+        )
+
+    def test_kprofile_response_does_not_overwrite_nozzle_diameter(self, mqtt_client):
+        # A genuine pushall reports the real 0.4mm nozzle.
+        mqtt_client._process_message({"print": {"nozzle_diameter": "0.4"}})
+        assert mqtt_client.state.nozzles[0].nozzle_diameter == "0.4"
+
+        # The K-profile probe's 0.8mm response arrives (as it did on the
+        # reporter's A1s). It must NOT clobber the hardware nozzle.
+        mqtt_client._process_message(
+            {
+                "print": {
+                    "command": "extrusion_cali_get",
+                    "nozzle_diameter": "0.8",
+                    "filaments": [],
+                    "sequence_id": "1501",
+                }
+            }
+        )
+        assert mqtt_client.state.nozzles[0].nozzle_diameter == "0.4"
+
+    def test_kprofile_response_is_still_parsed(self, mqtt_client):
+        # Skipping _update_state must not skip the K-profile handler: the
+        # response's profiles still populate state.kprofiles.
+        mqtt_client._process_message(
+            {
+                "print": {
+                    "command": "extrusion_cali_get",
+                    "nozzle_diameter": "0.4",
+                    "filaments": [
+                        {
+                            "cali_idx": 0,
+                            "nozzle_diameter": "0.4",
+                            "filament_id": "GFA00",
+                            "name": "PLA",
+                            "k_value": "0.020000",
+                        }
+                    ],
+                }
+            }
+        )
+        assert len(mqtt_client.state.kprofiles) == 1
+        assert mqtt_client.state.kprofiles[0].filament_id == "GFA00"
+
+    def test_genuine_pushall_still_updates_nozzle(self, mqtt_client):
+        # The one legitimate source of the hardware nozzle still works, and a
+        # later pushall corrects a value an old build left wrong.
+        mqtt_client.state.nozzles[0].nozzle_diameter = "0.8"
+        mqtt_client._process_message({"print": {"nozzle_diameter": "0.4"}})
+        assert mqtt_client.state.nozzles[0].nozzle_diameter == "0.4"

部分文件因文件數量過多而無法顯示