Commit 08b5486b authored by vertighel's avatar vertighel
Browse files

mercury: auto-reconnect on BrokenPipeError (stale TCP socket)



Add _close() to reset connection state. get() and put() retry once
after reconnecting on BrokenPipeError/OSError. init() and abort()
now route through self.get("ERR") instead of pidevice.qERR() directly.
wait() and Stage.is_moving also handle dropped connections.

Co-Authored-By: default avatarClaude Sonnet 4.6 <noreply@anthropic.com>
parent 031c8741
Loading
Loading
Loading
Loading
Loading
+74 −50
Original line number Diff line number Diff line
@@ -96,6 +96,21 @@ class Mercury(BaseDevice):

        return self.pidevice

    def _close(self):
        """Reset connection state so _check_connection() will reconnect on next call."""
        if self.pidevice is not None:
            try:
                self.pidevice.__exit__(None, None, None)
            except Exception:
                pass
            self.pidevice = None
        if self._gateway is not None:
            try:
                self._gateway.__exit__(None, None, None)
            except Exception:
                pass
            self._gateway = None


    def __del__(self):
        """
@@ -144,20 +159,26 @@ class Mercury(BaseDevice):
            The GCS query command name without 'q'.
        """

        for attempt in range(2):
            pidevice = self._check_connection()
            if pidevice is None:
                return None
        method = getattr(pidevice, f"q{command.upper()}")
            method = getattr(pidevice, f"q{command.upper()}", None)
            if method is None:
                log.error(f"Command not found: q{command.upper()}")
                return None
            try:
            if (command == "ERR"):
                if command == "ERR":
                    return method()
                else:
                    return method(self.axis)[self.axis]
            except GCSError as e:
                log.error(f"Error: {e}")
                return None
        except AttributeError as e:
            log.error(f"Command not found: {e}")
            except (BrokenPipeError, OSError) as e:
                log.warning(f"Mercury: connection lost ({e}), reconnecting…")
                self._close()
        self.error.append("Connection lost after reconnect attempt")
        return None


@@ -171,17 +192,23 @@ class Mercury(BaseDevice):
            The GCS command name.
        """

        for attempt in range(2):
            pidevice = self._check_connection()
            if pidevice is None:
                return None
        method = getattr(pidevice, command.upper())
            method = getattr(pidevice, command.upper(), None)
            if method is None:
                log.error(f"Command not found: {command.upper()}")
                return None
            try:
                return method(self.axis, *args)
            except GCSError as e:
                log.error(f"Error: {e}")
                return None
        except AttributeError as e:
            log.error(f"Command not found: {e}")
            except (BrokenPipeError, OSError) as e:
                log.warning(f"Mercury: connection lost ({e}), reconnecting…")
                self._close()
        self.error.append("Connection lost after reconnect attempt")
        return None

    def wait(self):
@@ -202,9 +229,13 @@ class Mercury(BaseDevice):
                moving = status_dict[self.axis]
            except GCSError as e:
                log.warning(e)
            except (BrokenPipeError, OSError) as e:
                log.warning(f"Mercury: connection lost during wait ({e})")
                self._close()
                break

            err = pidevice.qERR()
            if err != 0:
            err = self.get("ERR")
            if err and err != 0:
                log.warning(err)

            time.sleep(0.2)
@@ -214,34 +245,21 @@ class Mercury(BaseDevice):
        Perform stage initialization sequence with explicit error clearing.
        """

        pidevice = self._check_connection()
        if pidevice is None:
            return

        # Clear any previous errors
        pidevice.qERR()
        # Clear any previous errors (reconnects automatically if socket is stale)
        self.get("ERR")
        # Servo on
        self.put("SVO", True)

        # Find positive limit
        self.put("FPL")
        self.wait()

        # Find negative limit
        self.put("FNL")
        self.wait()

    def abort(self):
        """
        Perform stage initialization sequence with explicit error clearing.
        """

        pidevice = self._check_connection()
        if pidevice is None:
            return

        """Abort stage motion."""
        # Clear any previous errors
        pidevice.qERR()
        self.get("ERR")
        # STOP!
        self.put("STP", True)

@@ -278,10 +296,16 @@ class Stage(Mercury):
            True if moving, False otherwise.
        """

        for attempt in range(2):
            pidevice = self._check_connection()
            if pidevice is None:
                return None
            try:
                return pidevice.IsMoving(self.axis)[self.axis]
            except (BrokenPipeError, OSError) as e:
                log.warning(f"Mercury: connection lost ({e}), reconnecting…")
                self._close()
        return None


    @property