소스 검색

Add home-on-connect toggle and normalize pattern theta offsets

- Add "Home on Connect" setting to skip automatic homing on startup while
  keeping auto-connect. Users can trigger homing manually later.
- Normalize large theta offsets in patterns to avoid unnecessary revolutions
  at pattern start (handles community patterns starting at e.g. 498 rad).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tuanchris 4 달 전
부모
커밋
4381059695
4개의 변경된 파일73개의 추가작업 그리고 22개의 파일을 삭제
  1. 24 0
      frontend/src/pages/SettingsPage.tsx
  2. 26 19
      main.py
  3. 17 3
      modules/core/pattern_manager.py
  4. 6 0
      modules/core/state.py

+ 24 - 0
frontend/src/pages/SettingsPage.tsx

@@ -44,6 +44,7 @@ interface Settings {
   // Homing settings
   homing_mode?: number
   angular_offset?: number
+  home_on_connect?: boolean
   auto_home_enabled?: boolean
   auto_home_after_patterns?: number
   hard_reset_theta?: boolean
@@ -356,6 +357,7 @@ export function SettingsPage() {
         // Homing settings
         homing_mode: data.homing?.mode,
         angular_offset: data.homing?.angular_offset_degrees,
+        home_on_connect: data.homing?.home_on_connect,
         auto_home_enabled: data.homing?.auto_home_enabled,
         auto_home_after_patterns: data.homing?.auto_home_after_patterns,
         hard_reset_theta: data.homing?.hard_reset_theta,
@@ -658,6 +660,7 @@ export function SettingsPage() {
         homing: {
           mode: settings.homing_mode,
           angular_offset_degrees: settings.angular_offset,
+          home_on_connect: settings.home_on_connect,
           auto_home_enabled: settings.auto_home_enabled,
           auto_home_after_patterns: settings.auto_home_after_patterns,
           hard_reset_theta: settings.hard_reset_theta,
@@ -1059,6 +1062,27 @@ export function SettingsPage() {
               </div>
             )}
 
+            {/* Home on Connect */}
+            <div className="p-4 rounded-lg border space-y-3">
+              <div className="flex items-center justify-between">
+                <div>
+                  <p className="font-medium flex items-center gap-2">
+                    <span className="material-icons-outlined text-base">power</span>
+                    Home on Connect
+                  </p>
+                  <p className="text-xs text-muted-foreground mt-1">
+                    Automatically home when connecting on startup. Disable to connect without homing and home manually later.
+                  </p>
+                </div>
+                <Switch
+                  checked={settings.home_on_connect !== false}
+                  onCheckedChange={(checked) =>
+                    setSettings({ ...settings, home_on_connect: checked })
+                  }
+                />
+              </div>
+            </div>
+
             {/* Auto-Home During Playlists */}
             <div className="p-4 rounded-lg border space-y-3">
               <div className="flex items-center justify-between">

+ 26 - 19
main.py

@@ -104,26 +104,29 @@ async def lifespan(app: FastAPI):
             # Connect without homing first (fast)
             await asyncio.to_thread(connection_manager.connect_device, False)
 
-            # If connected, perform homing in background
+            # If connected, perform homing in background (unless disabled)
             if state.conn and state.conn.is_connected():
-                logger.info("Device connected, starting homing in background...")
-                state.is_homing = True
-                try:
-                    success = await asyncio.to_thread(connection_manager.home)
-                    if not success:
-                        logger.warning("Background homing failed or was skipped")
-                        # If sensor homing failed, close connection and wait for user action
-                        if state.sensor_homing_failed:
-                            logger.error("Sensor homing failed - closing connection. User must check sensor or switch to crash homing.")
-                            if state.conn:
-                                await asyncio.to_thread(state.conn.close)
-                                state.conn = None
-                            return  # Don't proceed with auto-play
-                finally:
-                    state.is_homing = False
-                    logger.info("Background homing completed")
-
-                # After homing, check for auto_play mode
+                if not state.home_on_connect:
+                    logger.info("Device connected. Home-on-connect is disabled — skipping automatic homing.")
+                else:
+                    logger.info("Device connected, starting homing in background...")
+                    state.is_homing = True
+                    try:
+                        success = await asyncio.to_thread(connection_manager.home)
+                        if not success:
+                            logger.warning("Background homing failed or was skipped")
+                            # If sensor homing failed, close connection and wait for user action
+                            if state.sensor_homing_failed:
+                                logger.error("Sensor homing failed - closing connection. User must check sensor or switch to crash homing.")
+                                if state.conn:
+                                    await asyncio.to_thread(state.conn.close)
+                                    state.conn = None
+                                return  # Don't proceed with auto-play
+                    finally:
+                        state.is_homing = False
+                        logger.info("Background homing completed")
+
+                # After homing (or skip), check for auto_play mode
                 if state.auto_play_enabled and state.auto_play_playlist:
                     logger.info(f"Homing complete, checking auto_play playlist: {state.auto_play_playlist}")
                     try:
@@ -408,6 +411,7 @@ class ScheduledPauseSettingsUpdate(BaseModel):
 class HomingSettingsUpdate(BaseModel):
     mode: Optional[int] = None
     angular_offset_degrees: Optional[float] = None
+    home_on_connect: Optional[bool] = None  # Auto-home after connecting on startup
     auto_home_enabled: Optional[bool] = None
     auto_home_after_patterns: Optional[int] = None
     hard_reset_theta: Optional[bool] = None  # Enable hard reset ($Bye) when resetting theta
@@ -679,6 +683,7 @@ async def get_all_settings():
             "mode": state.homing,
             "user_override": state.homing_user_override,  # True if user explicitly set, False if auto-detected
             "angular_offset_degrees": state.angular_homing_offset_degrees,
+            "home_on_connect": state.home_on_connect,
             "auto_home_enabled": state.auto_home_enabled,
             "auto_home_after_patterns": state.auto_home_after_patterns,
             "hard_reset_theta": state.hard_reset_theta  # Enable hard reset when resetting theta
@@ -875,6 +880,8 @@ async def update_settings(settings_update: SettingsUpdate):
             state.homing_user_override = True  # User explicitly set preference
         if h.angular_offset_degrees is not None:
             state.angular_homing_offset_degrees = h.angular_offset_degrees
+        if h.home_on_connect is not None:
+            state.home_on_connect = h.home_on_connect
         if h.auto_home_enabled is not None:
             state.auto_home_enabled = h.auto_home_enabled
         if h.auto_home_after_patterns is not None:

+ 17 - 3
modules/core/pattern_manager.py

@@ -1177,13 +1177,27 @@ async def _execute_pattern_internal(file_path):
     coordinates = await asyncio.to_thread(parse_theta_rho_file, file_path)
     total_coordinates = len(coordinates)
 
-    # Cache coordinates in state for frontend preview (avoids re-parsing large files)
-    state._current_coordinates = coordinates
-
     if total_coordinates < 2:
         logger.warning("Not enough coordinates for interpolation")
         return False
 
+    # Normalize theta values to avoid unnecessary revolutions at pattern start.
+    # Many community patterns have theta starting at high values (e.g., 498 rad ≈ 79 revolutions).
+    # Some patterns also start with two "0 0" origin points before jumping to a large theta.
+    # Detect this preamble and use the third coordinate as the reference instead.
+    ref_theta = coordinates[0][0]
+    if (len(coordinates) >= 3
+            and abs(coordinates[0][0]) < 1e-9 and abs(coordinates[0][1]) < 1e-9
+            and abs(coordinates[1][0]) < 1e-9 and abs(coordinates[1][1]) < 1e-9):
+        ref_theta = coordinates[2][0]
+    theta_offset = ref_theta - (ref_theta % (2 * pi))
+    if abs(theta_offset) > 1e-9:
+        coordinates = [(theta - theta_offset, rho) for theta, rho in coordinates]
+        logger.info(f"Normalized pattern theta by {theta_offset:.2f} rad ({theta_offset / (2 * pi):.1f} revolutions)")
+
+    # Cache coordinates in state for frontend preview (avoids re-parsing large files)
+    state._current_coordinates = coordinates
+
     # Pre-calculate rho-based weights for more accurate time estimation
     # Moves near center (low rho) are slower than perimeter moves due to
     # polar geometry - less linear distance per theta change at low rho

+ 6 - 0
modules/core/state.py

@@ -73,6 +73,10 @@ class AppState:
         # After homing, theta will be set to this value
         self.angular_homing_offset_degrees = 0.0
 
+        # Home on connect: whether to automatically home when connecting on startup
+        # When False, auto-connect still works but homing must be triggered manually
+        self.home_on_connect = True
+
         # Auto-homing settings for playlists
         # When enabled, performs homing after X patterns during playlist execution
         self.auto_home_enabled = False
@@ -480,6 +484,7 @@ class AppState:
             "homing": self.homing,
             "homing_user_override": self.homing_user_override,
             "angular_homing_offset_degrees": self.angular_homing_offset_degrees,
+            "home_on_connect": self.home_on_connect,
             "auto_home_enabled": self.auto_home_enabled,
             "auto_home_after_patterns": self.auto_home_after_patterns,
             "hard_reset_theta": self.hard_reset_theta,
@@ -583,6 +588,7 @@ class AppState:
         self.homing = data.get('homing', 0)
         self.homing_user_override = data.get('homing_user_override', False)
         self.angular_homing_offset_degrees = data.get('angular_homing_offset_degrees', 0.0)
+        self.home_on_connect = data.get('home_on_connect', True)
         self.auto_home_enabled = data.get('auto_home_enabled', False)
         self.auto_home_after_patterns = data.get('auto_home_after_patterns', 5)
         self.hard_reset_theta = data.get('hard_reset_theta', False)