Przeglądaj źródła

Add homing blocking - prevent movement during homing

Backend changes:
- Add is_homing flag to AppState
- Set flag during homing operation with proper cleanup on success/failure
- Add check_homing_in_progress() helper function
- Block these endpoints while homing: run_theta_rho, run_theta_rho_file,
  run_playlist, move_to_center, move_to_perimeter, send_coordinate
- Add is_homing to status response for frontend awareness
- Return HTTP 409 Conflict if action attempted during homing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
tuanchris 3 tygodni temu
rodzic
commit
ad8d7ba605
3 zmienionych plików z 50 dodań i 11 usunięć
  1. 46 11
      main.py
  2. 1 0
      modules/core/pattern_manager.py
  3. 3 0
      modules/core/state.py

+ 46 - 11
main.py

@@ -78,6 +78,12 @@ def _start_idle_led_timeout():
     )
 
 
+def check_homing_in_progress():
+    """Check if homing is in progress and raise exception if so."""
+    if state.is_homing:
+        raise HTTPException(status_code=409, detail="Cannot perform this action while homing is in progress")
+
+
 def normalize_file_path(file_path: str) -> str:
     """Normalize file path separators for consistent cross-platform handling."""
     if not file_path:
@@ -1279,7 +1285,9 @@ async def run_theta_rho(request: ThetaRhoRequest, background_tasks: BackgroundTa
         if not (state.conn.is_connected() if state.conn else False):
             logger.warning("Attempted to run a pattern without a connection")
             raise HTTPException(status_code=400, detail="Connection not established")
-        
+
+        check_homing_in_progress()
+
         if pattern_manager.get_pattern_lock().locked():
             logger.info("Another pattern is running, stopping it first...")
             await pattern_manager.stop_actions()
@@ -1320,14 +1328,26 @@ async def send_home():
         if not (state.conn.is_connected() if state.conn else False):
             logger.warning("Attempted to move to home without a connection")
             raise HTTPException(status_code=400, detail="Connection not established")
-        
-        # Run homing with 15 second timeout
-        success = await asyncio.to_thread(connection_manager.home)
-        if not success:
-            logger.error("Homing failed or timed out")
-            raise HTTPException(status_code=500, detail="Homing failed or timed out after 15 seconds")
-        
-        return {"success": True}
+
+        if state.is_homing:
+            raise HTTPException(status_code=409, detail="Homing already in progress")
+
+        # Set homing flag to block other movement operations
+        state.is_homing = True
+        logger.info("Homing started - blocking other movement operations")
+
+        try:
+            # Run homing with 15 second timeout
+            success = await asyncio.to_thread(connection_manager.home)
+            if not success:
+                logger.error("Homing failed or timed out")
+                raise HTTPException(status_code=500, detail="Homing failed or timed out after 15 seconds")
+
+            return {"success": True}
+        finally:
+            # Always clear homing flag when done (success or failure)
+            state.is_homing = False
+            logger.info("Homing completed - movement operations unblocked")
     except HTTPException:
         raise
     except Exception as e:
@@ -1339,11 +1359,13 @@ async def run_specific_theta_rho_file(file_name: str):
     file_path = os.path.join(pattern_manager.THETA_RHO_DIR, file_name)
     if not os.path.exists(file_path):
         raise HTTPException(status_code=404, detail="File not found")
-        
+
     if not (state.conn.is_connected() if state.conn else False):
         logger.warning("Attempted to run a pattern without a connection")
         raise HTTPException(status_code=400, detail="Connection not established")
 
+    check_homing_in_progress()
+
     pattern_manager.run_theta_rho_file(file_path)
     return {"success": True}
 
@@ -1391,10 +1413,14 @@ async def move_to_center():
             logger.warning("Attempted to move to center without a connection")
             raise HTTPException(status_code=400, detail="Connection not established")
 
+        check_homing_in_progress()
+
         logger.info("Moving device to center position")
         await pattern_manager.reset_theta()
         await pattern_manager.move_polar(0, 0)
         return {"success": True}
+    except HTTPException:
+        raise
     except Exception as e:
         logger.error(f"Failed to move to center: {str(e)}")
         raise HTTPException(status_code=500, detail=str(e))
@@ -1405,9 +1431,14 @@ async def move_to_perimeter():
         if not (state.conn.is_connected() if state.conn else False):
             logger.warning("Attempted to move to perimeter without a connection")
             raise HTTPException(status_code=400, detail="Connection not established")
+
+        check_homing_in_progress()
+
         await pattern_manager.reset_theta()
         await pattern_manager.move_polar(0, 1)
         return {"success": True}
+    except HTTPException:
+        raise
     except Exception as e:
         logger.error(f"Failed to move to perimeter: {str(e)}")
         raise HTTPException(status_code=500, detail=str(e))
@@ -1519,6 +1550,8 @@ async def send_coordinate(request: CoordinateRequest):
         logger.warning("Attempted to send coordinate without a connection")
         raise HTTPException(status_code=400, detail="Connection not established")
 
+    check_homing_in_progress()
+
     try:
         logger.debug(f"Sending coordinate: theta={request.theta}, rho={request.rho}")
         await pattern_manager.move_polar(request.theta, request.rho)
@@ -1664,7 +1697,9 @@ async def run_playlist_endpoint(request: PlaylistRequest):
         if not (state.conn.is_connected() if state.conn else False):
             logger.warning("Attempted to run a playlist without a connection")
             raise HTTPException(status_code=400, detail="Connection not established")
-        
+
+        check_homing_in_progress()
+
         if not os.path.exists(playlist_manager.PLAYLISTS_FILE):
             raise HTTPException(status_code=404, detail=f"Playlist '{request.playlist_name}' not found")
 

+ 1 - 0
modules/core/pattern_manager.py

@@ -1198,6 +1198,7 @@ def get_status():
         "manual_pause": state.pause_requested,
         "scheduled_pause": is_in_scheduled_pause_period(),
         "is_running": bool(state.current_playing_file and not state.stop_requested),
+        "is_homing": state.is_homing,
         "progress": None,
         "playlist": None,
         "speed": state.speed,

+ 3 - 0
modules/core/state.py

@@ -44,6 +44,9 @@ class AppState:
         self.homed_x = False  # Set to True when [MSG:Homed:X] is received
         self.homed_y = False  # Set to True when [MSG:Homed:Y] is received
 
+        # Homing in progress flag - blocks other movement operations
+        self.is_homing = False
+
         # Angular homing compass reference point
         # This is the angular offset in degrees where the sensor is placed
         # After homing, theta will be set to this value