Просмотр исходного кода

Add soft reset on device init and support both GRBL/FluidNC

- Perform soft reset before get_machine_steps() in device_init()
- Detect firmware type and use appropriate reset command:
  - FluidNC: $Bye
  - GRBL: Ctrl+X (0x18)
- Refactor to sync/async versions for different contexts
- Ensures controller is in known state before querying

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
tuanchris 1 неделя назад
Родитель
Сommit
2528a20e62
1 измененных файлов с 52 добавлено и 22 удалено
  1. 52 22
      modules/connection/connection_manager.py

+ 52 - 22
modules/connection/connection_manager.py

@@ -197,6 +197,11 @@ def list_serial_ports():
     return available_ports
 
 def device_init(homing=True):
+    # Perform soft reset first to ensure controller is in a known state
+    # This resets position counters to 0 before we query them
+    logger.info("Performing soft reset before device initialization...")
+    perform_soft_reset_sync()
+
     try:
         if get_machine_steps():
             logger.info(f"x_steps_per_mm: {state.x_steps_per_mm}, y_steps_per_mm: {state.y_steps_per_mm}, gear_ratio: {state.gear_ratio}")
@@ -1264,12 +1269,13 @@ async def update_machine_position():
             logger.error(f"Error updating machine position: {e}")
 
 
-async def perform_soft_reset():
+def perform_soft_reset_sync():
     """
-    Send $Bye soft reset to FluidNC controller and reset position counters.
+    Synchronous version of soft reset for use during device initialization.
 
-    $Bye triggers a software reset in FluidNC which clears all position counters
-    to 0. This is more reliable than G92 which only sets a work coordinate offset
+    Supports both FluidNC ($Bye) and GRBL (Ctrl+X / 0x18) firmware.
+    Triggers a software reset which clears position counters to 0.
+    This is more reliable than G92 which only sets a work coordinate offset
     without changing the actual machine position (MPos).
     """
     if not state.conn or not state.conn.is_connected():
@@ -1277,41 +1283,57 @@ async def perform_soft_reset():
         return False
 
     try:
-        logger.info(f"Sending $Bye soft reset (was: X={state.machine_x:.2f}, Y={state.machine_y:.2f})")
+        # Detect firmware type to use appropriate reset command
+        firmware_type, version = _detect_firmware()
+        logger.info(f"Detected firmware: {firmware_type} {version or ''}")
+
+        logger.info(f"Performing soft reset (was: X={state.machine_x:.2f}, Y={state.machine_y:.2f})")
 
         # Clear any pending data first
         if isinstance(state.conn, SerialConnection) and state.conn.ser:
             state.conn.ser.reset_input_buffer()
-            state.conn.ser.write(b'$Bye\n')
-            state.conn.ser.flush()
-            logger.info(f"$Bye sent directly via serial to {state.port}")
+
+        # Send appropriate reset command based on firmware
+        if firmware_type == 'fluidnc':
+            # FluidNC uses $Bye for soft reset
+            if isinstance(state.conn, SerialConnection) and state.conn.ser:
+                state.conn.ser.write(b'$Bye\n')
+                state.conn.ser.flush()
+                logger.info(f"$Bye sent directly via serial to {state.port}")
+            else:
+                state.conn.send('$Bye\n')
+                logger.info("$Bye sent via connection abstraction")
         else:
-            state.conn.send('$Bye\n')
-            logger.info("$Bye sent via connection abstraction")
+            # GRBL uses Ctrl+X (0x18) for soft reset
+            if isinstance(state.conn, SerialConnection) and state.conn.ser:
+                state.conn.ser.write(b'\x18')
+                state.conn.ser.flush()
+                logger.info(f"Ctrl+X (0x18) sent directly via serial to {state.port}")
+            else:
+                state.conn.send('\x18')
+                logger.info("Ctrl+X (0x18) sent via connection abstraction")
 
         # Wait for controller to fully restart
-        # The restart sequence is:
-        # 1. [MSG:INFO: Restarting] + ok
-        # 2. Many [MSG:INFO: ...] initialization lines
-        # 3. Final: "Grbl 3.9 [FluidNC v3.9.5 ...]" - this means ready
+        # FluidNC sequence: [MSG:INFO: Restarting] -> ... -> "Grbl 3.9 [FluidNC...]"
+        # GRBL sequence: "Grbl 1.1h ['$' for help]"
         start_time = time.time()
         reset_confirmed = False
         while time.time() - start_time < 5.0:  # 5 second timeout for full reboot
             try:
                 response = state.conn.readline()
                 if response:
-                    logger.debug(f"$Bye response: {response}")
-                    # Wait for the final "Grbl" startup banner - this means fully ready
+                    logger.debug(f"Reset response: {response}")
+                    # Wait for the "Grbl" startup banner - this means fully ready
                     if response.startswith("Grbl") or "fluidnc" in response.lower():
                         reset_confirmed = True
                         logger.info(f"Controller restart complete: {response}")
                         break
             except Exception:
                 pass
-            await asyncio.sleep(0.05)
+            time.sleep(0.05)
 
         # Small delay to let controller fully stabilize
-        await asyncio.sleep(0.2)
+        time.sleep(0.2)
 
         # Unlock controller in case it's in alarm state after reset
         if reset_confirmed:
@@ -1329,19 +1351,19 @@ async def perform_soft_reset():
                             break
                 except Exception:
                     pass
-                await asyncio.sleep(0.05)
+                time.sleep(0.05)
 
         # Reset state positions to 0 after soft reset
         state.machine_x = 0.0
         state.machine_y = 0.0
 
         if reset_confirmed:
-            logger.info("Machine position reset to 0 via $Bye soft reset")
+            logger.info(f"Machine position reset to 0 via {'$Bye' if firmware_type == 'fluidnc' else 'Ctrl+X'} soft reset")
         else:
-            logger.warning("$Bye sent but no reset confirmation received, position set to 0 anyway")
+            logger.warning("Soft reset sent but no confirmation received, position set to 0 anyway")
 
         # Save the reset position
-        await asyncio.to_thread(state.save)
+        state.save()
         logger.info(f"Machine position saved: {state.machine_x}, {state.machine_y}")
 
         return True
@@ -1351,6 +1373,14 @@ async def perform_soft_reset():
         return False
 
 
+async def perform_soft_reset():
+    """
+    Async version of soft reset for use in async contexts (API endpoints, pattern manager).
+    Wraps the sync version in a thread to avoid blocking the event loop.
+    """
+    return await asyncio.to_thread(perform_soft_reset_sync)
+
+
 def reset_work_coordinates():
     """
     Clear all work coordinate offsets for a clean start.