|
|
@@ -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.
|