Explorar o código

chang ball tracking to polling

tuanchris hai 3 meses
pai
achega
3681a1015c
Modificáronse 2 ficheiros con 87 adicións e 21 borrados
  1. 21 21
      modules/core/pattern_manager.py
  2. 66 0
      modules/led/ball_tracking_manager.py

+ 21 - 21
modules/core/pattern_manager.py

@@ -315,22 +315,12 @@ class MotionControlThread:
         # Call sync version of send_grbl_coordinates in this thread
         self._send_grbl_coordinates_sync(round(new_x_abs, 3), round(new_y_abs, 3), actual_speed)
 
-        # Update state
+        # Update state (ball tracking polls this periodically)
         state.current_theta = theta
         state.current_rho = rho
         state.machine_x = new_x_abs
         state.machine_y = new_y_abs
 
-        # Update ball tracking (manager checks if it's active internally)
-        if state.ball_tracking_manager:
-            state.ball_tracking_manager.update_position(theta, rho)
-            # Debug: Log occasionally to verify updates are being sent
-            if not hasattr(self, '_ball_tracking_log_counter'):
-                self._ball_tracking_log_counter = 0
-            self._ball_tracking_log_counter += 1
-            if self._ball_tracking_log_counter % 100 == 0:
-                logger.info(f"Sent position to ball tracking: theta={theta:.1f}°, rho={rho:.2f}")
-
     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}")
@@ -706,6 +696,10 @@ async def run_theta_rho_file(file_path, is_playlist=False):
             state.ball_tracking_manager.start()
             ball_tracking_active = True
 
+        # Notify ball tracking that pattern is starting (for both "playing_only" and "enabled" modes)
+        if state.ball_tracking_manager and (ball_tracking_active or state.ball_tracking_mode == "enabled"):
+            state.ball_tracking_manager.set_pattern_running(True)
+
         # Set LED effect
         if state.led_controller:
             if ball_tracking_active:
@@ -737,9 +731,11 @@ async def run_theta_rho_file(file_path, is_playlist=False):
                     if state.led_controller:
                         state.led_controller.effect_idle(state.dw_led_idle_effect)
                         start_idle_led_timeout()
-                    # Stop ball tracking on stop
-                    if state.ball_tracking_mode == "playing_only" and state.ball_tracking_manager:
-                        state.ball_tracking_manager.stop()
+                    # Stop ball tracking polling (and manager if mode is "playing_only")
+                    if state.ball_tracking_manager:
+                        state.ball_tracking_manager.set_pattern_running(False)
+                        if state.ball_tracking_mode == "playing_only":
+                            state.ball_tracking_manager.stop()
                     break
 
                 if state.skip_requested:
@@ -748,9 +744,11 @@ async def run_theta_rho_file(file_path, is_playlist=False):
                     if state.led_controller:
                         state.led_controller.effect_idle(state.dw_led_idle_effect)
                         start_idle_led_timeout()
-                    # Stop ball tracking on skip
-                    if state.ball_tracking_mode == "playing_only" and state.ball_tracking_manager:
-                        state.ball_tracking_manager.stop()
+                    # Stop ball tracking polling (and manager if mode is "playing_only")
+                    if state.ball_tracking_manager:
+                        state.ball_tracking_manager.set_pattern_running(False)
+                        if state.ball_tracking_mode == "playing_only":
+                            state.ball_tracking_manager.stop()
                     break
 
                 # Wait for resume if paused (manual or scheduled)
@@ -835,10 +833,12 @@ async def run_theta_rho_file(file_path, is_playlist=False):
             start_idle_led_timeout()
             logger.debug("LED effect set to idle after pattern completion")
 
-        # Stop ball tracking if mode is "playing_only"
-        if state.ball_tracking_mode == "playing_only" and state.ball_tracking_manager:
-            logger.info("Stopping ball tracking (pattern completed)")
-            state.ball_tracking_manager.stop()
+        # Stop ball tracking polling (and manager if mode is "playing_only")
+        if state.ball_tracking_manager:
+            state.ball_tracking_manager.set_pattern_running(False)
+            if state.ball_tracking_mode == "playing_only":
+                logger.info("Stopping ball tracking (pattern completed)")
+                state.ball_tracking_manager.stop()
 
         # Only clear state if not part of a playlist
         if not is_playlist:

+ 66 - 0
modules/led/ball_tracking_manager.py

@@ -59,6 +59,11 @@ class BallTrackingManager:
         self._update_count = 0  # Counter for debug logging
         self._skipped_updates = 0  # Track how many updates were skipped
 
+        # Polling timer for position updates
+        self._poll_timer = None
+        self._poll_interval = 0.5  # Check position every 0.5 seconds
+        self._is_pattern_running = False  # Flag to track if pattern is executing
+
         logger.info(f"BallTrackingManager initialized with {num_leds} LEDs")
 
     def start(self):
@@ -76,6 +81,8 @@ class BallTrackingManager:
             return
 
         self._active = False
+        self._stop_polling()
+
         if self._use_buffer and self.position_buffer:
             self.position_buffer.clear()
         else:
@@ -112,6 +119,65 @@ class BallTrackingManager:
         # Trigger LED update (with optimization)
         self._update_leds_optimized(theta, rho)
 
+    def set_pattern_running(self, is_running: bool):
+        """
+        Notify manager that pattern execution started/stopped
+
+        Args:
+            is_running: True if pattern is executing, False otherwise
+        """
+        self._is_pattern_running = is_running
+
+        if is_running and self._active:
+            # Pattern started, begin polling
+            self._start_polling()
+            logger.info("Pattern started - beginning position polling")
+        else:
+            # Pattern stopped, stop polling
+            self._stop_polling()
+            logger.info("Pattern stopped - stopping position polling")
+
+    def _start_polling(self):
+        """Start the position polling timer"""
+        if self._poll_timer is not None:
+            return  # Already polling
+
+        self._poll_position()  # Do first poll immediately
+
+    def _stop_polling(self):
+        """Stop the position polling timer"""
+        if self._poll_timer is not None:
+            self._poll_timer.cancel()
+            self._poll_timer = None
+
+    def _poll_position(self):
+        """Poll current position from state and update LEDs if needed"""
+        if not self._active or not self._is_pattern_running:
+            self._poll_timer = None
+            return
+
+        try:
+            # Import here to avoid circular dependency
+            from modules.core.state import state
+
+            # Get current position from global state
+            theta = state.current_theta
+            rho = state.current_rho
+
+            # Update position (this will skip if LED zone hasn't changed)
+            self._update_leds_optimized(theta, rho)
+
+        except Exception as e:
+            logger.error(f"Error polling position: {e}")
+
+        # Schedule next poll
+        if self._active and self._is_pattern_running:
+            self._poll_timer = threading.Timer(self._poll_interval, self._poll_position)
+            self._poll_timer.daemon = True
+            self._poll_timer.start()
+        else:
+            self._poll_timer = None
+
     def _update_leds_optimized(self, current_theta: float, current_rho: float):
         """
         Optimized LED update - only recalculates if LED zone changed