Martin Ziegler 9 месяцев назад
Родитель
Сommit
71ccda0ab2

+ 64 - 9
backend/app/services/bambu_mqtt.py

@@ -740,7 +740,48 @@ class BambuMQTTClient:
                     # Note: Do NOT clear pending_tray_target on tray_now=255 here.
                     # During filament change, the printer sends 255 first (unload), then the slot.
                     # We only clear pending_tray_target explicitly in ams_unload_filament().
-                    self.state.tray_now = parsed_tray_now
+
+                    # H2D special case: when tray_now=255 AND we're in active filament change,
+                    # check if any AMS has active info for disambiguation.
+                    # But when IDLE (ams_status_main=0), trust tray_now=255 - filament is unloaded.
+                    # The info field "1XXX" means actively feeding, "2XXX" means idle.
+                    if parsed_tray_now == 255 and self.state.ams_status_main == 1:
+                        # Only do info override during active filament change
+                        # Check if any AMS has info starting with "1" (active)
+                        inferred_from_info = None
+                        for ams_unit in ams_list:
+                            ams_id = ams_unit.get("id")
+                            if ams_id is None:
+                                continue
+                            try:
+                                ams_id = int(ams_id)
+                            except (ValueError, TypeError):
+                                continue
+
+                            info = ams_unit.get("info")
+                            if info is not None:
+                                info_str = str(info)
+                                if info_str.startswith("1") and len(info_str) >= 2:
+                                    # Info format: 1XYZ where X is the slot index
+                                    try:
+                                        slot_idx = int(info_str[1])
+                                        if 0 <= slot_idx <= 3:
+                                            inferred_from_info = ams_id * 4 + slot_idx
+                                            logger.info(
+                                                f"[{self.serial_number}] H2D tray_now override: "
+                                                f"printer reports 255 but AMS {ams_id} info={info_str} shows "
+                                                f"slot {slot_idx} is active -> using global ID {inferred_from_info}"
+                                            )
+                                            break
+                                    except (ValueError, IndexError):
+                                        pass
+
+                        if inferred_from_info is not None:
+                            self.state.tray_now = inferred_from_info
+                        else:
+                            self.state.tray_now = parsed_tray_now
+                    else:
+                        self.state.tray_now = parsed_tray_now
 
                 logger.debug(f"[{self.serial_number}] tray_now updated: {self.state.tray_now}")
 
@@ -2462,22 +2503,36 @@ class BambuMQTTClient:
             logger.warning(f"[{self.serial_number}] Cannot unload filament: not connected")
             return False
 
-        # Build unload command with all required fields (per HA-Bambulab integration)
+        # Get the currently loaded tray info
+        tray_now = self.state.tray_now
+        logger.info(f"[{self.serial_number}] Unload requested, tray_now={tray_now}")
+
+        # Determine the ams_id from tray_now (if valid tray is loaded)
+        # From BambuStudio source: unload requires target=255 AND slot_id=255
+        # plus the ams_id of the source AMS unit
+        if tray_now is not None and tray_now < 254:
+            ams_id = tray_now // 4
+        else:
+            ams_id = 0  # Default to AMS 0 if no tray loaded
+
+        # Build unload command using BambuStudio's "new protocol"
+        # Key: Both target=255 AND slot_id=255 are required for unload
         command = {
             "print": {
                 "command": "ams_change_filament",
-                "target": 255,  # 255 = unload
-                "ams_id": 255,
-                "slot_id": 0,
-                "curr_temp": 0,
-                "tar_temp": 0,
-                "sequence_id": "0"
+                "sequence_id": "0",
+                "target": 255,    # 255 = unload
+                "slot_id": 255,   # 255 = unload (new protocol)
+                "ams_id": ams_id,
+                "curr_temp": 220,
+                "tar_temp": 220
             }
         }
+
         command_json = json.dumps(command)
         logger.info(f"[{self.serial_number}] Publishing ams_change_filament (unload) command: {command_json}")
         self._client.publish(self.topic_publish, command_json)
-        logger.info(f"[{self.serial_number}] Unloading filament")
+        logger.info(f"[{self.serial_number}] Unloading filament from AMS {ams_id}")
 
         # Clear tracked load request since we're unloading
         self._last_load_tray_id = None

+ 24 - 7
frontend/src/components/control/AMSSectionDual.tsx

@@ -813,6 +813,10 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
   // Track if we've done initial sync from tray_now
   const initialSyncDone = useRef(false);
 
+  // Track intended operation type synchronously (refs update immediately, unlike state)
+  // This prevents race conditions where MQTT updates arrive before React state updates
+  const intendedOperationRef = useRef<'load' | 'unload' | null>(null);
+
   // Sync selectedTray from status.tray_now on initial load
   // tray_now: 255 = no filament loaded, 0-253 = valid tray ID, 254 = external spool
   useEffect(() => {
@@ -920,6 +924,8 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
     if (selectedTray !== null) {
       const extruderId = getExtruderIdForTray(selectedTray);
       console.log(`[AMSSectionDual] Calling loadMutation.mutate(tray: ${selectedTray}, extruder: ${extruderId})`);
+      // Set ref synchronously FIRST (refs update immediately, before MQTT can respond)
+      intendedOperationRef.current = 'load';
       // Show filament change card immediately
       setUserFilamentChange({ isLoading: true, targetTrayId: selectedTray });
       loadMutation.mutate({ trayId: selectedTray, extruderId });
@@ -927,14 +933,19 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
   };
 
   const handleUnload = () => {
+    console.log(`[AMSSectionDual] handleUnload called, printerId: ${printerId}, trayNow: ${status?.tray_now}`);
+    // Set ref synchronously FIRST (refs update immediately, before MQTT can respond)
+    intendedOperationRef.current = 'unload';
     // Show filament change card immediately (no target tray for unload)
     setUserFilamentChange({ isLoading: false, targetTrayId: null });
+    console.log(`[AMSSectionDual] Calling unloadMutation.mutate()`);
     unloadMutation.mutate();
   };
 
   // Callback for FilamentChangeCard to close itself
   const handleFilamentChangeComplete = () => {
     console.log(`[AMSSectionDual] FilamentChangeCard completed, closing card`);
+    intendedOperationRef.current = null; // Clear the synchronous ref
     setUserFilamentChange(null);
   };
 
@@ -1005,8 +1016,9 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
       setUserFilamentChange(null);
     } else if (wasActive && !isMqttFilamentChangeActive) {
       // Transition from active (1) to idle (0)
-      // Close the card by clearing user state
+      // Close the card by clearing user state and the synchronous ref
       console.log(`[AMSSectionDual] ams_status_main transitioned 1->0, closing card`);
+      intendedOperationRef.current = null;
       setUserFilamentChange(null);
     }
 
@@ -1017,16 +1029,21 @@ export function AMSSectionDual({ printerId, printerModel, status, nozzleCount }:
   // Show FilamentChangeCard when either MQTT reports active ams_status OR user just clicked load/unload
   const showFilamentChangeCard = isMqttFilamentChangeActive || userFilamentChange !== null;
 
-  // Determine if loading or unloading for the card display
-  // Use user intent if available, otherwise default to loading (most common operation)
-  const isFilamentLoading = userFilamentChange !== null
-    ? userFilamentChange.isLoading
-    : true; // Default to loading when detected via MQTT
-
   // Get the loaded tray info for wire coloring
   // Wire coloring should show the path from the currently loaded filament to the extruder
   // But ONLY if the currently displayed AMS panel is the one with the loaded filament
   const trayNow = status?.tray_now ?? 255;
+
+  // Determine if loading or unloading for the card display
+  // Priority: 1) Synchronous ref (set immediately on click), 2) React state, 3) MQTT signals
+  // The ref prevents race conditions where MQTT updates arrive before React state updates
+  const amsStatusSub = status?.ams_status_sub ?? 0;
+  const SUB_RETRACT = 4; // Only happens during unload
+  const isFilamentLoading =
+    intendedOperationRef.current === 'load' ? true :
+    intendedOperationRef.current === 'unload' ? false :
+    userFilamentChange !== null ? userFilamentChange.isLoading :
+    !(amsStatusSub === SUB_RETRACT || trayNow === 255); // Unload if retracting or tray_now is 255
   const getLoadedTrayInfo = (): {
     leftActiveSlot: number | null;
     rightActiveSlot: number | null;

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/assets/index-BLdylhup.js


+ 1 - 1
static/index.html

@@ -7,7 +7,7 @@
     <link rel="icon" type="image/png" sizes="32x32" href="/img/favicon-32x32.png" />
     <link rel="icon" type="image/png" sizes="16x16" href="/img/favicon-16x16.png" />
     <link rel="apple-touch-icon" sizes="180x180" href="/img/apple-touch-icon.png" />
-    <script type="module" crossorigin src="/assets/index-CLt_WkAI.js"></script>
+    <script type="module" crossorigin src="/assets/index-BLdylhup.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-BREH29lW.css">
   </head>
   <body>

Некоторые файлы не были показаны из-за большого количества измененных файлов