Browse Source

fix(printer): Flow Calibration was silently skipped — wrong project_file fields (#1478)

  The H2S never ran flow-dynamics calibration even with the print option
  enabled, because start_print built the project_file command wrong:

  - extrude_cali_flag was hardcoded to 0. A BambuStudio request-topic
    capture from a real H2D (plus X1C/P2S captures) shows it is always 1
    (run calibration) or 2 (skip, reuse stored PA), paired with flow_cali,
    never 0 — so the printer skipped calibration regardless of the toggle.
  - flow_cali and the other calibration/leveling fields were integer-
    encoded for the H2 family on a mistaken belief that H2 firmware
    requires 0/1. The same capture sends plain JSON booleans for every
    model; the belief conflated these fields with use_ams (which does
    need to stay boolean — the actual #1386 cause).

  Fix: extrude_cali_flag = 1 if flow_cali else 2; send timelapse,
  bed_leveling, flow_cali, vibration_cali and layer_inspect as booleans
  for all models; drop the is_h_family integer-conversion branch.
  use_ams is unchanged.

  Corrected the two tests that asserted the integer format and renamed
  them; all three model tests now also assert extrude_cali_flag.
maziggy 6 days ago
parent
commit
379b1c14bc
3 changed files with 56 additions and 55 deletions
  1. 0 0
      CHANGELOG.md
  2. 22 27
      backend/app/services/bambu_mqtt.py
  3. 34 28
      backend/tests/unit/services/test_bambu_mqtt.py

File diff suppressed because it is too large
+ 0 - 0
CHANGELOG.md


+ 22 - 27
backend/app/services/bambu_mqtt.py

@@ -3262,21 +3262,17 @@ class BambuMQTTClient:
             use_ams: Use AMS for automatic filament changes
         """
         if self._client and self.state.connected:
-            # Bambu print command format - matches Bambu Studio's format
-            # H2-family firmware (H2D, H2D Pro, H2C, H2S, X2D) requires integer
-            # values (0/1) for calibration/leveling fields. X1C/P1S/A1/P2S need
-            # actual booleans. use_ams stays boolean across the board — H2D Pro
-            # firmware interprets integer use_ams as nozzle index (1 = deputy),
-            # causing wrong extruder routing (#1386 root cause was here too: the
-            # old flag conflated firmware-format with dual-nozzle routing).
-            is_h_family = self.model and self.model.upper().strip() in (
-                "H2D",
-                "H2D PRO",
-                "H2DPRO",
-                "H2C",
-                "H2S",
-                "X2D",
-            )
+            # Bambu print command format — matches Bambu Studio's format.
+            # The calibration/leveling fields (timelapse, bed_leveling,
+            # flow_cali, vibration_cali, layer_inspect) are JSON booleans for
+            # every model. An earlier revision integer-encoded them for the H2
+            # family (H2D/H2S/H2C/X2D) on the belief that H2 firmware required
+            # 0/1 — but a BambuStudio request-topic capture from a real H2D
+            # sends plain booleans, and the integer encoding made the H2S
+            # silently skip flow-dynamics calibration (#1478). use_ams is the
+            # one field that genuinely must stay boolean: H2D Pro firmware
+            # reads an integer use_ams as a nozzle index (1 = deputy), which is
+            # what actually caused the wrong-extruder routing behind #1386.
             # Dual-nozzle routing for external spool (254 = deputy/left,
             # 255 = main/right) and the use_ams=False fallback. H2S is in the
             # H2 firmware family but is single-nozzle, despite sharing serial
@@ -3372,15 +3368,20 @@ class BambuMQTTClient:
                     "file": filename,
                     "md5": "",
                     "bed_type": "auto",
-                    "timelapse": (1 if timelapse else 0) if is_h_family else timelapse,
-                    "bed_leveling": (1 if bed_levelling else 0) if is_h_family else bed_levelling,
+                    "timelapse": timelapse,
+                    "bed_leveling": bed_levelling,
                     "auto_bed_leveling": 1 if bed_levelling else 0,
-                    "flow_cali": (1 if flow_cali else 0) if is_h_family else flow_cali,
-                    "vibration_cali": (1 if vibration_cali else 0) if is_h_family else vibration_cali,
-                    "layer_inspect": (1 if layer_inspect else 0) if is_h_family else layer_inspect,
+                    "flow_cali": flow_cali,
+                    "vibration_cali": vibration_cali,
+                    "layer_inspect": layer_inspect,
                     "use_ams": use_ams,
                     "cfg": "0",
-                    "extrude_cali_flag": 0,
+                    # extrude_cali_flag gates flow-dynamics calibration:
+                    # 1 = run it, 2 = skip and reuse the stored PA value.
+                    # BambuStudio always pairs this with flow_cali and never
+                    # sends 0; a hardcoded 0 made the printer skip calibration
+                    # regardless of the flow_cali toggle (#1478).
+                    "extrude_cali_flag": 1 if flow_cali else 2,
                     "extrude_cali_manual_mode": 0,
                     "nozzle_offset_cali": 2,
                     "subtask_name": filename.replace(".3mf", "").replace(".gcode", ""),
@@ -3391,12 +3392,6 @@ class BambuMQTTClient:
                 }
             }
 
-            if is_h_family:
-                logger.debug(
-                    "[%s] H-family firmware detected: using integer format for calibration fields (use_ams stays boolean)",
-                    self.serial_number,
-                )
-
             # P2S-specific parameter adjustments
             # P2S printer doesn't support vibration calibration like X1/P1 series
             if self.model and self.model.upper().strip() in ("P2S", "N7"):

+ 34 - 28
backend/tests/unit/services/test_bambu_mqtt.py

@@ -3730,13 +3730,13 @@ class TestStartPrintAmsMapping:
             {"ams_id": 255, "slot_id": 0},
         ]
 
-    def test_x2d_uses_integer_format_for_calibration_fields(self, mqtt_client):
-        """X2D must use H2D-style integer (0/1) format for calibration fields (#988).
+    def test_x2d_uses_boolean_format_for_calibration_fields(self, mqtt_client):
+        """X2D sends calibration fields as JSON booleans, like every model (#1478).
 
-        The reporter's support bundle showed X2D running firmware in the same
-        family as H2D. Booleans in these fields are interpreted as nozzle
-        indexes by H2D firmware; X2D is treated identically until proven
-        otherwise.
+        An earlier revision integer-encoded these for the H2 family on the
+        belief that H2 firmware required 0/1. A BambuStudio request-topic
+        capture from a real H2D disproved it — BambuStudio sends plain
+        booleans — so X2D follows the same boolean format.
         """
         mqtt_client.model = "X2D"
         mqtt_client.start_print(
@@ -3749,24 +3749,24 @@ class TestStartPrintAmsMapping:
         )
 
         cmd = self._get_published_command(mqtt_client)
-        assert cmd["timelapse"] == 1
-        assert cmd["bed_leveling"] == 0
-        assert cmd["flow_cali"] == 1
-        assert cmd["vibration_cali"] == 0
-        assert cmd["layer_inspect"] == 1
-
-    def test_p2s_still_uses_boolean_format(self, mqtt_client):
-        """Regression guard: P2S is NOT in the H-family firmware gate — must still use booleans.
-
-        Adding X2D to the H-family set must not accidentally affect P2S, which
-        is single-nozzle and uses boolean format like X1C/A1/P1.
-        """
+        assert cmd["timelapse"] is True
+        assert cmd["bed_leveling"] is False
+        assert cmd["flow_cali"] is True
+        assert cmd["vibration_cali"] is False
+        assert cmd["layer_inspect"] is True
+        # flow_cali on → extrude_cali_flag must request the calibration pass.
+        assert cmd["extrude_cali_flag"] == 1
+
+    def test_p2s_uses_boolean_format(self, mqtt_client):
+        """P2S sends calibration fields as JSON booleans (single-nozzle, like X1C/A1/P1)."""
         mqtt_client.model = "P2S"
         mqtt_client.start_print("test.3mf", timelapse=True, flow_cali=False)
 
         cmd = self._get_published_command(mqtt_client)
         assert cmd["timelapse"] is True
         assert cmd["flow_cali"] is False
+        # flow_cali off → extrude_cali_flag=2 (skip, reuse stored PA value).
+        assert cmd["extrude_cali_flag"] == 2
 
     def test_h2s_single_external_spool_uses_main_id(self, mqtt_client):
         """H2S is single-nozzle (#1386): external spool (254) → ams_id=255.
@@ -3797,11 +3797,14 @@ class TestStartPrintAmsMapping:
         cmd = self._get_published_command(mqtt_client)
         assert cmd["use_ams"] is False
 
-    def test_h2s_keeps_integer_format_for_calibration_fields(self, mqtt_client):
-        """H2S shares the H-family firmware (int 0/1 for calibration fields)
-        even though it's single-nozzle. Verified empirically against H2S
-        bundles: the print command structure was always accepted, only the
-        AMS routing failed (#1386).
+    def test_h2s_uses_boolean_format_for_calibration_fields(self, mqtt_client):
+        """H2S sends calibration fields as JSON booleans (#1478).
+
+        The H2S was previously integer-encoded as part of the H2 family. That
+        made it accept the print command but silently skip flow-dynamics
+        calibration — the reporter saw poor corner quality from a stale K
+        value. BambuStudio sends booleans for these fields and pairs flow_cali
+        with extrude_cali_flag=1 to actually run the calibration pass.
         """
         mqtt_client.model = "H2S"
         mqtt_client.start_print(
@@ -3814,11 +3817,14 @@ class TestStartPrintAmsMapping:
         )
 
         cmd = self._get_published_command(mqtt_client)
-        assert cmd["timelapse"] == 1
-        assert cmd["bed_leveling"] == 0
-        assert cmd["flow_cali"] == 1
-        assert cmd["vibration_cali"] == 0
-        assert cmd["layer_inspect"] == 1
+        assert cmd["timelapse"] is True
+        assert cmd["bed_leveling"] is False
+        assert cmd["flow_cali"] is True
+        assert cmd["vibration_cali"] is False
+        assert cmd["layer_inspect"] is True
+        # flow_cali on → extrude_cali_flag=1 so the printer runs the
+        # flow-dynamics calibration instead of reusing the stored PA value.
+        assert cmd["extrude_cali_flag"] == 1
 
 
 class TestStartPrintUniqueIdentityFields:

Some files were not shown because too many files changed in this diff