Przeglądaj źródła

add reed homing

tuanchris 3 miesięcy temu
rodzic
commit
c9a0639b2a

+ 23 - 0
main.py

@@ -380,6 +380,29 @@ async def set_scheduled_pause(request: ScheduledPauseRequest):
         logger.error(f"Error updating Still Sands settings: {str(e)}")
         logger.error(f"Error updating Still Sands settings: {str(e)}")
         raise HTTPException(status_code=500, detail=f"Failed to update Still Sands settings: {str(e)}")
         raise HTTPException(status_code=500, detail=f"Failed to update Still Sands settings: {str(e)}")
 
 
+@app.get("/api/angular-homing")
+async def get_angular_homing():
+    """Get current angular homing setting."""
+    return {
+        "angular_homing_enabled": state.angular_homing_enabled
+    }
+
+class AngularHomingRequest(BaseModel):
+    angular_homing_enabled: bool
+
+@app.post("/api/angular-homing")
+async def set_angular_homing(request: AngularHomingRequest):
+    """Update angular homing setting."""
+    try:
+        state.angular_homing_enabled = request.angular_homing_enabled
+        state.save()
+
+        logger.info(f"Angular homing {'enabled' if request.angular_homing_enabled else 'disabled'}")
+        return {"success": True, "message": "Angular homing setting updated"}
+    except Exception as e:
+        logger.error(f"Error updating angular homing setting: {str(e)}")
+        raise HTTPException(status_code=500, detail=f"Failed to update angular homing setting: {str(e)}")
+
 @app.get("/list_serial_ports")
 @app.get("/list_serial_ports")
 async def list_ports():
 async def list_ports():
     logger.debug("Listing available serial ports")
     logger.debug("Listing available serial ports")

+ 88 - 0
modules/connection/connection_manager.py

@@ -8,6 +8,8 @@ import asyncio
 
 
 from modules.core.state import state
 from modules.core.state import state
 from modules.led.led_controller import effect_loading, effect_idle, effect_connected, LEDController
 from modules.led.led_controller import effect_loading, effect_idle, effect_connected, LEDController
+from modules.connection.reed_switch import ReedSwitchMonitor
+
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
 IGNORE_PORTS = ['/dev/cu.debug-console', '/dev/cu.Bluetooth-Incoming-Port']
 IGNORE_PORTS = ['/dev/cu.debug-console', '/dev/cu.Bluetooth-Incoming-Port']
@@ -445,8 +447,94 @@ def home(timeout=15):
                 finally:
                 finally:
                     loop.close()
                     loop.close()
 
 
+            # Wait for device to reach idle state after homing
+            logger.info("Waiting for device to reach idle state after homing...")
+            idle_reached = check_idle()
+
+            if not idle_reached:
+                logger.error("Device did not reach idle state after homing")
+                homing_complete.set()
+                return
+
+            # Perform angular homing if enabled (Raspberry Pi only)
+            if state.angular_homing_enabled:
+                logger.info("Starting angular homing sequence")
+                try:
+                    # Initialize reed switch monitor
+                    reed_switch = ReedSwitchMonitor(gpio_pin=18)
+
+                    if not reed_switch.is_raspberry_pi:
+                        logger.warning("Angular homing is enabled but not running on Raspberry Pi. Skipping angular homing.")
+                    else:
+                        # Move radial arm to perimeter
+                        logger.info("Moving radial arm to perimeter (y20)")
+                        loop = asyncio.new_event_loop()
+                        asyncio.set_event_loop(loop)
+                        try:
+                            result = loop.run_until_complete(send_grbl_coordinates(0, 20, 400, home=False))
+                            if result == False:
+                                logger.error("Failed to move to perimeter for angular homing")
+                                homing_complete.set()
+                                return
+                            state.machine_y += 20
+
+                            # Wait for idle
+                            idle_reached = check_idle()
+                            if not idle_reached:
+                                logger.error("Device did not reach idle state after moving to perimeter")
+                                homing_complete.set()
+                                return
+
+                            # Import pattern_manager here to avoid circular import
+                            from modules.core import pattern_manager
+
+                            # Perform angular rotation until reed switch is triggered
+                            logger.info("Rotating around perimeter to find home position (6.28 radians)")
+                            # We'll do this in small increments to check the reed switch
+                            # One full rotation is 2*pi (6.28) radians
+                            increment = 0.1  # Small angular increment
+                            current_theta = 0
+                            max_theta = 6.28  # One full rotation
+
+                            while current_theta < max_theta:
+                                # Check reed switch
+                                if reed_switch.is_triggered():
+                                    logger.info(f"Reed switch triggered at theta={current_theta}")
+                                    break
+
+                                # Move to next position
+                                current_theta += increment
+                                result = loop.run_until_complete(
+                                    asyncio.to_thread(
+                                        pattern_manager.motion_controller._move_polar_sync,
+                                        current_theta,
+                                        1.0,  # rho = 1.0 (perimeter)
+                                        400   # speed
+                                    )
+                                )
+
+                                # Small delay to allow reed switch to settle
+                                time.sleep(0.05)
+
+                            if current_theta >= max_theta:
+                                logger.warning("Completed full rotation without reed switch trigger")
+
+                            # Set theta to 0 at this position
+                            state.current_theta = 0
+                            logger.info("Angular homing completed")
+
+                        finally:
+                            loop.close()
+                            reed_switch.cleanup()
+
+                except Exception as e:
+                    logger.error(f"Error during angular homing: {e}")
+                    # Continue with normal homing completion even if angular homing fails
+
             state.current_theta = state.current_rho = 0
             state.current_theta = state.current_rho = 0
             homing_success = True
             homing_success = True
+            logger.info("Homing completed and device is idle")
+
             homing_complete.set()
             homing_complete.set()
         except Exception as e:
         except Exception as e:
             logger.error(f"Error during homing: {e}")
             logger.error(f"Error during homing: {e}")

+ 127 - 0
modules/connection/reed_switch.py

@@ -0,0 +1,127 @@
+"""
+Reed switch monitoring module for Raspberry Pi GPIO.
+Used for angular homing to detect home position.
+"""
+import logging
+import platform
+
+logger = logging.getLogger(__name__)
+
+class ReedSwitchMonitor:
+    """Monitor a reed switch connected to a Raspberry Pi GPIO pin."""
+
+    def __init__(self, gpio_pin=18):
+        """
+        Initialize the reed switch monitor.
+
+        Args:
+            gpio_pin: GPIO pin number (BCM numbering) for the reed switch
+        """
+        self.gpio_pin = gpio_pin
+        self.gpio = None
+        self.is_raspberry_pi = self._check_raspberry_pi()
+
+        if self.is_raspberry_pi:
+            try:
+                import RPi.GPIO as GPIO
+                self.gpio = GPIO
+
+                # Set up GPIO mode (BCM numbering)
+                self.gpio.setmode(GPIO.BCM)
+
+                # Set up the pin as input with pull-up resistor
+                # Reed switch should connect pin to ground when triggered
+                self.gpio.setup(self.gpio_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
+
+                logger.info(f"Reed switch initialized on GPIO pin {self.gpio_pin}")
+            except ImportError:
+                logger.warning("RPi.GPIO not available. Reed switch monitoring disabled.")
+                self.is_raspberry_pi = False
+            except Exception as e:
+                logger.error(f"Error initializing reed switch: {e}")
+                self.is_raspberry_pi = False
+        else:
+            logger.info("Not running on Raspberry Pi. Reed switch monitoring disabled.")
+
+    def _check_raspberry_pi(self):
+        """Check if we're running on a Raspberry Pi."""
+        try:
+            # Check if we're on Linux first
+            if platform.system() != 'Linux':
+                return False
+
+            # Check for Raspberry Pi specific identifiers
+            with open('/proc/cpuinfo', 'r') as f:
+                cpuinfo = f.read()
+                if 'Raspberry Pi' in cpuinfo or 'BCM' in cpuinfo:
+                    return True
+
+            # Alternative check using device tree
+            try:
+                with open('/proc/device-tree/model', 'r') as f:
+                    model = f.read()
+                    if 'Raspberry Pi' in model:
+                        return True
+            except FileNotFoundError:
+                pass
+
+            return False
+        except Exception as e:
+            logger.debug(f"Error checking for Raspberry Pi: {e}")
+            return False
+
+    def is_triggered(self):
+        """
+        Check if the reed switch is currently triggered.
+
+        Returns:
+            bool: True if reed switch is triggered (pin is LOW), False otherwise
+        """
+        if not self.is_raspberry_pi or not self.gpio:
+            return False
+
+        try:
+            # Pin is LOW (0) when reed switch is closed (triggered)
+            return self.gpio.input(self.gpio_pin) == 0
+        except Exception as e:
+            logger.error(f"Error reading reed switch: {e}")
+            return False
+
+    def wait_for_trigger(self, timeout=None):
+        """
+        Wait for the reed switch to be triggered.
+
+        Args:
+            timeout: Maximum time to wait in seconds (None = wait indefinitely)
+
+        Returns:
+            bool: True if triggered, False if timeout occurred
+        """
+        if not self.is_raspberry_pi or not self.gpio:
+            logger.warning("Reed switch not available, cannot wait for trigger")
+            return False
+
+        try:
+            # Wait for falling edge (pin goes from HIGH to LOW)
+            channel = self.gpio.wait_for_edge(
+                self.gpio_pin,
+                self.gpio.FALLING,
+                timeout=int(timeout * 1000) if timeout else None
+            )
+            return channel is not None
+        except Exception as e:
+            logger.error(f"Error waiting for reed switch trigger: {e}")
+            return False
+
+    def cleanup(self):
+        """Clean up GPIO resources."""
+        if self.is_raspberry_pi and self.gpio:
+            try:
+                self.gpio.cleanup(self.gpio_pin)
+                logger.info(f"Reed switch GPIO pin {self.gpio_pin} cleaned up")
+            except Exception as e:
+                logger.error(f"Error cleaning up reed switch GPIO: {e}")
+
+    def __del__(self):
+        """Destructor to ensure GPIO cleanup."""
+        self.cleanup()

+ 5 - 1
modules/core/state.py

@@ -34,7 +34,9 @@ class AppState:
         self.gear_ratio = 10
         self.gear_ratio = 10
         # 0 for crash homing, 1 for auto homing
         # 0 for crash homing, 1 for auto homing
         self.homing = 0
         self.homing = 0
-        
+        # Angular homing with reed switch (Raspberry Pi only)
+        self.angular_homing_enabled = False
+
         self.STATE_FILE = "state.json"
         self.STATE_FILE = "state.json"
         self.mqtt_handler = None  # Will be set by the MQTT handler
         self.mqtt_handler = None  # Will be set by the MQTT handler
         self.conn = None
         self.conn = None
@@ -179,6 +181,7 @@ class AppState:
             "y_steps_per_mm": self.y_steps_per_mm,
             "y_steps_per_mm": self.y_steps_per_mm,
             "gear_ratio": self.gear_ratio,
             "gear_ratio": self.gear_ratio,
             "homing": self.homing,
             "homing": self.homing,
+            "angular_homing_enabled": self.angular_homing_enabled,
             "current_playlist": self._current_playlist,
             "current_playlist": self._current_playlist,
             "current_playlist_name": self._current_playlist_name,
             "current_playlist_name": self._current_playlist_name,
             "current_playlist_index": self.current_playlist_index,
             "current_playlist_index": self.current_playlist_index,
@@ -218,6 +221,7 @@ class AppState:
         self.y_steps_per_mm = data.get("y_steps_per_mm", 0.0)
         self.y_steps_per_mm = data.get("y_steps_per_mm", 0.0)
         self.gear_ratio = data.get('gear_ratio', 10)
         self.gear_ratio = data.get('gear_ratio', 10)
         self.homing = data.get('homing', 0)
         self.homing = data.get('homing', 0)
+        self.angular_homing_enabled = data.get('angular_homing_enabled', False)
         self._current_playlist = data.get("current_playlist", None)
         self._current_playlist = data.get("current_playlist", None)
         self._current_playlist_name = data.get("current_playlist_name", None)
         self._current_playlist_name = data.get("current_playlist_name", None)
         self.current_playlist_index = data.get("current_playlist_index", None)
         self.current_playlist_index = data.get("current_playlist_index", None)

+ 83 - 0
static/js/settings.js

@@ -1027,6 +1027,7 @@ async function initializeauto_playMode() {
 document.addEventListener('DOMContentLoaded', function() {
 document.addEventListener('DOMContentLoaded', function() {
     initializeauto_playMode();
     initializeauto_playMode();
     initializeStillSandsMode();
     initializeStillSandsMode();
+    initializeAngularHomingConfig();
 });
 });
 
 
 // Still Sands Mode Functions
 // Still Sands Mode Functions
@@ -1365,3 +1366,85 @@ async function initializeStillSandsMode() {
         });
         });
     }
     }
 }
 }
+
+// Angular Homing Configuration Functions
+async function initializeAngularHomingConfig() {
+    logMessage('Initializing Angular Homing configuration', LOG_TYPE.INFO);
+
+    const angularHomingToggle = document.getElementById('angularHomingToggle');
+    const angularHomingInfo = document.getElementById('angularHomingInfo');
+    const saveHomingConfigButton = document.getElementById('saveHomingConfig');
+
+    // Check if elements exist
+    if (!angularHomingToggle || !angularHomingInfo || !saveHomingConfigButton) {
+        logMessage('Angular Homing elements not found, skipping initialization', LOG_TYPE.WARNING);
+        return;
+    }
+
+    logMessage('All Angular Homing elements found successfully', LOG_TYPE.INFO);
+
+    // Load current angular homing setting
+    try {
+        const response = await fetch('/api/angular-homing');
+        const data = await response.json();
+
+        angularHomingToggle.checked = data.angular_homing_enabled || false;
+        if (data.angular_homing_enabled) {
+            angularHomingInfo.style.display = 'block';
+        }
+    } catch (error) {
+        logMessage(`Error loading angular homing settings: ${error.message}`, LOG_TYPE.ERROR);
+        // Initialize with default (disabled) if load fails
+        angularHomingToggle.checked = false;
+        angularHomingInfo.style.display = 'none';
+    }
+
+    // Function to save settings
+    async function saveAngularHomingSettings() {
+        // Update button UI to show loading state
+        const originalButtonHTML = saveHomingConfigButton.innerHTML;
+        saveHomingConfigButton.disabled = true;
+        saveHomingConfigButton.innerHTML = '<span class="material-icons text-lg animate-spin">refresh</span><span class="truncate">Saving...</span>';
+
+        try {
+            const response = await fetch('/api/angular-homing', {
+                method: 'POST',
+                headers: { 'Content-Type': 'application/json' },
+                body: JSON.stringify({
+                    angular_homing_enabled: angularHomingToggle.checked
+                })
+            });
+
+            if (!response.ok) {
+                const errorData = await response.json();
+                throw new Error(errorData.detail || 'Failed to save angular homing setting');
+            }
+
+            // Show success state temporarily
+            saveHomingConfigButton.innerHTML = '<span class="material-icons text-lg">check</span><span class="truncate">Saved!</span>';
+            showStatusMessage('Angular homing configuration saved successfully', 'success');
+
+            // Restore button after 2 seconds
+            setTimeout(() => {
+                saveHomingConfigButton.innerHTML = originalButtonHTML;
+                saveHomingConfigButton.disabled = false;
+            }, 2000);
+        } catch (error) {
+            logMessage(`Error saving angular homing settings: ${error.message}`, LOG_TYPE.ERROR);
+            showStatusMessage(`Failed to save settings: ${error.message}`, 'error');
+
+            // Restore button immediately on error
+            saveHomingConfigButton.innerHTML = originalButtonHTML;
+            saveHomingConfigButton.disabled = false;
+        }
+    }
+
+    // Event listeners
+    angularHomingToggle.addEventListener('change', () => {
+        logMessage(`Angular homing toggle changed: ${angularHomingToggle.checked}`, LOG_TYPE.INFO);
+        angularHomingInfo.style.display = angularHomingToggle.checked ? 'block' : 'none';
+        logMessage(`Info display set to: ${angularHomingInfo.style.display}`, LOG_TYPE.INFO);
+    });
+
+    saveHomingConfigButton.addEventListener('click', saveAngularHomingSettings);
+}

+ 50 - 0
templates/settings.html

@@ -308,6 +308,56 @@ input:checked + .slider:before {
       </div>
       </div>
     </div>
     </div>
   </section>
   </section>
+  <section class="bg-white rounded-xl shadow-sm overflow-hidden">
+    <h2
+      class="text-slate-800 text-xl sm:text-2xl font-semibold leading-tight tracking-[-0.01em] px-6 py-4 border-b border-slate-200"
+    >
+      Homing Configuration
+    </h2>
+    <div class="px-6 py-5 space-y-6">
+      <div class="flex items-center justify-between">
+        <div class="flex-1">
+          <h3 class="text-slate-700 text-base font-medium leading-normal flex items-center gap-2">
+            <span class="material-icons text-slate-600">explore</span>
+            Angular Homing (Raspberry Pi Only)
+          </h3>
+          <p class="text-xs text-slate-500 mt-1">
+            Enable angular homing using a reed switch on GPIO 18 to establish a home position for rotation.
+          </p>
+        </div>
+        <label class="switch">
+          <input type="checkbox" id="angularHomingToggle">
+          <span class="slider round"></span>
+        </label>
+      </div>
+
+      <div id="angularHomingInfo" class="text-xs text-slate-600 bg-blue-50 border border-blue-200 rounded-lg p-3" style="display: none;">
+        <div class="flex items-start gap-2">
+          <span class="material-icons text-blue-600 text-base">info</span>
+          <div>
+            <p class="font-medium text-blue-800">Angular Homing Details:</p>
+            <ul class="mt-1 space-y-1 text-blue-700">
+              <li>• After radial homing, the arm moves to the perimeter (y20)</li>
+              <li>• The table rotates around the perimeter until the reed switch on GPIO 18 is triggered</li>
+              <li>• This position is set as the angular home (theta = 0)</li>
+              <li>• Only works on Raspberry Pi with a reed switch connected to GPIO 18</li>
+              <li>• Reed switch should connect GPIO 18 to ground when triggered</li>
+            </ul>
+          </div>
+        </div>
+      </div>
+
+      <div class="flex justify-end">
+        <button
+          id="saveHomingConfig"
+          class="flex items-center justify-center gap-2 min-w-[140px] cursor-pointer rounded-lg h-10 px-4 bg-sky-600 hover:bg-sky-700 text-white text-sm font-medium leading-normal tracking-[0.015em] transition-colors"
+        >
+          <span class="material-icons text-lg">save</span>
+          <span class="truncate">Save Homing Config</span>
+        </button>
+      </div>
+    </div>
+  </section>
   <section class="bg-white rounded-xl shadow-sm overflow-hidden">
   <section class="bg-white rounded-xl shadow-sm overflow-hidden">
     <h2
     <h2
       class="text-slate-800 text-xl sm:text-2xl font-semibold leading-tight tracking-[-0.01em] px-6 py-4 border-b border-slate-200"
       class="text-slate-800 text-xl sm:text-2xl font-semibold leading-tight tracking-[-0.01em] px-6 py-4 border-b border-slate-200"

+ 88 - 0
test_reed_switch.py

@@ -0,0 +1,88 @@
+#!/usr/bin/env python3
+"""
+Simple test script to verify reed switch functionality on GPIO 18.
+Run this script on your Raspberry Pi to test the reed switch.
+
+Usage:
+    python test_reed_switch.py
+"""
+
+import time
+import sys
+
+try:
+    from modules.connection.reed_switch import ReedSwitchMonitor
+except ImportError:
+    print("Error: Could not import ReedSwitchMonitor")
+    print("Make sure you're running this from the dune-weaver directory")
+    sys.exit(1)
+
+def main():
+    print("=" * 60)
+    print("Reed Switch Test - GPIO 18")
+    print("=" * 60)
+    print()
+
+    # Initialize the reed switch monitor
+    print("Initializing reed switch monitor on GPIO 18...")
+    reed_switch = ReedSwitchMonitor(gpio_pin=18)
+
+    # Check if we're on a Raspberry Pi
+    if not reed_switch.is_raspberry_pi:
+        print("❌ ERROR: Not running on a Raspberry Pi!")
+        print("This test must be run on a Raspberry Pi with GPIO support.")
+        return
+
+    print("✓ Running on Raspberry Pi")
+    print("✓ GPIO initialized successfully")
+    print()
+    print("=" * 60)
+    print("MONITORING REED SWITCH")
+    print("=" * 60)
+    print()
+    print("Instructions:")
+    print("  • The reed switch should be connected:")
+    print("    - One terminal → GPIO 18")
+    print("    - Other terminal → Ground (any GND pin)")
+    print()
+    print("  • Bring a magnet close to the reed switch to trigger it")
+    print("  • You should see 'TRIGGERED!' when the switch closes")
+    print("  • Press Ctrl+C to exit")
+    print()
+    print("-" * 60)
+
+    try:
+        last_state = None
+        trigger_count = 0
+
+        while True:
+            # Check if reed switch is triggered
+            is_triggered = reed_switch.is_triggered()
+
+            # Only print when state changes (to avoid spam)
+            if is_triggered != last_state:
+                if is_triggered:
+                    trigger_count += 1
+                    print(f"🔴 TRIGGERED! (count: {trigger_count})")
+                else:
+                    print("⚪ Not triggered")
+
+                last_state = is_triggered
+
+            # Small delay to avoid overwhelming the GPIO
+            time.sleep(0.05)
+
+    except KeyboardInterrupt:
+        print()
+        print("-" * 60)
+        print(f"✓ Test completed. Reed switch was triggered {trigger_count} times.")
+        print()
+
+    finally:
+        # Clean up GPIO
+        reed_switch.cleanup()
+        print("✓ GPIO cleaned up")
+        print()
+
+if __name__ == "__main__":
+    main()