瀏覽代碼

Fix build error and add retry logic for motion commands

- Remove unused mainConnectionPort state (fixes TS6133 build error)
- Add 1-second timeout with automatic resend for motion commands
- Motion thread now retries forever until 'ok' is received
- Prevents silent hang if response is missed

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
tuanchris 1 周之前
父節點
當前提交
1c3f59805d
共有 2 個文件被更改,包括 27 次插入15 次删除
  1. 0 2
      frontend/src/pages/TableControlPage.tsx
  2. 27 13
      modules/core/pattern_manager.py

+ 0 - 2
frontend/src/pages/TableControlPage.tsx

@@ -43,7 +43,6 @@ export function TableControlPage() {
   const [serialCommand, setSerialCommand] = useState('')
   const [serialCommand, setSerialCommand] = useState('')
   const [serialHistory, setSerialHistory] = useState<Array<{ type: 'cmd' | 'resp' | 'error'; text: string; time: string }>>([])
   const [serialHistory, setSerialHistory] = useState<Array<{ type: 'cmd' | 'resp' | 'error'; text: string; time: string }>>([])
   const [serialLoading, setSerialLoading] = useState(false)
   const [serialLoading, setSerialLoading] = useState(false)
-  const [mainConnectionPort, setMainConnectionPort] = useState<string | null>(null)
   const serialOutputRef = useRef<HTMLDivElement>(null)
   const serialOutputRef = useRef<HTMLDivElement>(null)
   const serialInputRef = useRef<HTMLInputElement>(null)
   const serialInputRef = useRef<HTMLInputElement>(null)
 
 
@@ -257,7 +256,6 @@ export function TableControlPage() {
         // This prevents race conditions where stale port data from a different
         // This prevents race conditions where stale port data from a different
         // backend (e.g., Mac port on a Pi) could be set and auto-connected
         // backend (e.g., Mac port on a Pi) could be set and auto-connected
         if (availablePorts.includes(data.port)) {
         if (availablePorts.includes(data.port)) {
-          setMainConnectionPort(data.port)
           setSelectedSerialPort(data.port)
           setSelectedSerialPort(data.port)
         } else {
         } else {
           console.warn(`Port ${data.port} from status not in available ports, ignoring`)
           console.warn(`Port ${data.port} from status not in available ports, ignoring`)

+ 27 - 13
modules/core/pattern_manager.py

@@ -516,30 +516,41 @@ class MotionControlThread:
         state.machine_y = new_y_abs
         state.machine_y = new_y_abs
 
 
     def _send_grbl_coordinates_sync(self, x: float, y: float, speed: int = 600, timeout: int = 2, home: bool = False):
     def _send_grbl_coordinates_sync(self, x: float, y: float, speed: int = 600, timeout: int = 2, home: bool = False):
-        """Synchronous version of send_grbl_coordinates for motion thread."""
-        logger.debug(f"Motion thread sending G-code: X{x} Y{y} at F{speed}")
+        """Synchronous version of send_grbl_coordinates for motion thread.
+
+        Sends coordinate and waits for 'ok'. If no 'ok' received within 1 second,
+        resends the command. Retries forever until 'ok' is received.
+        """
+        gcode = f"$J=G91 G21 Y{y} F{speed}" if home else f"G1 G53 X{x} Y{y} F{speed}"
+        retry_count = 0
 
 
         while True:
         while True:
             # Check stop_requested at the start of each iteration
             # Check stop_requested at the start of each iteration
             if state.stop_requested:
             if state.stop_requested:
                 logger.debug("Motion thread: Stop requested, aborting command")
                 logger.debug("Motion thread: Stop requested, aborting command")
-                return
+                return False
 
 
             try:
             try:
-                gcode = f"$J=G91 G21 Y{y} F{speed}" if home else f"G1 G53 X{x} Y{y} F{speed}"
+                logger.debug(f"Motion thread sending G-code: {gcode}")
                 state.conn.send(gcode + "\n")
                 state.conn.send(gcode + "\n")
-                logger.debug(f"Motion thread sent command: {gcode}")
 
 
-                while True:
+                # Wait for 'ok' response with 1 second timeout
+                start_time = time.time()
+                while time.time() - start_time < 1.0:
                     # Also check stop_requested while waiting for response
                     # Also check stop_requested while waiting for response
                     if state.stop_requested:
                     if state.stop_requested:
                         logger.debug("Motion thread: Stop requested while waiting for response")
                         logger.debug("Motion thread: Stop requested while waiting for response")
-                        return
+                        return False
                     response = state.conn.readline()
                     response = state.conn.readline()
-                    logger.debug(f"Motion thread response: {response}")
-                    if response.lower() == "ok":
-                        logger.debug("Motion thread: Command execution confirmed.")
-                        return
+                    if response:
+                        logger.debug(f"Motion thread response: {response}")
+                        if response.lower() == "ok":
+                            logger.debug("Motion thread: Command execution confirmed.")
+                            return True
+
+                # No 'ok' received within timeout, will retry
+                retry_count += 1
+                logger.warning(f"Motion thread: No 'ok' received for {gcode}, resending... (attempt {retry_count})")
 
 
             except Exception as e:
             except Exception as e:
                 error_str = str(e)
                 error_str = str(e)
@@ -554,8 +565,11 @@ class MotionControlThread:
                     logger.info("Connection marked as disconnected due to device error")
                     logger.info("Connection marked as disconnected due to device error")
                     return False
                     return False
 
 
-            logger.warning(f"Motion thread: No 'ok' received for X{x} Y{y}, speed {speed}. Retrying...")
-            time.sleep(0.1)
+                retry_count += 1
+                logger.warning(f"Motion thread: Exception occurred, retrying... (attempt {retry_count})")
+
+            # Wait 1 second before resending
+            time.sleep(1.0)
 
 
 # Global motion control thread instance
 # Global motion control thread instance
 motion_controller = MotionControlThread()
 motion_controller = MotionControlThread()