Explorar o código

fix performance issue

tuanchris hai 4 meses
pai
achega
bebd41eccd

+ 6 - 5
main.py

@@ -218,11 +218,12 @@ async def broadcast_status_update(status: dict):
 
 @app.websocket("/ws/cache-progress")
 async def websocket_cache_progress_endpoint(websocket: WebSocket):
+    from modules.core.cache_manager import get_cache_progress
+
     await websocket.accept()
     active_cache_progress_connections.add(websocket)
     try:
         while True:
-            from modules.core.cache_manager import get_cache_progress
             progress = get_cache_progress()
             try:
                 await websocket.send_json({
@@ -233,7 +234,7 @@ async def websocket_cache_progress_endpoint(websocket: WebSocket):
                 if "close message has been sent" in str(e):
                     break
                 raise
-            await asyncio.sleep(0.5)  # Update every 500ms
+            await asyncio.sleep(1.0)  # Update every 1 second (reduced frequency for better performance)
     except WebSocketDisconnect:
         pass
     finally:
@@ -625,7 +626,7 @@ async def move_to_center():
 
         logger.info("Moving device to center position")
         pattern_manager.reset_theta()
-        pattern_manager.move_polar(0, 0)
+        await pattern_manager.move_polar(0, 0)
         return {"success": True}
     except Exception as e:
         logger.error(f"Failed to move to center: {str(e)}")
@@ -638,7 +639,7 @@ async def move_to_perimeter():
             logger.warning("Attempted to move to perimeter without a connection")
             raise HTTPException(status_code=400, detail="Connection not established")
         pattern_manager.reset_theta()
-        pattern_manager.move_polar(0, 1)
+        await pattern_manager.move_polar(0, 1)
         return {"success": True}
     except Exception as e:
         logger.error(f"Failed to move to perimeter: {str(e)}")
@@ -747,7 +748,7 @@ async def send_coordinate(request: CoordinateRequest):
 
     try:
         logger.debug(f"Sending coordinate: theta={request.theta}, rho={request.rho}")
-        pattern_manager.move_polar(request.theta, request.rho)
+        await pattern_manager.move_polar(request.theta, request.rho)
         return {"success": True}
     except Exception as e:
         logger.error(f"Failed to send coordinate: {str(e)}")

+ 59 - 28
modules/connection/connection_manager.py

@@ -4,6 +4,7 @@ import logging
 import serial
 import serial.tools.list_ports
 import websocket
+import asyncio
 
 from modules.core.state import state
 from modules.led.led_controller import effect_loading, effect_idle, effect_connected, LEDController
@@ -71,7 +72,14 @@ class SerialConnection(BaseConnection):
         return self.ser is not None and self.ser.is_open
 
     def close(self) -> None:
-        update_machine_position()
+        # Run async update_machine_position in sync context
+        try:
+            loop = asyncio.new_event_loop()
+            asyncio.set_event_loop(loop)
+            loop.run_until_complete(update_machine_position())
+            loop.close()
+        except Exception as e:
+            logger.error(f"Error updating machine position on close: {e}")
         with self.lock:
             if self.ser.is_open:
                 self.ser.close()
@@ -119,7 +127,14 @@ class WebSocketConnection(BaseConnection):
         return self.ws is not None
 
     def close(self) -> None:
-        update_machine_position()
+        # Run async update_machine_position in sync context
+        try:
+            loop = asyncio.new_event_loop()
+            asyncio.set_event_loop(loop)
+            loop.run_until_complete(update_machine_position())
+            loop.close()
+        except Exception as e:
+            logger.error(f"Error updating machine position on close: {e}")
         with self.lock:
             if self.ws:
                 self.ws.close()
@@ -220,24 +235,26 @@ def parse_machine_position(response: str):
     return None
 
 
-def send_grbl_coordinates(x, y, speed=600, timeout=2, home=False):
+async def send_grbl_coordinates(x, y, speed=600, timeout=2, home=False):
     """
     Send a G-code command to FluidNC and wait for an 'ok' response.
     If no response after set timeout, sets state to stop and disconnects.
     """
     logger.debug(f"Sending G-code: X{x} Y{y} at F{speed}")
-    
+
     # Track overall attempt time
     overall_start_time = time.time()
-    
+
     while True:
         try:
             gcode = f"$J=G91 G21 Y{y} F{speed}" if home else f"G1 X{x} Y{y} F{speed}"
-            state.conn.send(gcode + "\n")
+            # Use asyncio.to_thread for both send and receive operations to avoid blocking
+            await asyncio.to_thread(state.conn.send, gcode + "\n")
             logger.debug(f"Sent command: {gcode}")
             start_time = time.time()
             while True:
-                response = state.conn.readline()
+                # Use asyncio.to_thread for blocking I/O operations
+                response = await asyncio.to_thread(state.conn.readline)
                 logger.debug(f"Response: {response}")
                 if response.lower() == "ok":
                     logger.debug("Command execution confirmed.")
@@ -246,7 +263,7 @@ def send_grbl_coordinates(x, y, speed=600, timeout=2, home=False):
             # Store the error string inside the exception block
             error_str = str(e)
             logger.warning(f"Error sending command: {error_str}")
-            
+
             # Immediately return for device not configured errors
             if "Device not configured" in error_str or "Errno 6" in error_str:
                 logger.error(f"Device configuration error detected: {error_str}")
@@ -256,9 +273,9 @@ def send_grbl_coordinates(x, y, speed=600, timeout=2, home=False):
                 logger.info("Connection marked as disconnected due to device error")
                 return False
 
-            
+
         logger.warning(f"No 'ok' received for X{x} Y{y}, speed {speed}. Retrying...")
-        time.sleep(0.1)
+        await asyncio.sleep(0.1)
     
     # If we reach here, the timeout has occurred
     logger.error(f"Failed to receive 'ok' response after {max_total_attempt_time} seconds. Stopping and disconnecting.")
@@ -406,20 +423,27 @@ def home(timeout=15):
                     homing_speed = 120
                 logger.info("Sensorless homing not supported. Using crash homing")
                 logger.info(f"Homing with speed {homing_speed}")
-                if state.gear_ratio == 6.25:
-                    result = send_grbl_coordinates(0, - 30, homing_speed, home=True)
-                    if result == False:
-                        logger.error("Homing failed - send_grbl_coordinates returned False")
-                        homing_complete.set()
-                        return
-                    state.machine_y -= 30
-                else:
-                    result = send_grbl_coordinates(0, -22, homing_speed, home=True)
-                    if result == False:
-                        logger.error("Homing failed - send_grbl_coordinates returned False")
-                        homing_complete.set()
-                        return
-                    state.machine_y -= 22
+
+                # Run async function in new event loop
+                loop = asyncio.new_event_loop()
+                asyncio.set_event_loop(loop)
+                try:
+                    if state.gear_ratio == 6.25:
+                        result = loop.run_until_complete(send_grbl_coordinates(0, - 30, homing_speed, home=True))
+                        if result == False:
+                            logger.error("Homing failed - send_grbl_coordinates returned False")
+                            homing_complete.set()
+                            return
+                        state.machine_y -= 30
+                    else:
+                        result = loop.run_until_complete(send_grbl_coordinates(0, -22, homing_speed, home=True))
+                        if result == False:
+                            logger.error("Homing failed - send_grbl_coordinates returned False")
+                            homing_complete.set()
+                            return
+                        state.machine_y -= 22
+                finally:
+                    loop.close()
 
             state.current_theta = state.current_rho = 0
             homing_success = True
@@ -462,7 +486,14 @@ def check_idle():
         response = get_status_response()
         if response and "Idle" in response:
             logger.info("Device is idle")
-            update_machine_position()
+            # Run async update_machine_position in sync context
+            try:
+                loop = asyncio.new_event_loop()
+                asyncio.set_event_loop(loop)
+                loop.run_until_complete(update_machine_position())
+                loop.close()
+            except Exception as e:
+                logger.error(f"Error updating machine position: {e}")
             return True
         time.sleep(1)
         
@@ -490,12 +521,12 @@ def get_machine_position(timeout=5):
     logger.warning("Timeout reached waiting for machine position")
     return None, None
 
-def update_machine_position():
+async def update_machine_position():
     if (state.conn.is_connected() if state.conn else False):
         try:
             logger.info('Saving machine position')
-            state.machine_x, state.machine_y = get_machine_position()
-            state.save()
+            state.machine_x, state.machine_y = await asyncio.to_thread(get_machine_position)
+            await asyncio.to_thread(state.save)
             logger.info(f'Machine position saved: {state.machine_x}, {state.machine_y}')
         except Exception as e:
             logger.error(f"Error updating machine position: {e}")

+ 6 - 2
modules/core/cache_manager.py

@@ -731,9 +731,13 @@ async def generate_cache_background():
         raise
 
 def get_cache_progress():
-    """Get the current cache generation progress."""
+    """Get the current cache generation progress.
+
+    Returns a reference to the cache_progress dict for read-only access.
+    The WebSocket handler should not modify this dict.
+    """
     global cache_progress
-    return cache_progress.copy()
+    return cache_progress  # Return reference instead of copy for better performance
 
 def is_cache_generation_needed():
     """Check if cache generation is needed."""

+ 44 - 24
modules/core/pattern_manager.py

@@ -345,7 +345,7 @@ async def run_theta_rho_file(file_path, is_playlist=False):
                 else:
                     current_speed = state.speed
                     
-                move_polar(theta, rho, current_speed)
+                await move_polar(theta, rho, current_speed)
                 
                 # Update progress for all coordinates including the first one
                 pbar.update(1)
@@ -518,36 +518,49 @@ def stop_actions(clear_playlist = True):
             state.current_playing_file = None
             state.execution_progress = None
             state.is_clearing = False
-            
+
             if clear_playlist:
                 # Clear playlist state
                 state.current_playlist = None
                 state.current_playlist_index = None
                 state.playlist_mode = None
-                
+
                 # Cancel progress update task if we're clearing the playlist
                 global progress_update_task
                 if progress_update_task and not progress_update_task.done():
                     progress_update_task.cancel()
-                
+
             state.pause_condition.notify_all()
-            connection_manager.update_machine_position()
+            # Run async function in sync context
+            try:
+                loop = asyncio.new_event_loop()
+                asyncio.set_event_loop(loop)
+                loop.run_until_complete(connection_manager.update_machine_position())
+                loop.close()
+            except Exception as update_err:
+                logger.error(f"Error updating machine position: {update_err}")
     except Exception as e:
         logger.error(f"Error during stop_actions: {e}")
         # Ensure we still update machine position even if there's an error
-        connection_manager.update_machine_position()
-
-def move_polar(theta, rho, speed=None):
+        try:
+            loop = asyncio.new_event_loop()
+            asyncio.set_event_loop(loop)
+            loop.run_until_complete(connection_manager.update_machine_position())
+            loop.close()
+        except Exception as update_err:
+            logger.error(f"Error updating machine position on error: {update_err}")
+
+async def move_polar(theta, rho, speed=None):
     """
     This functions take in a pair of theta rho coordinate, compute the distance to travel based on current theta, rho,
-    and translate the motion to gcode jog command and sent to grbl. 
-    
-    Since having similar steps_per_mm will make x and y axis moves at around the same speed, we have to scale the 
+    and translate the motion to gcode jog command and sent to grbl.
+
+    Since having similar steps_per_mm will make x and y axis moves at around the same speed, we have to scale the
     x_steps_per_mm and y_steps_per_mm so that they are roughly the same. Here's the range of motion:
-    
+
     X axis (angular): 50mm = 1 revolution
     Y axis (radial): 0 => 20mm = theta 0 (center) => 1 (perimeter)
-    
+
     Args:
         theta (_type_): _description_
         rho (_type_): _description_
@@ -557,42 +570,42 @@ def move_polar(theta, rho, speed=None):
     # soft_limit_inner = 0.01
     # if rho < soft_limit_inner:
     #     rho = soft_limit_inner
-    
+
     # soft_limit_outter = 0.015
     # if rho > (1-soft_limit_outter):
     #     rho = (1-soft_limit_outter)
-    
+
     if state.table_type == 'dune_weaver_mini':
         x_scaling_factor = 2
         y_scaling_factor = 3.7
     else:
         x_scaling_factor = 2
         y_scaling_factor = 5
-    
+
     delta_theta = theta - state.current_theta
     delta_rho = rho - state.current_rho
     x_increment = delta_theta * 100 / (2 * pi * x_scaling_factor)  # Added -1 to reverse direction
     y_increment = delta_rho * 100 / y_scaling_factor
-    
+
     x_total_steps = state.x_steps_per_mm * (100/x_scaling_factor)
     y_total_steps = state.y_steps_per_mm * (100/y_scaling_factor)
-        
+
     offset = x_increment * (x_total_steps * x_scaling_factor / (state.gear_ratio * y_total_steps * y_scaling_factor))
 
     if state.table_type == 'dune_weaver_mini' or state.y_steps_per_mm == 546:
         y_increment -= offset
     else:
         y_increment += offset
-    
+
     new_x_abs = state.machine_x + x_increment
     new_y_abs = state.machine_y + y_increment
-    
+
     # Use provided speed or fall back to state.speed
     actual_speed = speed if speed is not None else state.speed
-    
+
     # dynamic_speed = compute_dynamic_speed(rho, max_speed=actual_speed)
-    
-    connection_manager.send_grbl_coordinates(round(new_x_abs, 3), round(new_y_abs,3), actual_speed)
+
+    await connection_manager.send_grbl_coordinates(round(new_x_abs, 3), round(new_y_abs,3), actual_speed)
     state.current_theta = theta
     state.current_rho = rho
     state.machine_x = new_x_abs
@@ -615,7 +628,14 @@ def resume_execution():
 def reset_theta():
     logger.info('Resetting Theta')
     state.current_theta = state.current_theta % (2 * pi)
-    connection_manager.update_machine_position()
+    # Run async function in sync context
+    try:
+        loop = asyncio.new_event_loop()
+        asyncio.set_event_loop(loop)
+        loop.run_until_complete(connection_manager.update_machine_position())
+        loop.close()
+    except Exception as e:
+        logger.error(f"Error updating machine position in reset_theta: {e}")
 
 def set_speed(new_speed):
     state.speed = new_speed